What is idempotency, and why do payments break without it?
The request timed out. The payment may have gone through. You have to decide what to do next, and both obvious answers are wrong.
Two requests arrived. One charge exists. That is the whole idea.
The problem it solves
Networks fail in a specific and awkward way. A request goes out, and no response comes back. That tells you the response was lost. It does not tell you whether the request arrived.
So after a timeout there are exactly two possibilities and no way to distinguish them from where you are standing. The payment was created and you did not hear. Or the payment was never created.
Retry, and you risk charging twice. Do not retry, and you risk a customer who believes they paid and has no order. Both are real failures and neither is acceptable, which is why the answer is not a better guess but a different design.
How the key works
The client generates a unique key per logical operation, not per HTTP call, and sends it with the request. The provider stores the key alongside the result.
On a repeat request with a key it has already seen, the provider does not perform the operation again. It returns the result of the first attempt. The retry becomes safe, and the ambiguity of the timeout stops mattering, because asking twice and asking once produce the same outcome.
Two details matter in practice. The key must be generated before the first attempt and reused across every retry of that same operation; generating a new key on retry defeats the entire mechanism and is the most common implementation error. And keys expire, usually after a day, so this protects against retries rather than against a duplicate submitted a week later.
What it does not cover
Idempotency protects one operation against duplication. It does not protect a customer from pressing pay twice on two different requests with two different keys, which is a different problem solved by disabling the button and by server-side duplicate detection on amount, customer and time window.
It also does not make a distributed system consistent. If your record of a payment and the provider's record disagree, idempotency does not reconcile them. That is what webhooks and reconciliation exist for, and any serious payment integration needs all three: idempotency for safe retries, webhooks for authoritative state, reconciliation for the disagreements that survive both.
A lift button that is already lit. Press it four more times and the lift does not come four more times, because the building recorded the request rather than the press. The person pressing repeatedly is not wrong to press. The system is simply built so that it does not matter.
Where you meet it
Every payment API with an `Idempotency-Key` header. Every checkout that disables the button after one press. Every support ticket that begins "I was charged twice".
Building this? A second pair of eyes on the architecture is what the advisory is for. →
