Idempotency Keys Beyond Payments
Where else exactly-once thinking saves you from duplicate side effects.
The Problem
Retries happen in every real system: mobile networks fail, load balancers time out, queues redeliver messages, and users double-click buttons. Without idempotency, every retry can create duplicate side effects.
Why It Matters
Distributed systems cannot reliably know whether a timed-out request failed before or after the server performed the work. Idempotency gives clients a safe retry contract: "this key represents one logical operation."
Stripe publicly documents idempotency keys for API writes because payment APIs must survive client retries without double charging. The same idea belongs in many backend workflows, not only payment gateways.
Project Example
Payments are the obvious case, but the same pattern applies to order creation,
invoice generation, account provisioning, email campaigns, webhook processing, and
background jobs. A POST /orders request should not create two orders because the
client retried after a timeout.
Implementation Example
CREATE TABLE idempotency_keys (
user_id bigint NOT NULL,
key text NOT NULL,
request_hash text NOT NULL,
status text NOT NULL,
response jsonb,
created_at timestamptz NOT NULL DEFAULT now(),
PRIMARY KEY (user_id, key)
);
Store a hash of the request body. If the same key is reused with a different body,
return 409 Conflict; otherwise return the saved response for completed requests.
Implementation Checklist
- Require a client-generated idempotency key for unsafe writes.
- Store the key before doing the side effect.
- Use a unique constraint on
(user_id, idempotency_key). - Return the previous response when the same key is reused.
- Expire keys after a business-appropriate window.
- Save request hash and final response.
- Use the same transactional datastore as the business write when possible.
- Treat in-progress duplicate requests as
409or wait with a timeout.
Production Notes
For queue consumers, the message id or business event id can become the idempotency key. For webhooks, use the provider event id. For APIs, require the client to generate a UUID before the first attempt.
Common Mistakes
- Generating the key on the server after the first request arrives.
- Storing the key only in Redis when the write is in Postgres.
- Not handling two concurrent requests with the same key.
- Treating idempotency as "exactly once" instead of "same result for same request."
- Allowing the same key to run different request bodies.
Summary
Idempotency keys let clients and workers retry safely. Use them anywhere duplicate side effects would create support tickets, bad data, or customer-visible damage.
The weekly engineering digest
Production-grade engineering writing in your inbox. No spam, unsubscribe anytime.