Idempotency Implementation Guide — Key Generator & Dedup Simulator

May 25, 2026 · 15 min read · By Michael Lip

Idempotency is the property that ensures an operation produces the same result regardless of how many times it is executed. In distributed systems, network failures, client retries, and webhook redeliveries mean that the same request can arrive multiple times. Without idempotency guarantees, a payment API that processes the same charge request twice will charge the customer twice. A webhook handler that processes the same event twice will create duplicate records. The Idempotency Key Generator gives you tools to generate keys in multiple formats, simulate deduplication logic, and generate production-ready middleware code.

Choose from UUID v4 (random, universally unique), hash-based (deterministic from request content), or timestamp-based (ordered, collision-resistant) key formats. Use the dedup simulator to send requests with duplicate keys and watch how idempotent processing works in real time. Then generate middleware code for Express.js, FastAPI, or Spring Boot that you can drop directly into your project.

Idempotency Key Generator
Click "Generate New" to create a key.
No keys generated yet.
Deduplication Simulator

Simulate sending API requests with idempotency keys. Duplicate keys return the cached response instead of re-processing.

Ready. Send a request to begin simulation.
Code Generator
Strategy Comparison
Strategy Format Deterministic Sortable Best For Risk
UUID v4 550e8400-e29b-41d4-a716-446655440000 No No General API idempotency Client must store key for retries
Hash-Based sha256(method+path+body) Yes No Content deduplication Identical payloads always dedup
Timestamp + Random 1716648000000-a3f9b2 No Yes Time-ordered request logs Clock skew in distributed systems
ULID 01H5KBWZ7QR... (26 chars) No Yes Database-friendly sorted IDs Larger than UUID, less tooling
Composite clientId:timestamp:nonce Partial Yes Multi-tenant systems Requires client identity

Why Idempotency Is Non-Negotiable in Distributed Systems

Every network request can fail in one of three observable ways: the client receives a success response, the client receives an error response, or the client receives no response at all. The third case — the timeout — is the fundamental challenge that makes idempotency necessary. When a payment request times out, the client does not know whether the server processed it. The payment may have succeeded (the server charged the card but the response was lost in transit), or it may have failed (the server never received the request). The only safe action is to retry with the same idempotency key, which guarantees that the server either processes the request for the first time or returns the cached result of the previous execution.

This pattern is not optional for financial operations, order processing, account creation, or any state-changing operation where duplicates have real-world consequences. Stripe requires idempotency keys for all POST requests. AWS requires client tokens for many write operations. Google Cloud APIs support request deduplication via client-specified request IDs. The pattern is universal because the underlying problem — unreliable networks — is universal.

Idempotency Key Generation Strategies

UUID v4 is the most common format. The client generates a random 128-bit identifier (formatted as 32 hexadecimal characters with hyphens) before each request and includes it in the Idempotency-Key header. If the request fails and needs to be retried, the client reuses the same UUID. If the request succeeds and the client wants to make a new request with the same parameters (like placing a second identical order), it generates a new UUID. The critical requirement is that the client stores the UUID between the initial attempt and any retries.

Hash-based keys are deterministically derived from the request content. The client computes SHA-256 of the HTTP method, request path, and request body, then uses the hash as the idempotency key. Identical requests automatically produce the same key without any client-side storage. This is ideal for true deduplication where the same payload should never be processed twice. However, it prevents intentional duplicate operations — if a customer wants to place two identical orders, hash-based keys would block the second order.

Timestamp-based keys combine a Unix timestamp with a random suffix (like 1716648000000-a3f9b2). These are naturally sortable, which is useful for debugging and log analysis. The timestamp provides approximate ordering, and the random suffix prevents collisions for requests made within the same millisecond. Timestamp-based keys work well for systems that need ordered request IDs but do not need deterministic deduplication.

ULID (Universally Unique Lexicographically Sortable Identifier) combines a 48-bit timestamp with 80 bits of randomness, encoded as a 26-character Crockford Base32 string. ULIDs are monotonically sortable (later IDs sort after earlier ones), which makes them excellent primary keys in databases where UUID v4 causes index fragmentation. ULIDs are gaining adoption as a modern alternative to UUID v4 for systems where sort order matters.

Server-Side Idempotency Implementation

The server-side implementation follows a consistent pattern regardless of the framework. First, the middleware extracts the idempotency key from the request header. Second, it performs an atomic check-and-set operation: if the key exists in the store, return the cached response; if not, mark the key as “in progress” and proceed with processing. Third, after the business logic completes, store the response alongside the key with a TTL. Fourth, return the response to the client.

The “in progress” state is critical for handling concurrent duplicate requests. Without it, two identical requests arriving simultaneously could both pass the “key not found” check and both execute the business logic. The atomic set-if-not-exists operation (Redis SETNX, database INSERT ... ON CONFLICT DO NOTHING, or Java ConcurrentHashMap.putIfAbsent) ensures that only one request proceeds while the other waits or receives an immediate “request in progress” response.

Storage Backend Considerations

