Digital Engineering
Idempotency in Distributed Systems — How to Build APIs That Are Safe to Retry
Idempotency in Distributed Systems — How to Build APIs That Are Safe to Retry
Duplicate payments and order processing are critical failures. Learn how to architect idempotent APIs to ensure every request results in exactly one state change, regardless of retries.
Duplicate payments and order processing are critical failures. Learn how to architect idempotent APIs to ensure every request results in exactly one state change, regardless of retries.
08 min read

In the architecture of modern distributed systems, failure is not an anomaly; it is a fundamental expectation. Network timeouts, service crashes, partial deployments, and packet loss are inevitable. When a client sends a request to a server, the "three-way handshake" or the eventual acknowledgment can fail at any stage.
When a client receives a timeout or a 5xx error, the most common corrective action is to retry the request. However, in a distributed system, retrying blindly is dangerous. If the first request actually reached the server and was processed—but the acknowledgment was lost on the return trip—a retry could result in duplicate charges, double-processed orders, or corrupted data.
Idempotency is the property of an operation whereby it can be applied multiple times without changing the result beyond the initial application. Building idempotent APIs is the gold standard for ensuring system reliability and data consistency in the face of partial failures.
1. The Mathematical and Technical Foundation
An operation $f(x)$ is idempotent if:
$$f(f(x)) = f(x)$$
In the context of HTTP and RESTful APIs, this means that making multiple identical requests has the same effect on the server state as making a single request.
HTTP Method Idempotency
Not all HTTP methods are inherently idempotent. Developers must understand these definitions to design compliant APIs:
Method | Idempotent? | Description |
GET | Yes | Retrieves data; does not change server state. |
PUT | Yes | Replaces a resource at a specific URI; multiple puts to the same URI result in the same state. |
DELETE | Yes | Removes a resource; multiple deletions result in the resource being gone. |
POST | No | Typically creates a new resource; multiple posts create multiple resources. |
PATCH | No | Partially updates a resource; depending on implementation, it may change state cumulatively. |
While PUT and DELETE are semantically idempotent, they are only as idempotent as your backend implementation. A DELETE that returns "1 row deleted" is idempotent, but a DELETE that "decrements a counter" is not.
2. The Core Problem: Why Do We Need Idempotency?
Consider the "Lost Acknowledgement" scenario:
Client sends a "Transfer $100" request to the Payment Gateway.
Payment Gateway successfully processes the payment.
Network fails while sending the "Success" response back to the Client.
Client times out and retries the "Transfer $100" request.
Payment Gateway processes the second request.
Result: The customer has been charged $200.
Without an idempotency mechanism, the client is forced to choose between potential data inconsistency and bad user experience.
3. Strategies for Implementing Idempotency
To build a robust system, you must implement a mechanism that allows the server to recognize "seen" requests.
A. Idempotency Keys (The Header Approach)
This is the industry-standard approach used by companies like Stripe and Adyen.
Mechanism: The client generates a unique identifier (a UUID) for the transaction and includes it in the HTTP header (e.g.,
Idempotency-Key: <UUID>).Server Lifecycle:
The server receives the request and checks if the key exists in a cache (e.g., Redis).
If the key exists and a response is stored, the server returns the cached response without re-executing the logic.
If the key does not exist, the server executes the business logic, stores the result, and returns the response.
B. Database Constraints (Unique Indexes)
For create operations, you can rely on the database layer to enforce idempotency. If you have an order_id generated by the client, you can define it as a UNIQUE constraint in your database schema.
Pros: Guaranteed integrity at the storage layer.
Cons: Handling the error requires graceful logic to return a "200 OK" or "204 No Content" instead of a "500 Internal Server Error" when the collision occurs.
C. State Machine Transitions
If your resource has a status (e.g., PENDING, COMPLETED, CANCELLED), you can implement idempotency by validating state transitions.
Example: An order can only transition from
PENDINGtoCOMPLETED. If a retry arrives for an order that is alreadyCOMPLETED, the server simply returns success without running the payment logic again.
4. Architectural Implementation Patterns
Implementing idempotency requires a shift in how you handle requests.
The "Check-Act-Store" Pattern
This is the most common pattern for handling high-throughput idempotent requests.
Check: Does the Idempotency Key exist in the store (Redis/Database)?
Act: If not, acquire a distributed lock on the key, perform the business transaction (e.g., talk to the bank), and store the result.
Store: Save the final HTTP response (status code and body) associated with the key with a reasonable Time-To-Live (TTL).
Managing Distributed Locks
When building high-concurrency systems, two identical requests might arrive at different server nodes simultaneously. If you don't use a lock, both nodes might check the cache, see that the key is missing, and both proceed to process the transaction.
Use Redis Redlock or a database-level advisory lock to ensure that even if two requests arrive at the same millisecond, only one executes the business logic.
5. Implementation Checklist for Engineers
Building safe APIs is not just about the code; it is about the lifecycle of the data.
1. Client Generation: Clients must be responsible for generating the idempotency keys. Use version 4 UUIDs to minimize collision risk.
2. Expiration Policies: Do not store idempotency keys forever. A TTL of 24–48 hours is standard. After that, assume the client has either succeeded or abandoned the request.
3. Response Replay: Ensure the stored response is exactly what was returned the first time. If you returned a
201 Createdwith a specific resource ID, the retried request must return the exact same data.4. Error Handling: If a client tries to reuse an idempotency key for a different request (a mismatch), the server must return a
400 Bad Requestor422 Unprocessable Entity. Do not reuse keys for different operations.5. Transactional Integrity: The storage of the business result and the storage of the idempotency key must happen within the same atomic transaction. If you save the payment but fail to save the idempotency key, you have lost your record of completion.
6. Challenges and Trade-offs
The Performance Overhead
Adding a check to a Redis cache for every request adds latency (typically 1–5ms). While negligible for most systems, it can become a bottleneck in massive-scale financial systems.
Partial Success (The "Zombie" State)
What happens if your business logic involves multiple microservices?
Service A calls Service B (Payment).
Service A calls Service C (Inventory).
If Service B succeeds but Service C fails, the retry will trigger the idempotency check. Service B will return the "success" result from its cache, but Service C will attempt to update inventory again. To solve this, you need distributed transactions (e.g., Saga Pattern) or event-driven eventual consistency.
7. Comparison of Idempotency Approaches
Approach | Implementation Complexity | Data Integrity | Best For |
Idempotency Headers | Medium | High | Public APIs, Payment Gateways |
Database Constraints | Low | Very High | Simple resource creation |
State Machine | High | Very High | Order management, Workflows |
Client-Side Deduplication | Medium | Moderate | Mobile/Offline-first apps |
8. Real-World Case Study: Financial Services
Most major payment processors require an Idempotency-Key. If you look at the Stripe API, they explicitly warn:
"If you use the same key for two different requests, we will return the response from the first request."
This forces developers to treat the key as a "unit of work" rather than a "request identifier." In a distributed payment system, the flow is:
Request Initiation: The server receives the request.
Locking: Use a distributed lock based on the
Idempotency-Key(e.g.,lock:order_123).Persistence: Within a single database transaction, write the order to the
orderstable and write the response to theidempotency_responsestable.Release: Release the lock and return the response.
If the server crashes during step 3, the database transaction rolls back, and the client receives no response. The client retries, the lock is acquired, and the transaction starts from scratch. If the server crashes after step 3, the response is in the database; upon retry, the server detects the existing record and serves it back without re-processing.
9. Handling "In-Flight" Requests
One of the most complex edge cases is the "In-Flight" request. This occurs when a request is still processing, but the client sends the same request again.
Option A: Fail Fast. Return a
409 Conflict(or429 Too Many Requests) indicating that a request with this key is already being processed. This is highly recommended as it prevents unnecessary server load.Option B: Wait (Polling/Blocking). Have the server hold the request open until the first process finishes. This is technically difficult to manage at scale due to connection limits and timeout overheads.
For most distributed systems, Option A is the preferred path. It forces the client to handle the "wait" logic, which is cleaner and more scalable.
10. Summary and Future Trends
As we move toward more granular microservices, the necessity of idempotency grows. Systems are becoming more asynchronous, utilizing event buses like Kafka or RabbitMQ.
In an event-driven architecture, idempotency is handled by the consumer. When a service consumes a message from a queue, it must check if the message_id has already been processed before executing its task. This "exactly-once delivery" requirement is effectively "at-least-once delivery plus idempotency."
Final Considerations
Observability: Always log when an idempotency key collision occurs. If you see a high volume of key collisions, it indicates a bug in your client-side retry logic.
Security: Idempotency keys should be treated with the same sensitivity as authentication tokens. Ensure they are not exposed in logs or insecure storage.
Simplicity: Do not over-engineer. If your API is strictly read-only, you do not need idempotency. Apply it only to state-changing operations where the cost of a duplicate is high.
Building for idempotency is a sign of a mature engineering team. It demonstrates an understanding that infrastructure is fallible and that the responsibility for data integrity must be shared between the client and the server. By implementing these strategies, you move your system away from "hope-based engineering" and toward a robust, self-healing architecture that handles the reality of the network with confidence.
Implementation Workflow Visualization
Client sends
Request + Idempotency-Key.API Gateway routes request to Service.
Service checks Global Cache.
Flow Split: * If Found: Return Result.
If Not Found: Execute logic -> Save Result -> Return.
By adhering to these patterns, you ensure that even under heavy network turbulence, your system maintains a "single version of the truth." Your APIs become not just functional, but reliable, predictable, and—most importantly—resilient.
Recommended Best Practices Table
Category | Recommendation |
Communication | Always document expected behavior for retries in API specs (OpenAPI/Swagger). |
Design | Ensure idempotency logic is in the application layer, not the infrastructure. |
Testing | Write integration tests that simulate "timeout then retry" scenarios. |
Monitoring | Set up alerts for unexpected idempotency key collisions (indicates logic errors). |
Security | Rate limit requests by |
The transition from a naive API to an idempotent one is the most significant step an engineer can take to improve the "Mean Time To Recovery" (MTTR) of their services. By ensuring that every request is safe to repeat, you remove the fear of network volatility and empower your users to interact with your system with the assurance that their commands—and only their commands—will be executed exactly as intended.
In the architecture of modern distributed systems, failure is not an anomaly; it is a fundamental expectation. Network timeouts, service crashes, partial deployments, and packet loss are inevitable. When a client sends a request to a server, the "three-way handshake" or the eventual acknowledgment can fail at any stage.
When a client receives a timeout or a 5xx error, the most common corrective action is to retry the request. However, in a distributed system, retrying blindly is dangerous. If the first request actually reached the server and was processed—but the acknowledgment was lost on the return trip—a retry could result in duplicate charges, double-processed orders, or corrupted data.
Idempotency is the property of an operation whereby it can be applied multiple times without changing the result beyond the initial application. Building idempotent APIs is the gold standard for ensuring system reliability and data consistency in the face of partial failures.
1. The Mathematical and Technical Foundation
An operation $f(x)$ is idempotent if:
$$f(f(x)) = f(x)$$
In the context of HTTP and RESTful APIs, this means that making multiple identical requests has the same effect on the server state as making a single request.
HTTP Method Idempotency
Not all HTTP methods are inherently idempotent. Developers must understand these definitions to design compliant APIs:
Method | Idempotent? | Description |
GET | Yes | Retrieves data; does not change server state. |
PUT | Yes | Replaces a resource at a specific URI; multiple puts to the same URI result in the same state. |
DELETE | Yes | Removes a resource; multiple deletions result in the resource being gone. |
POST | No | Typically creates a new resource; multiple posts create multiple resources. |
PATCH | No | Partially updates a resource; depending on implementation, it may change state cumulatively. |
While PUT and DELETE are semantically idempotent, they are only as idempotent as your backend implementation. A DELETE that returns "1 row deleted" is idempotent, but a DELETE that "decrements a counter" is not.
2. The Core Problem: Why Do We Need Idempotency?
Consider the "Lost Acknowledgement" scenario:
Client sends a "Transfer $100" request to the Payment Gateway.
Payment Gateway successfully processes the payment.
Network fails while sending the "Success" response back to the Client.
Client times out and retries the "Transfer $100" request.
Payment Gateway processes the second request.
Result: The customer has been charged $200.
Without an idempotency mechanism, the client is forced to choose between potential data inconsistency and bad user experience.
3. Strategies for Implementing Idempotency
To build a robust system, you must implement a mechanism that allows the server to recognize "seen" requests.
A. Idempotency Keys (The Header Approach)
This is the industry-standard approach used by companies like Stripe and Adyen.
Mechanism: The client generates a unique identifier (a UUID) for the transaction and includes it in the HTTP header (e.g.,
Idempotency-Key: <UUID>).Server Lifecycle:
The server receives the request and checks if the key exists in a cache (e.g., Redis).
If the key exists and a response is stored, the server returns the cached response without re-executing the logic.
If the key does not exist, the server executes the business logic, stores the result, and returns the response.
B. Database Constraints (Unique Indexes)
For create operations, you can rely on the database layer to enforce idempotency. If you have an order_id generated by the client, you can define it as a UNIQUE constraint in your database schema.
Pros: Guaranteed integrity at the storage layer.
Cons: Handling the error requires graceful logic to return a "200 OK" or "204 No Content" instead of a "500 Internal Server Error" when the collision occurs.
C. State Machine Transitions
If your resource has a status (e.g., PENDING, COMPLETED, CANCELLED), you can implement idempotency by validating state transitions.
Example: An order can only transition from
PENDINGtoCOMPLETED. If a retry arrives for an order that is alreadyCOMPLETED, the server simply returns success without running the payment logic again.
4. Architectural Implementation Patterns
Implementing idempotency requires a shift in how you handle requests.
The "Check-Act-Store" Pattern
This is the most common pattern for handling high-throughput idempotent requests.
Check: Does the Idempotency Key exist in the store (Redis/Database)?
Act: If not, acquire a distributed lock on the key, perform the business transaction (e.g., talk to the bank), and store the result.
Store: Save the final HTTP response (status code and body) associated with the key with a reasonable Time-To-Live (TTL).
Managing Distributed Locks
When building high-concurrency systems, two identical requests might arrive at different server nodes simultaneously. If you don't use a lock, both nodes might check the cache, see that the key is missing, and both proceed to process the transaction.
Use Redis Redlock or a database-level advisory lock to ensure that even if two requests arrive at the same millisecond, only one executes the business logic.
5. Implementation Checklist for Engineers
Building safe APIs is not just about the code; it is about the lifecycle of the data.
1. Client Generation: Clients must be responsible for generating the idempotency keys. Use version 4 UUIDs to minimize collision risk.
2. Expiration Policies: Do not store idempotency keys forever. A TTL of 24–48 hours is standard. After that, assume the client has either succeeded or abandoned the request.
3. Response Replay: Ensure the stored response is exactly what was returned the first time. If you returned a
201 Createdwith a specific resource ID, the retried request must return the exact same data.4. Error Handling: If a client tries to reuse an idempotency key for a different request (a mismatch), the server must return a
400 Bad Requestor422 Unprocessable Entity. Do not reuse keys for different operations.5. Transactional Integrity: The storage of the business result and the storage of the idempotency key must happen within the same atomic transaction. If you save the payment but fail to save the idempotency key, you have lost your record of completion.
6. Challenges and Trade-offs
The Performance Overhead
Adding a check to a Redis cache for every request adds latency (typically 1–5ms). While negligible for most systems, it can become a bottleneck in massive-scale financial systems.
Partial Success (The "Zombie" State)
What happens if your business logic involves multiple microservices?
Service A calls Service B (Payment).
Service A calls Service C (Inventory).
If Service B succeeds but Service C fails, the retry will trigger the idempotency check. Service B will return the "success" result from its cache, but Service C will attempt to update inventory again. To solve this, you need distributed transactions (e.g., Saga Pattern) or event-driven eventual consistency.
7. Comparison of Idempotency Approaches
Approach | Implementation Complexity | Data Integrity | Best For |
Idempotency Headers | Medium | High | Public APIs, Payment Gateways |
Database Constraints | Low | Very High | Simple resource creation |
State Machine | High | Very High | Order management, Workflows |
Client-Side Deduplication | Medium | Moderate | Mobile/Offline-first apps |
8. Real-World Case Study: Financial Services
Most major payment processors require an Idempotency-Key. If you look at the Stripe API, they explicitly warn:
"If you use the same key for two different requests, we will return the response from the first request."
This forces developers to treat the key as a "unit of work" rather than a "request identifier." In a distributed payment system, the flow is:
Request Initiation: The server receives the request.
Locking: Use a distributed lock based on the
Idempotency-Key(e.g.,lock:order_123).Persistence: Within a single database transaction, write the order to the
orderstable and write the response to theidempotency_responsestable.Release: Release the lock and return the response.
If the server crashes during step 3, the database transaction rolls back, and the client receives no response. The client retries, the lock is acquired, and the transaction starts from scratch. If the server crashes after step 3, the response is in the database; upon retry, the server detects the existing record and serves it back without re-processing.
9. Handling "In-Flight" Requests
One of the most complex edge cases is the "In-Flight" request. This occurs when a request is still processing, but the client sends the same request again.
Option A: Fail Fast. Return a
409 Conflict(or429 Too Many Requests) indicating that a request with this key is already being processed. This is highly recommended as it prevents unnecessary server load.Option B: Wait (Polling/Blocking). Have the server hold the request open until the first process finishes. This is technically difficult to manage at scale due to connection limits and timeout overheads.
For most distributed systems, Option A is the preferred path. It forces the client to handle the "wait" logic, which is cleaner and more scalable.
10. Summary and Future Trends
As we move toward more granular microservices, the necessity of idempotency grows. Systems are becoming more asynchronous, utilizing event buses like Kafka or RabbitMQ.
In an event-driven architecture, idempotency is handled by the consumer. When a service consumes a message from a queue, it must check if the message_id has already been processed before executing its task. This "exactly-once delivery" requirement is effectively "at-least-once delivery plus idempotency."
Final Considerations
Observability: Always log when an idempotency key collision occurs. If you see a high volume of key collisions, it indicates a bug in your client-side retry logic.
Security: Idempotency keys should be treated with the same sensitivity as authentication tokens. Ensure they are not exposed in logs or insecure storage.
Simplicity: Do not over-engineer. If your API is strictly read-only, you do not need idempotency. Apply it only to state-changing operations where the cost of a duplicate is high.
Building for idempotency is a sign of a mature engineering team. It demonstrates an understanding that infrastructure is fallible and that the responsibility for data integrity must be shared between the client and the server. By implementing these strategies, you move your system away from "hope-based engineering" and toward a robust, self-healing architecture that handles the reality of the network with confidence.
Implementation Workflow Visualization
Client sends
Request + Idempotency-Key.API Gateway routes request to Service.
Service checks Global Cache.
Flow Split: * If Found: Return Result.
If Not Found: Execute logic -> Save Result -> Return.
By adhering to these patterns, you ensure that even under heavy network turbulence, your system maintains a "single version of the truth." Your APIs become not just functional, but reliable, predictable, and—most importantly—resilient.
Recommended Best Practices Table
Category | Recommendation |
Communication | Always document expected behavior for retries in API specs (OpenAPI/Swagger). |
Design | Ensure idempotency logic is in the application layer, not the infrastructure. |
Testing | Write integration tests that simulate "timeout then retry" scenarios. |
Monitoring | Set up alerts for unexpected idempotency key collisions (indicates logic errors). |
Security | Rate limit requests by |
The transition from a naive API to an idempotent one is the most significant step an engineer can take to improve the "Mean Time To Recovery" (MTTR) of their services. By ensuring that every request is safe to repeat, you remove the fear of network volatility and empower your users to interact with your system with the assurance that their commands—and only their commands—will be executed exactly as intended.
FAQs
insights
Explore more on AI, Design and Growth
AI and Data Analytics
Data Lakehouse Architecture for Indian Companies: When to Move Beyond a Pure Data Warehouse
Your data warehouse handles SQL transformations smoothly until your product team starts feeding image and text streams into production and query costs triple overnight

