← all writing
Tech By Jesse Moraga · Sep 23, 2026 · 3 min read

The Idempotency-Key Header: How Payment APIs Make POST Safe to Retry

Fault tolerance for POST is a header plus a cache, and that's basically the whole trick.

A request goes out. The network eats the response on the way back. Now your client has no idea whether it charged the card or not, and both guesses are bad: retry and you might double-charge, don't retry and the customer might never get billed at all.

Payment APIs solved this years ago and almost nobody outside of payments copied it.

CLIENT                                  SERVER
  |                                       |
  | POST /invoices                        |
  | Idempotency-Key: 8f3c...a91  --------->|  key seen? NO
  |                                       |  -> do the work
  |                                       |  -> store key + response
  |   X  response lost in transit  <-------|
  |                                       |
  | RETRY (same key, same body)           |
  | Idempotency-Key: 8f3c...a91  --------->|  key seen? YES
  |                                       |  -> skip the work
  |   200 (replayed, identical)   <-------|  -> replay stored response
  |                                       |

Look at the second pass: the server does no work. It reads the key, finds the stored result, and hands back the same bytes.

The key is generated by the client, not the server, and that's the part people get backwards. It has to be client-side, because the whole failure mode is "I never heard back", so the client is the only party who knows that attempt two is really attempt one wearing a different hat. A UUID per logical operation. Not per HTTP call. Per thing you meant to do.

Server side you need three behaviors and they're all boring. Store the key with the response you produced. On a repeat key, replay that response instead of re-running the handler. And if a request comes in with the same key but a different body, reject it, because that's a bug on the client, not a retry. The IETF draft for the Idempotency-Key HTTP header spells out the fingerprinting and conflict semantics better than I can, and it's short enough to read in one sitting.

In my own company's back office, the places that needed this weren't glamorous. Invoice creation. Sending a follow-up. Anything the phone assistant kicks off, because a call can drop mid-sentence and the retry logic upstream doesn't know what already landed. Before I added keys I had a handful of duplicate records that I had to go clean up by hand, which is exactly the kind of unpaid janitorial work I got into building systems to avoid. Now the key rides along with the operation and a replay is a non-event.

The common practice I'll push back on: wrapping the write in a "check if it exists first" query and calling that idempotent. It isn't. Two retries can both read "doesn't exist" before either one writes, and now you've got the same duplicate with extra steps. The key has to be unique in the storage layer so the database refuses the second one, not your application logic hoping it wins a race.

Set a retention window and be honest about it. Twenty-four hours is common. Keys aren't permanent history, they're a short memory for "did I already do this".

If a client can't tell whether the write happened, give it a key so the server can tell for it.

Jesse

growth-as-a-service

I build with AI so small businesses can take on the giants. Let me build yours.

Visit Art3ry → art3ry.com