A payments API must guarantee that a network retry never charges a customer twice. Design the mechanism end to end.
Show the full answer Hide the answer
Why this is unavoidable
A client that times out on a POST cannot know whether the request succeeded. Not retrying risks a lost payment; retrying risks a duplicate. There is no client-side solution — the server must make retry safe.
The mechanism
The client generates a key — a UUID, generated once per logical operation and reused across retries. If the client generates a new key on retry, the mechanism does nothing, so this must be explicit in the documentation and the client SDK.
The server records the key before doing the work, with a unique constraint. The insert is what serialises concurrent duplicates — application-level checking has a race window.
On a duplicate key, return the stored response, not a new attempt and not an error. Same status code, same body.
Handle the in-flight case. A duplicate arriving while the first is still processing should return 409 so the client retries shortly, rather than blocking or double-processing.
Store the key and the result in the same transaction as the business operation. If they can diverge, the guarantee is gone.
The specifications that get missed
Scope: per API key or account. Global scope creates cross-tenant collisions and leaks information.
Retention: longer than any plausible retry. Not minutes. A client retrying after a deployment, a queued job redelivered hours later, a mobile client that was offline. 24 hours is a common floor; shorter windows silently reintroduce duplicates in exactly the cases the mechanism exists for.
Key reuse with a different payload is an error, not a replay. It means the client reused a key for a different operation. Store a hash of the request and return a clear error on mismatch — this catches a real class of client bug.
Downstream
Your idempotency does not extend to the payment processor. Propagate a deterministic key derived from yours to their API, so a retry at that boundary is also safe. Every hop in the chain needs its own guarantee.
The framing that matters
There is no exactly-once delivery — there is at-least-once delivery plus idempotent processing.
Idempotency keys are how that principle is implemented at an API boundary. The same principle appears as
consumer-side deduplication in messaging and as ON CONFLICT in data pipelines.