Rate limits
Per-merchant request quotas, response headers, and recommended retry strategies.
Implement exponential backoff in your integration from the start. Most client libraries support this out of the box — honour the Retry-After header rather than retrying at a fixed interval.
Merchant API rate limit
payzum enforces a per-merchant limit on all authenticated endpoints:
| Parameter | Value |
|-----------|-------|
| Window | 60 seconds (rolling) |
| Max requests | 60 |
| Limit key | Merchant ID (derived from the API key) |
| Rate-limit header on 429 | Retry-After: 60 |
When the limit is exceeded the server responds immediately with:
HTTP/1.1 429 Too Many Requests
Retry-After: 60
Content-Type: application/json
{ "statusCode": 429, "code": "RATE_LIMIT_EXCEEDED", "message": "..." }
Back off for at least as many seconds as indicated by Retry-After before issuing the next request.
Shared budget across adapters
The /v1/* native API and the /legacy/* compatibility adapter share the same per-merchant rate-limit budget. If your integration uses both paths simultaneously — for example, the native API for invoice creation and the legacy adapter for older shopping-cart plugins — both sets of requests count against the single 60 rpm quota.
Buyer-facing invoice polling
The GET /v1/invoices/:id/status endpoint used by the hosted checkout and embeddable widget to poll invoice progress uses a separate limiter keyed on the client IP address. Polling traffic from buyers does not count against the merchant's 60 rpm budget.
Recommended retry strategy
async function fetchWithRetry(url: string, options: RequestInit, maxAttempts = 4): Promise<Response> {
for (let attempt = 0; attempt < maxAttempts; attempt++) {
const res = await fetch(url, options);
if (res.status !== 429) return res;
const retryAfter = Number(res.headers.get("Retry-After") ?? 60);
const jitter = Math.random() * 1000;
// exponential backoff: 60s, 120s, 240s … capped at Retry-After base
const delay = retryAfter * 1000 * Math.pow(2, attempt) + jitter;
if (attempt < maxAttempts - 1) await new Promise((r) => setTimeout(r, delay));
}
throw new Error("Rate limit: max retry attempts exceeded");
}Do not retry 429 QUOTA_EXCEEDED with the same request. That error means the merchant has too many open invoices — retrying will keep hitting the same quota wall. Close or expire existing invoices first, then create new ones.