Idempotency
Safe retries with X-Idempotency-Key on mutating endpoints.
Mutating endpoints accept an X-Idempotency-Key header so that a network retry of the same logical operation returns the original response instead of creating a duplicate. This is the safe-retry contract for funds-moving and card-issuing requests.
Where it applies
| Endpoint | Header | Durability |
|---|---|---|
POST /cards | Optional | Response cached 24 h |
POST /cards/{cardId}/funding | Optional | Durable — no expiry |
POST /cards/{cardId}/withdrawal | Optional | Durable — no expiry |
POST /payouts | Required | Durable — no expiry |
Other endpoints are either read-only or naturally idempotent and do not require the header. See Durability & failed retries for how the two durability models differ.
Header format
X-Idempotency-Keystringheader1–64 characters matching [a-zA-Z0-9_-]. Pick a unique value per logical operation — a UUID, your own order id, or any short stable identifier.
Behavior
| Request | Server behavior |
|---|---|
| New key | Process normally and record the outcome |
| Same key + same body, first attempt completed | Return the original response — no side effect |
Same key + different body (or a different cardId) | 409 Conflict — never silently dedup the wrong response |
| Same key while the first attempt is still processing | 409 Conflict, "in progress, please retry shortly" |
Durability & failed retries
The two endpoint groups differ in how long a key is remembered and what happens after a failure:
- Card issuance (
POST /cards) caches the successful response for 24 hours; afterwards the key is treated as new. A failed attempt releases the key immediately, so you may retry with the same key once you have fixed the cause. - Funds movement (funding, withdrawal, payouts) uses durable idempotency with no expiry — the recorded outcome is returned indefinitely, so a retry never moves funds twice. For funding and withdrawal, a definitively failed operation makes the key terminal: retrying it returns
409— submit a new key to make a fresh attempt, and confirm the prior outcome viaGET /operations/{idempotencyKey}. For payouts, the original order (including aFAILEDstatus) is returned on retry; reconcile viaGET /payouts/{merOrderNo}.
X-Idempotency-Key vs X-Nonce
These two headers solve different problems and must not be confused.
X-Idempotency-Key | X-Nonce | |
|---|---|---|
| Purpose | Idempotency — safe retry of one logical operation | Anti-replay — block reuse of a signed request |
| Lifetime | 24 h cache of the response | 5 min — single use only |
| On retry | Reuse the same value | Rotate — every physical request must carry a fresh nonce |
| Same value reused | Returns the original response | 409 Conflict, request rejected |
A network retry must rotate X-Nonce (and recompute X-Signature with a fresh X-Timestamp) while reusing X-Idempotency-Key.
Retry pattern
Generate the idempotency key once
At the moment the operation enters your queue, pick a UUID and store it next to the operation record. Reuse it across every retry of that operation.
Rotate nonce, timestamp, and signature per attempt
Every physical HTTP request must carry a fresh X-Nonce, a current X-Timestamp, and a recomputed X-Signature. The replay-protection layer rejects any reused nonce within its 5-minute window.
On retry exhaustion, query for state
If retries fail, do not blindly create a new operation. Check the outcome via GET /operations/{idempotencyKey} for funding and withdrawal, via GET /cards/{cardId} or the CARD_CREATED webhook for card issuance, and via GET /payouts/{merOrderNo} or the PAYOUT_* webhooks for payouts.
This 24-hour caveat applies only to card issuance, whose key expires after 24 h — if your retry budget exceeds that window, rotate the key and verify the prior outcome first to avoid a double-charge. Funds-movement keys (funding, withdrawal, payouts) are durable and never expire: do not rotate them — a same-key retry is always safe and returns the original outcome.