+91 98726 60544 hello@mitstech.co Mon–Sat · 09:00–18:30 IST

Idempotency keys: making retries safe

Cloud By Mits Engineering Team 3 min read
Idempotency keys: making retries safe

Clients retry. Mobile applications on unreliable connections retry, gateways retry, load balancers retry on timeout, and users press the button again when nothing appears to happen. An idempotency key is how an API makes that safe, and designing for it is not optional politeness - the retries arrive whether or not you planned for them, and the second request is the one that charges the card twice.

The core idea is small. The client generates a unique key for an operation it intends to perform once and sends it with the request. The server records that key alongside the outcome. If the same key arrives again, the server does not repeat the work; it returns the original response. What makes this work is that the key belongs to the client's intent rather than to the network attempt - a retry of the same logical operation reuses the key, while genuinely new work gets a new one.

Returning the stored response matters as much as skipping the work. An implementation that detects a duplicate and returns a bare conflict error has protected the database and left the client unable to discover what happened. The caller cannot distinguish 'your first attempt succeeded' from 'this key belongs to something else', so it cannot decide whether to tell the user the operation worked. Store the status and body of the original response and replay it.

Scope keys carefully. A key should be unique per operation and, in a multi-tenant system, namespaced by the account it belongs to, so one customer cannot collide with or probe another's. Keys should also be bound to the request that first carried them: if the same key arrives with a different payload, that is a client bug, and silently returning the original result hides it. Comparing a hash of the request and failing loudly on mismatch is the correct behaviour.

There is a genuine race to handle. Two copies of the same request can arrive at once, both find no existing key, and both proceed. The reliable fix is to insert the key first, in the same transaction that performs the work, with a uniqueness constraint doing the arbitration; the loser of that insert waits for and returns the winner's result. Checking for existence and then acting is the version that passes tests and fails under real concurrency.

Keys need a lifetime, and the choice is a trade. Retaining them long enough to cover any realistic retry window costs storage, while expiring them early risks a late retry being treated as new work. Deciding this deliberately, and documenting the window for the clients that depend on it, beats the default of keeping everything forever and later discovering the table is among your largest.

Not every endpoint needs this. Reads are naturally repeatable, and operations setting a value to a specific state are already safe to repeat. The work belongs on operations that create or move something and cannot be cheaply undone - payments, orders, outbound messages, provisioning. Applying idempotency uniformly across an entire API spends effort where the risk is not, and it dilutes the attention the genuinely risky endpoints deserve.

Need help with this? Explore our Cloud Solutions & Migration services. Learn more Back to all news

Keep reading

More on Cloud