The Double-Charge Problem
You tap "Pay ₹999." The spinner keeps going. The network drops, so the app retries. Did the first request reach the server? If it did, you've now paid twice.
Networks fail in the worst way: a request can succeed on the server while the response gets lost. The client can't tell "it failed" from "it worked but I didn't hear back." The fix for this is idempotency.
🎯 Quick Answer (30-Second Read)
- Idempotent = doing something once or many times has the same effect.
- Safe retries need idempotency: without it, retries can create duplicate orders, payments or emails.
- HTTP: GET, PUT and DELETE are defined as idempotent. POST isn't, so "create" requests need extra help.
- Idempotency keys: the client sends a unique key (e.g. a UUID) with the request. The server saves the result for that key and returns the same result for any retry.
- Stripe uses an
Idempotency-Keyheader and saves the first response for each key.
Idempotent vs Not
"Set my address to Pune." Do it 5 times, the address is still Pune.
Pressing a lift button again.
"Add ₹100 to my wallet." Do it 5 times, you get ₹500.
"Create an order." Retry = second order.
HTTP Methods
| Method | Idempotent? | Why |
|---|---|---|
| GET | ✅ | Only reads |
| PUT | ✅ | Replaces a resource with the same value each time |
| DELETE | ✅ | Deleting twice leaves it deleted (the second call may return 404, but the state is the same) |
| POST | ❌ | Usually creates something new each time |
| PATCH | Not guaranteed | Depends on the operation ("set" vs "increment") |
Idempotent describes the effect on the server, not the response. A second DELETE can return a different status code and still be idempotent.
How Idempotency Keys Work
- The client generates a unique key per operation, such as a UUID, and reuses it for every retry of that operation.
- The server checks whether the key exists.
- New key: process the request, then store the key with the response.
- Seen key: skip the work and return the stored response.
How Stripe Does It
Stripe's API is the best-known example:
- Send
Idempotency-Key: <unique value>on POST requests. - Stripe saves the status code and body of the first request made with a key and returns them for retries, even if the first result was a 500 error.
- Keys can be up to 255 characters. Stripe suggests V4 UUIDs and warns against using sensitive data like email addresses as keys.
- If a retry uses the same key with different parameters, Stripe returns an error to prevent mistakes.
- Keys can be removed after they're at least 24 hours old. Reusing an old key after that creates a new request.
curl https://api.stripe.com/v1/customers \
-u sk_test_...: \
-H "Idempotency-Key: 9b2f1c4e-6a1d-4c3b-9f5e-2d8a7b6c1e0f" \
-d description="New customer"Building It Yourself
create table idempotency_keys (
key text primary key,
user_id bigint not null,
request_hash text not null, -- detect same key, different body
status_code int,
response jsonb,
created_at timestamptz default now()
);Design details that matter:
- Insert the key first, atomically (a unique constraint), so two simultaneous retries can't both process.
- Do the business write and the key update in the same transaction where possible.
- Scope keys per user so one customer's key can't collide with another's.
- Hash the request body and reject reuse with different parameters.
- Expire old keys (for example after 24 hours) to keep the table small.
Idempotency Beyond APIs
- Message queues: consumers may receive a message twice, so they track processed message IDs. (See Message Queue Explained.)
- Payments: UPI gives each payment a unique transaction ID so retries can't debit twice. (See How Does UPI Work?.)
- Database writes:
INSERT ... ON CONFLICT DO NOTHINGmakes inserts safe to repeat. - Infrastructure: tools like Terraform aim for idempotent runs, applying the same config twice changes nothing.
Frequently Asked Questions
What does idempotent mean in a REST API?
An idempotent API operation produces the same effect on the server whether it's called once or many times. GET, PUT and DELETE are idempotent by definition in HTTP. POST is not, so APIs often add idempotency keys to make create operations safe to retry.
How long should idempotency keys be stored?
Long enough to cover any realistic retry window. Stripe allows keys to be removed once they are at least 24 hours old, which is a common reference point. Payment systems with longer retry windows may keep keys longer.
Who generates the idempotency key, client or server?
The client generates it, because only the client knows that two requests are retries of the same operation. A random UUID per operation works well. The client must reuse the same key for every retry of that operation and use a new key for a genuinely new one.
Conclusion
Idempotency makes retries safe. Some operations are naturally idempotent, like setting a value or deleting a record. For the rest, especially creating payments and orders, idempotency keys let the server recognise a retry and return the original result instead of doing the work twice.
Sources
- Stripe API reference: Idempotent requests
- RFC 9110: HTTP Semantics, idempotent methods
- IETF draft: The Idempotency-Key HTTP Header Field
Last verified: 14 October 2026.
Related reads: Message Queue Explained · How Does UPI Work? · Rate Limiter Explained · Circuit Breaker Pattern Explained
I write about system design and how real products are built. Follow on LinkedIn or X, or invite me to speak.