Heads up: posts on this site are drafted by Claude and fact-checked by Codex. Both can still get things wrong — read with care and verify anything load-bearing before relying on it.
why how

Why idempotency keys exist

The network can drop your response after the work is done. Now you have to retry — and you have no idea whether you'd be doing it for the first time or the second. Idempotency keys are the small protocol the client and server agree on so the retry is safe.

Systems intro Apr 29, 2026

Why it exists

You call POST /charges for $42. The request goes out. The connection times out. Did the charge happen?

You don’t know. The packet that would have told you got lost — but the packet that asked for the work might have arrived just fine. From the client’s seat, “request succeeded but reply was dropped” is indistinguishable from “request never made it.” Both look like a timeout.

So you have to choose, and both choices are bad:

This is the core problem. The network can hide the answer without hiding the work. Idempotency is the way out: design the operation so retrying it is harmless. For a GET that’s free — reading the same row twice is the same as reading it once. For a write, you usually need help. The help is an idempotency key: a token the client invents and sends along, that the server uses to recognize “I’ve already done this exact request, here’s the same answer I gave last time.”

The key turns a dangerous retry into a safe lookup.

Why it matters now

Anywhere the cost of a duplicate is real, idempotency keys appear:

The pattern shows up wherever a network or process boundary can swallow an acknowledgement. Which is, on a long enough timeline, everywhere.

The short answer

idempotency key = client-generated unique ID + server-side dedupe table

The client picks a unique value (often a UUID) before it sends the request, and reuses that same value for every retry of that logical operation. The server, before doing the work, checks a dedupe store: “have I seen this key?” If yes, return the recorded response. If no, do the work, record the response under the key, then return it. Two halves: the client’s promise that the same key means the same intent, and the server’s commitment to remember.

How it works

The basic dance

For a single POST /charges with key k:

  1. Client generates k once, before the first attempt. Sends Idempotency-Key: k plus the request body.
  2. Server looks up k in its dedupe store.
    • Hit, with a stored response → return it. The work has already been done; the client just didn’t hear about it.
    • Miss → mark k as in-progress, do the work, record the response under k, return the response.
    • Hit, but still in-progress → either wait for the first attempt to finish, or return a “duplicate-in-flight” error so the client backs off and retries later.
  3. Client retries on timeout/5xx, with the same k, and gets either the original outcome or a quick deterministic error.

That’s the whole mechanism. The subtleties are all in the details of each step.

Why the client picks the key, not the server

It has to be the client. The whole point is to survive the case where the server’s response never arrives — so the client must be able to identify the operation before it has any reply to anchor on. A server-generated key is fine for referencing a created resource afterwards; it can’t be used to deduplicate the request that created it.

What the server actually stores

A dedupe store is at minimum a map from key to “I’m working on it” or “here’s the recorded result.” A common implementation puts the key in a row of the same database the work itself writes to, and updates both inside one transaction — so the key insertion and the side effect commit together or not at all. That atomicity is the part most home-grown implementations get wrong.

A common shape:

INSERT INTO idempotency_keys (key, request_fingerprint, status)
VALUES ($1, $2, 'in_progress')
ON CONFLICT (key) DO NOTHING;

If the insert wins, this attempt does the work and updates the row to completed with the response, in the same transaction as the business write. If it loses, the row already exists and the server hands back whatever’s already recorded.

Same key, same request

Idempotency keys are a promise about intent, not just identity. If the client sends key k once with {amount: 42} and again with {amount: 4200}, the server should not silently treat the second as a duplicate of the first — that would let a bug or a race quietly overwrite a charge with a different one.

The defensive move is for the server to also store a fingerprint of the request body and reject mismatches. Stripe’s API documents exactly this behavior — replaying a key with a different payload is an error, not a silent dedupe. Anything less is a footgun.

Keys expire

Storing every key forever is unbounded growth. Most real systems give keys a TTL — Stripe’s v1 docs say keys can be pruned once they’re at least 24 hours old; v2 extends the replay window much further. The retention has to comfortably exceed the client’s worst-case retry budget, or else the client retries with a key the server has already forgotten, the dedupe miss looks fresh, and the operation runs again.

Show the seams

Going deeper