Skip to content
ZeroKYC Pay

Rate limits & idempotency

Per-key limits with X-RateLimit headers and Retry-After, plus Idempotency-Key semantics: replays, conflicts, the 24-hour window.

Two mechanisms keep your integration reliable under load and under retries: per-key rate limits, and idempotent invoice creation.

Rate limits

Limits are per API key, in a fixed 60-second window:

ScopeEndpointsLive keySandbox key (pk_test_…)
Invoice creationPOST /v1/invoices30 / min10 / min
ReadsGET /v1/invoices…, /v1/events, /v1/payouts300 / min90 / min

Every authenticated response carries the counters, so you never have to guess:

X-RateLimit-Limit: 30
X-RateLimit-Remaining: 27
X-RateLimit-Reset: 1694007937

X-RateLimit-Reset is a Unix timestamp of the window end. When you exceed the limit the API answers 429 rate_limited with an extra Retry-After: <seconds> header — wait it out, don't tight-loop. If your checkout legitimately bursts higher, batch order creation or contact us about a raised limit.

Polling guidance: GET /v1/invoices/{id} once every few seconds per open checkout is far below the read budget. You rarely need to poll at all — the webhook is the intended push channel; poll as a fallback.

Idempotent invoice creation

POST /v1/invoices accepts an Idempotency-Key header (up to 120 characters). Send it on every creation — it turns network-timeout retries from "accidental double invoice" into "same invoice returned again":

  • Same key + same body within 24 hours → the original invoice is returned again with 201 and an extra Idempotent-Replay: true header. Not a duplicate, not an error.
  • Same key + different body → 409 idempotency_conflict. The key is bound to the exact request body (canonically hashed); change the payload — change the key.
  • Keys are scoped per merchant and per mode: the same key used with a pk_test_… and later a pk_live_… key never collide.
curl -X POST https://api.zerokyc-payments.com/v1/invoices \
  -H "Authorization: Bearer pk_live_..." \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-1042-create" \
  -d '{"amount": "49.00", "order_id": "order-1042", "payment_currency": "any"}'

A good key is deterministic — derive it from your order id and intent (order-1042-create), so a retry after a crash or a timeout reuses it automatically. Random keys defeat the purpose.

Putting both together

  1. Create with a deterministic Idempotency-Key; treat both 201 (with or without Idempotent-Replay) as success and store the invoice id against your order.
  2. On 429, sleep for Retry-After seconds and retry the same request — idempotency makes it safe.
  3. On 5xx/timeouts, retry with exponential backoff, same key, same body.
  4. Fulfil on the payment.confirmed webhook; use reads for reconciliation, not as the primary signal.