AI and Data Analytics
Shopify Attribution Models: First Click vs Last Click vs Data-Driven
Compare Shopify attribution models with practical guidance on first click, last click and data-driven measurement for clearer marketing decisions.

AI and Data Analytics
Shopify Analytics for Beginners: 5 Reports to Review Every Week
Learn which five Shopify reports to review each week, with practical guidance on reading store data, spotting priorities and making clearer decisions.
AI and Data Analytics
Data Lakehouse Architecture for Indian Companies: When to Move Beyond a Pure Data Warehouse
Your data warehouse handles SQL transformations smoothly until your product team starts feeding image and text streams into production and query costs triple overnight

AI and Data Analytics
Shopify Attribution Models: First Click vs Last Click vs Data-Driven
Compare Shopify attribution models with practical guidance on first click, last click and data-driven measurement for clearer marketing decisions.
get in touch
Ready to Grow From Day One?
Strategy, execution, and digital experiences designed to move together. Fill out the form below and our team will contact you shortly.
get in touch
Ready to Grow From Day One?
Strategy, execution, and digital experiences designed to move together. Fill out the form below and our team will contact you shortly.
get in touch
Ready to Grow From Day One?
Strategy, execution, and digital experiences designed to move together. Fill out the form below and our team will contact you shortly.
Services
We'd love to hear from you.
Tell us what you're building and where you need support.
© 2026 projectsupply AI, Data and Digital Engineering
Company. Pune, India. All rights reserved.
Part of Tangle
Services
We'd love to hear from you.
Tell us what you're building and where you need support.
© 2026 projectsupply AI, Data and Digital Engineering
Company. Pune, India. All rights reserved.
Part of Tangle
Services
We'd love to hear from you.
Tell us what you're building and where you need support.
© 2026 projectsupply AI, Data and Digital Engineering
Company. Pune, India. All rights reserved.
Part of Tangle
