An idempotency key is useful only when the server and client share a precise retry contract. The contract must define the key’s scope, how request equality is checked, what happens during concurrent attempts, which result is replayed, and how long the record survives. A unique column is merely one implementation detail.
Bind a key to one intended operation
The client creates a high-entropy key for one logical operation and reuses it only when retrying that operation. The server scopes it at least by caller and endpoint; otherwise two customers or two operations can collide. Persist a request fingerprint over the semantically relevant inputs. If the same key arrives with a different fingerprint, reject it with a stable conflict error rather than guessing which request the client intended.
The IETF draft for the Idempotency-Key HTTP header describes uniqueness, expiry policies, request fingerprints, and duplicate handling as parts of the resource server’s published policy. It is an Internet-Draft, so pin the implemented behavior in your own API contract rather than treating the draft as an immutable standard.
Canonicalization deserves care. Hashing raw JSON bytes makes whitespace and object-key order significant. Hashing an incomplete subset can treat different operations as equal. Prefer a documented semantic representation produced after parsing and validation. Keep authorization-relevant context, such as account or tenant identity, outside attacker-controlled request fields and inside the storage key.
Serialize concurrent duplicates
Two identical requests can arrive before either has committed. Insert an in-progress record atomically, then let one execution own it. A duplicate should wait briefly, poll a status resource, or receive a documented request_in_progress response. It must not execute the side effect again. If the owner crashes, a lease and recovery process must determine whether the operation committed before another worker takes over.
This is where database and business transactions meet. Store the idempotency record and durable side effect in one transaction when possible. When an external system is involved, record an operation identifier before calling it and use that identifier in the downstream system too. AWS’s discussion of safe retries with idempotent APIs stresses caller-provided request identifiers and the ambiguity created by late-arriving requests.
Replay the result the client needs
After completion, replay the same semantic outcome: status, response body, and resource identity. Decide explicitly whether deterministic client errors are cached. Caching validation failures can be sensible because the same operation should not change under the same key, but transient overload or dependency failures may deserve another attempt. Never silently rerun merely because the first response was a failure; document the rule.
Retention sets the retry horizon. Publish how long keys remain valid and return a distinct response when a client retries after expiry, because the server may no longer know whether the original side effect happened. Retention should cover realistic client retry schedules and delayed network delivery while limiting storage and replay exposure.
Specify observable states
A complete contract names four cases: new key, same key and same request while in progress, same key and same request after completion, and same key with a different request. Add an expired-key case if the server can distinguish it. For each, publish the response code, machine error code, retry guidance, and whether the operation may have executed.
Start by writing those five rows for one money-moving or resource-creation endpoint. Then test simultaneous duplicates, a crash after commit but before response, a changed payload, and a retry just beyond retention. If any result leaves the client unable to decide whether to retry, the protocol is unfinished.
Protect the record itself
Apply authorization before looking up or replaying a stored result, and never let a caller probe whether another caller used a key. Encrypt sensitive cached bodies or store a reference to the durable resource instead. Bound key and response sizes, rate-limit new keys, and verify that log pipelines do not capture credentials or full payloads.
Sources are linked throughout this guide. Product capabilities can change; consult the linked documentation for your deployment.
Read our editorial approach ↗