Every payment API eventually has to answer an uncomfortable question: what happens when the same request arrives twice? A customer’s connection drops after they tap pay. A mobile client retries a timed-out request. A queue redelivers a message it already processed. None of these are edge cases — at any real transaction volume, they happen every day. The systems that handle this well are boring in exactly the right way. The ones that don’t end up double-charging customers, or worse, silently dropping money on the floor.
Idempotency keys, not clever deduplication
The reliable pattern is simple: the client generates a unique key per logical operation, and the server stores the result of the first request under that key. Every retry with the same key returns the original result instead of processing again — no timestamp heuristics, no “was this close enough in time” guessing.
Where teams get this wrong
- Generating the key server-side, which defeats the point — the client needs to send the same key on retry.
- Storing the key without storing the response, so a retry re-runs side effects even though it recognises the duplicate.
- Scoping keys too broadly, so two different customers’ legitimate requests collide.
None of this is exotic engineering. It’s the unglamorous plumbing that decides whether your payment system is boring in production, or exciting in the way nobody wants.