Buvei
Concepts

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

EndpointHeaderDurability
POST /cardsOptionalResponse cached 24 h
POST /cards/{cardId}/fundingOptionalDurable — no expiry
POST /cards/{cardId}/withdrawalOptionalDurable — no expiry
POST /payoutsRequiredDurable — 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-Keystringheader

1–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

RequestServer behavior
New keyProcess normally and record the outcome
Same key + same body, first attempt completedReturn 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 processing409 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 via GET /operations/{idempotencyKey}. For payouts, the original order (including a FAILED status) is returned on retry; reconcile via GET /payouts/{merOrderNo}.

X-Idempotency-Key vs X-Nonce

These two headers solve different problems and must not be confused.

X-Idempotency-KeyX-Nonce
PurposeIdempotency — safe retry of one logical operationAnti-replay — block reuse of a signed request
Lifetime24 h cache of the response5 min — single use only
On retryReuse the same valueRotate — every physical request must carry a fresh nonce
Same value reusedReturns the original response409 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.