API design
Idempotency Keys: Prevent Duplicate API Side Effects
Idempotency keys make retries safe when customers cannot tell whether a write succeeded. Design de-duplication, replay outcomes, and safe side effects.
TL;DR — An idempotency key or equivalent durable de-duplication lets a client safely retry a write after it cannot tell whether the first attempt succeeded. The server records the request identity and outcome, then returns the original result for the same request instead of performing the side effect twice. It is appropriate for actions such as creating an order, sending a message, or charging an account.
Networks fail in inconvenient ways. A server can complete a write just as a connection drops; a mobile app can time out after the work has committed; a customer can double-click. Without a replay-safe contract, the client has two bad choices: retry and risk duplication, or stop and leave the user unsure whether anything happened.
HTTP method names are not enough. A POST usually represents a new action and is not inherently idempotent. RFC 9110 defines idempotent method semantics, but an application-level idempotency key is what lets a POST safely represent “do this once, even if I repeat the request.” Stripe’s idempotent request documentation is a practical example of returning the first result to later retries.
Define exactly what “same request” means
A strong key is generated by the client for one intended action. Store it alongside the authenticated account or tenant, endpoint, a digest of relevant request parameters, status, and response. The key should not be globally reusable across customers or actions. If the same key arrives with different parameters, reject it clearly rather than silently replaying an unrelated result.
The retention period depends on the longest realistic retry and support window for your product. Do not choose a duration from a blog post; document the reason and communicate the limit to API users. A key that expires before a delayed retry loses its purpose, while retaining every key forever creates unnecessary data and privacy obligations.
Make concurrent retries safe
The hard case is two identical requests arriving together. Both may check for a key before either writes the record. Prevent that race with a unique database constraint or an atomic claim operation, then have the losing request wait for or read the completed result. A memory-only map works in a demo but fails as soon as traffic reaches another process or a restart.
Imagine, hypothetically, a customer submits an invoice payment and their phone loses service. The client repeats the same key. The first request reserves the key and starts work. The second finds it in progress. It should not start another payment; it can return a defined in-progress response or wait briefly for the stored response. Once complete, every matching retry receives the original outcome.
Pair this with API rate limiting, because a network problem may cause retries to arrive in bursts. Pair it with database connection pooling, because a queue of waiting duplicate requests should not consume every connection.
Do not confuse idempotency with transactions
An idempotency key protects one business intent across retries. A database transaction makes a local group of database changes atomic. A payment processor or email provider may still need its own idempotency mechanism. When an action spans systems, use an outbox, durable state machine, or provider-supported key so a crash between steps has a recoverable record.
The founder decision is to define the customer-visible result for partial completion. If an order is saved but notification delivery is pending, report that honestly and retry the notification safely. Do not hide ambiguity behind a generic success response.
Failure modes to test
- The key is stored after the side effect, leaving a duplicate window.
- Different payloads reuse a key and receive the wrong response.
- A restart loses in-progress keys.
- The stored response is missing an error outcome, so retries behave differently after a failure.
- A key applies to the whole account rather than one endpoint and blocks unrelated actions.
API versioning should keep key behavior compatible across supported versions. A client cannot safely retry if an upgrade changes the meaning of the replayed response without notice.
Acceptance checklist
- The API documents where to send the key and which actions require it.
- Key scope includes tenant, endpoint, and a validated request identity.
- An atomic persistence rule prevents concurrent duplicate work.
- Matching retries replay the original success or defined failure outcome.
- Tests cover timeout-after-commit, simultaneous retries, restart recovery, and payload mismatch.
An idempotency key is a small API field with a large promise: a customer can try again without being punished for a network failure. Keep that promise at the boundary where the side effect begins.