A card-issuing platform exposes an API where duplicate requests would move money twice. What is the complete idempotency contract, including the cases most implementations miss?
Show the full answer Hide the answer
The core contract
The client supplies a key. The server stores the key, a fingerprint of the request, the resulting state and the response to replay — committed in the same transaction as the state change. A repeat of the same key returns the stored response without re-executing.
Key first then effect makes a real request look like a duplicate forever after a crash. Effect first then key produces a duplicate on retry. One transaction, or the mechanism does not hold.
The cases most implementations miss
- The concurrent retry. The key exists, the response does not, because the first request is still running. Returning "duplicate" is wrong and returning success is a lie. A distinct in-progress response — 409 with a retry-after — is the only correct answer, and under load it stops being rare.
- The key reused with different parameters. Without a stored request fingerprint, the client silently gets the previous response for a different operation. That is worse than a duplicate because nothing signals it. Return a conflict.
- Scope. The key must be unique within (tenant, key). Global uniqueness lets one customer collide with another's key space.
- Lifetime. The record must outlive the longest client retry window, including a mobile client that retries the next morning after being backgrounded.
- Coverage. Creation is always keyed; refunds, captures, cancellations, adjustments and inbound webhooks frequently are not — and those are the lower-volume, higher-consequence flows.
- Failed requests. Should a key that produced a 500 be replayable, or should a retry be allowed to try again? The answer must be deliberate: replay a deterministic client error, allow retry on a server error, and never leave it to whichever branch the code happens to take.
What the platform must document
The client cannot use this correctly without knowing: what constitutes a duplicate, how long keys live, what happens on parameter mismatch, what happens during a concurrent retry, and whether errors are replayed. Idempotency documented as "send an idempotency key" is not a contract, and integrators will build against assumptions that differ from yours.
The ordering that prevents most incidents
Persist the attempt with its key before calling the downstream network. A crash mid-call then leaves a record to reconcile from rather than an authorisation that exists at the network and nowhere in your system. That single ordering choice is the origin of most reconciliation breaks in payment infrastructure.