A payment request that times out is the most dangerous response an API can give. The charge may have gone through, or it may not have — the client has no way to tell. Retry blindly and you risk charging the customer twice; give up and you risk dropping a sale that already succeeded. Idempotency keys exist to make that ambiguity safe.
One key, one effect
An idempotency key is a unique token the client generates and attaches to a mutating request — usually a UUID per logical operation, sent in a header. The server's contract is simple: the first request carrying a given key is processed normally; every subsequent request carrying the same key returns the stored result of the first, without executing the side effect again. The customer is charged once no matter how many times the request arrives. The key is scoped to the operation, not the HTTP call, so a checkout and its retries share one key while two distinct checkouts never collide.
Generating the key on the client is what makes this work. If the server minted it, a dropped response would leave the client without the token it needs to retry safely. The client creates the key before the first attempt, persists it alongside the pending operation, and reuses it for every retry until it gets a definitive answer. Only then does it discard the key and move on.
Storing results without races
On the server, the key maps to a record in durable storage: the request fingerprint, a status, and the eventual response. The first request inserts this row inside the same transaction that performs the charge, so the key and its effect commit or roll back together. Concurrent retries — common when a client fires a second attempt before the first finishes — are serialized with a unique constraint on the key; the loser of that race either waits for the in-flight result or is told to retry shortly. We also fingerprint the request body so a key reused with different parameters is rejected rather than silently returning the wrong stored answer.
A retry should be boring. If replaying a request can change the outcome, the API isn't finished — it's a liability waiting for a flaky network.— Protocore · Payments engineering
The details that bite are operational. Keys need a retention window long enough to outlive any reasonable retry but short enough to bound storage — we keep ours for 24 hours and reject stragglers after that. In-flight records need a timeout so a crashed first attempt does not wedge the key forever. And the whole mechanism has to be documented loudly, because a client that does not send keys gets none of the protection. Done right, idempotency turns the network's worst habit — losing your response after doing the work — into a non-event the customer never sees.
Have a system to build?
Tell us the problem. We'll come back with an architecture and a plan.
Start a project