Redis is the most popular backend for idempotency key storage. It provides atomic SET NX EX (set if not exists with expiry) in a single command, sub-millisecond latency, and automatic key expiration via TTL. The trade-off is that Redis is an additional infrastructure dependency, and keys are lost if Redis restarts without persistence enabled. For production systems, use Redis with AOF persistence or Redis Cluster for durability.

In-memory storage (a hash map in the application process) works for single-instance deployments and development environments. It has zero latency and no external dependencies, but keys are lost on application restart and are not shared across multiple application instances. Never use in-memory storage for production systems behind a load balancer, as different instances will have different key sets.

Database storage uses a dedicated table with columns for the idempotency key (primary key), the cached response, the creation timestamp, and a status field. This provides durability and works across all application instances that share the database. The trade-off is higher latency (a database query per request) and the need for a cleanup job to delete expired keys. For teams managing high-throughput webhook processing, database storage adds measurable latency but provides the strongest durability guarantees.

Idempotency in Webhook Handlers

Webhook handlers are the most critical application of idempotency because the caller (the webhook provider) controls the delivery semantics. Stripe, GitHub, and Slack all use at-least-once delivery, meaning they will retry failed deliveries for hours or days. A webhook handler must be idempotent by design — the event ID provided by the webhook sender serves as the natural idempotency key.

The implementation for webhooks differs slightly from API idempotency. Instead of caching the response, the handler checks whether the event ID has been processed. If it has, the handler returns a 200 OK immediately (to acknowledge the delivery and stop retries) without executing any business logic. If it has not, the handler processes the event, records the event ID as processed, and returns 200 OK. The event ID store should have a TTL of at least 7 days, as some providers retry for extended periods.

Edge Cases and Common Mistakes

Missing idempotency key: Decide whether to reject requests without an idempotency key (strict mode) or generate one server-side (lenient mode). Stripe rejects POST requests without the key. For internal APIs, generating a key server-side is acceptable but provides no protection against client retries, since each retry generates a new key.

Key reuse across different operations: If a client reuses an idempotency key for a different request (different body, different endpoint), the server must detect this and return a 422 Unprocessable Entity error. Stripe stores a fingerprint of the original request alongside the key and compares it against subsequent requests. Silently returning a cached response for a different operation would be a serious bug — imagine returning a cached $10 payment response for a $1,000 payment request.

Partial processing failures: If the business logic partially completes before crashing (e.g., the payment was charged but the order record was not created), the idempotency key may be marked as “in progress” indefinitely. Implement a timeout for the in-progress state (typically 60 seconds) after which the key can be retried. Alternatively, use the outbox pattern to ensure that state changes and side effects are atomically committed, which is essential for systems building reliable transaction pipelines.

Key collision in hash-based strategies: SHA-256 has a theoretical collision probability of 1 in 2^128 for two different inputs, which is negligible in practice. However, if you truncate the hash (e.g., using only the first 16 characters for storage efficiency), the collision probability increases significantly. Always use the full hash or a sufficiently long prefix (at least 32 characters of hex) to maintain collision resistance.

Testing Idempotency Implementations

Test idempotency with five scenarios: (1) a single request with a new key should process normally, (2) a duplicate request with the same key should return the cached response without re-processing, (3) concurrent duplicate requests should result in exactly one execution, (4) a request with the same key but different body should be rejected, and (5) a request after the TTL expires should process normally (the key slot is available again). The dedup simulator above demonstrates scenarios 1 and 2 interactively. For scenario 3, you need load testing tools like k6 or Apache Bench sending parallel requests with the same key.

Frequently Asked Questions

What is an idempotency key?

An idempotency key is a unique identifier attached to an API request that allows the server to recognize duplicate submissions and return the original response instead of processing the request again. Stripe uses the Idempotency-Key header, AWS uses client tokens, and many APIs accept a custom request ID. The key is typically a UUID v4, a hash of the request body, or a composite of timestamp and client ID.

Why is idempotency important for webhooks?

Webhook providers deliver events with at-least-once semantics, meaning the same event may be delivered multiple times due to network timeouts, retries, or provider-side failures. Without idempotency, a payment webhook delivered twice could charge a customer twice. Idempotent webhook handlers check the event ID against a processed-events store and skip events that have already been handled.

How do I implement idempotency in a REST API?

Implement idempotency as middleware that intercepts requests before they reach your business logic. The middleware extracts the idempotency key from a header, checks a key-value store for an existing response, and either returns the cached response or allows the request to proceed and caches the response afterward. Use atomic operations (Redis SETNX or database upserts) to handle concurrent duplicates.

What is the difference between UUID v4 and hash-based idempotency keys?

UUID v4 keys are randomly generated and unique per request attempt — the client must store them for retries. Hash-based keys are deterministically derived from the request content, so identical requests automatically produce the same key. UUID v4 is better when the same payload should be allowed multiple times; hash-based keys are better for true content deduplication.

How long should I store idempotency keys?

Most implementations use a TTL of 24 to 48 hours. Stripe uses 24 hours, and AWS recommends similar windows. For webhook event IDs, store them for at least 7 days since webhook providers may retry failed deliveries over extended periods. Use Redis with TTL-based expiry or a database table with a scheduled cleanup job to manage key storage efficiently.

Related Tools