Idempotency & Preventing Double Payments
Make retries safe, so a timeout never charges a customer twice.
Networks fail in ambiguous ways. A client sends “charge $50”, the connection drops, and it can’t tell whether the charge happened. If it retries blindly, the customer may be charged twice.
An operation is idempotent if doing it several times has the same effect as doing it once. Payment providers make non-idempotent operations safe with an idempotency key.
Diagram
How it works
- The client generates a unique key (a UUID) per logical operation and sends it in an
Idempotency-Keyheader. - The server tries to insert the key into a store with a unique constraint, marked “in progress”.
- If the insert succeeds, it performs the operation and saves the response against the key.
- If the key already exists and is complete, it returns the saved response without doing the work again.
- If it exists and is still in progress, it returns
409 Conflictso the client waits and retries. - Keys expire after a retention period, such as 24 hours.
Key concepts
- Naturally idempotent
GET,PUT(replace) andDELETEshould be idempotent by design.POSTusually is not.- Idempotency key
- Client-generated ID for one logical operation, reused on every retry of that operation.
- At-least-once delivery
- Message brokers may deliver twice, so consumers must also be idempotent.
- Exponential backoff + jitter
- Retries wait longer each time, with randomness, so clients don’t retry in lockstep.
Trade-offs
Benefits
- Retries become safe for clients and message consumers
- No duplicate charges, orders or emails
- Simplifies failure handling everywhere
Costs
- Needs a durable key store with a unique constraint
- Must decide how long keys live and what counts as “same request”
Use it when
- Payments, transfers, order placement, any operation with money or side effects
- Message consumers in at-least-once systems
Common mistakes
- Generating a new key on each retry, which defeats the purpose
- Checking “does the key exist?” and inserting it in two steps (race condition) — use an atomic insert
- Reusing a key with a different request body — reject it
Interview questions
Q1Why isn’t a database transaction enough to prevent double charges?
The duplicate is a second, separate request. Each transaction succeeds on its own. You need a key that links the two requests as the same operation.
Q2Is DELETE /orders/42 idempotent?
Yes in effect: after the first call the order is gone, and later calls leave it gone. The status code may differ (204 then 404), but the state is the same.
Q3How do you make a message consumer idempotent?
Record processed message IDs in the same transaction as the business change, and skip messages whose ID is already recorded.
Related lessons
APIs & Security
HTTP Status Codes for APIs
Return the right status code so clients, proxies and monitoring understand what happened.
Messaging & MicroservicesThe Saga Pattern
Run a business transaction across services as a series of local steps with compensations.
Messaging & MicroservicesMessage Brokers: RabbitMQ vs Kafka vs ActiveMQ
Queues and logs for decoupling services — and how the three popular brokers differ.