Free tools Windows power users keep installed
One-click scans. No signup required.
An idempotency key lets an API recognize that a retry belongs to an operation it has already seen. For a completed request, a well-designed API can return the saved status and body instead of performing the operation again. That is essential for operations such as creating a payment or order, where a client may time out without knowing whether the server succeeded.
The key is not a magic exactly-once switch: it does not make a database update, external API call, or sequence of side effects atomic. A production design must define how requests are matched, what concurrent retries receive, how results are stored, and how failures and expiry are handled. Redis can provide a fast claim and response record, but the business operation still needs its own consistency and recovery strategy.
As an Amazon Associate I earn from qualifying purchases.
What is an idempotency key?
An idempotency key is a client-supplied identifier for one logical operation. The client generates it once, includes it on the request, and reuses it when retrying that same operation. The server associates the key with the request and its outcome. If a matching retry arrives after completion, the server can return the saved result rather than run the work again.
This solves an ambiguity common to networked APIs: a timeout does not tell the client whether the server never received the request, received it but did not finish, or completed it but lost the response on the way back. Stripe describes its own API behavior this way: “The API supports idempotency for safely retrying requests without accidentally performing the same operation twice.” That is Stripe’s contract, not a universal rule for every API.
#1 Best Overall
Idempotency keys are particularly useful for retryable requests that cause a meaningful side effect, often a POST. They do not change the HTTP method’s general semantics; they add an application-level way to identify duplicate attempts at the same operation.
What should happen when the same key is sent twice?
Write down the contract before writing the storage code. The key alone is not enough: the server must know which caller and operation it applies to, whether the retry is the same request, and what response to return in each state.
| Request condition | Recommended API behavior |
|---|---|
| First request with a valid key | Claim the key, execute the operation, and save the outcome before returning success. |
| Same key, same request, operation completed | Replay the saved status and body; do not repeat the side effect. |
| Same key, same request, operation still running | Return a documented in-progress response, such as a conflict with retry guidance, or wait under a documented policy. Do not start the side effect again. |
| Same key, materially different request | Reject the reuse. Do not treat a different payload as a retry of the original operation. |
| Validation fails before work begins | Choose and document whether the key is retained. Stripe says it does not save an idempotent result when validation fails. |
| Retry races with an executing request | Return the defined in-progress response. Stripe documents that a conflict with another executing request does not save a result. |
| Key has expired or been pruned | A later request may be treated as new. Tell clients how long they can safely retry. |
The validation, concurrency, parameter-matching, replay, and retention behaviors in the examples above are specifically documented by Stripe’s idempotent requests documentation. Stripe is an example of a vendor contract, not evidence that another API should make identical choices.
How should the key be generated and scoped?
Generate one key per logical operation
The client should generate a high-entropy key and keep it unchanged across retries of that operation. It must generate a new key for a different operation. In Node.js, crypto.randomUUID() generates a random RFC 4122 version 4 UUID using a cryptographic pseudorandom number generator. Node documents it as added in v14.17.0 and v15.6.0; the code sketch below targets Node.js 25.9.0. Stripe suggests a V4 UUID or another sufficiently random string and documents a 255-character maximum for keys sent to its API. That character limit is Stripe-specific, not a general HTTP limit. See Node.js Crypto documentation and Stripe’s key guidance.
Rank #2
Scope records to caller and operation
Do not use the raw client key as a globally shared Redis key. Scope the record to the authenticated principal and the operation—for example, tenant or account, HTTP method, route, and client key—so the same key from unrelated callers or endpoints cannot collide. Derive a storage key from those components, and avoid putting sensitive request data directly into a Redis key.
Also store a fingerprint of the request’s meaningful parameters. Compare a canonical representation rather than arbitrary raw JSON bytes, since property order or irrelevant formatting should not make equivalent requests appear different. Decide which fields define the operation; for example, the account, amount, and currency may matter for a payment, while a tracing header generally should not. If the same scoped key arrives with a different fingerprint, reject it rather than replaying an unrelated result.
How does a Redis-backed idempotency engine work?
A practical state model has a record for each scoped key, moving from in_progress to completed. Some systems may also need a deliberate failure state. The record needs enough information to recognize a matching retry and, after completion, reproduce the promised response: at minimum the request fingerprint, status code, and response body. If clients rely on selected response headers, define which of those are saved and replayed too.
- Validate before claiming. Authenticate the caller and perform validation that can safely happen without executing the business operation. Decide whether invalid requests consume a key; Stripe’s policy is not to save an idempotent result for validation failures.
- Build the scope and fingerprint. Combine the authenticated principal, method, route, and idempotency key into a non-colliding record identity. Canonicalize and fingerprint the parameters that define the operation.
- Atomically claim a missing key. Use Redis
SET key value NX EX secondsto create an initialin_progressrecord only if no record exists, with an expiry. Redis returnsOKwhen the claim succeeds and a null result when theNXcondition fails. The Redis SET command documentation describes these options and return values. - Resolve a failed claim. Read the existing record. A different fingerprint means reject key reuse. A matching
completedrecord means replay its saved result. A matchingin_progressrecord means return the documented in-progress response or wait; never interpret a failed claim as permission to execute concurrently. - Run the business operation once under its own correctness rules. The claim prevents ordinary simultaneous duplicates from both entering the work path, but it does not make the work atomic with Redis.
- Persist the completed response before replying. Transition the record to
completedwith the status and body the API promises to replay. Use an atomic Redis operation for state transitions that must not overwrite a different fingerprint or an unexpected state. If persistence fails, do not report a successfully replayable result that was never saved; reconcile the business operation before deciding what a retry should do.
SET ... NX EX ... is a claim primitive, not a response-replay engine. It does not atomically claim a key now and later save an arbitrary response as one indivisible operation. Protect transitions with an appropriate script or transaction, and verify that the record is in the expected state before changing it. A plain overwrite can otherwise let a late worker replace a newer state.
Rank #3
For example, this Node.js sketch targets Node.js 25.9.0 and assumes an already connected Redis client with the common set/get interface. It illustrates the claim decision, not a complete server, framework, persistence policy, or atomic completion script:
import { randomUUID, createHash } from 'node:crypto';
const key = randomUUID(); // Generate once for this logical operation; reuse on retry.
const scope = `${tenantId}:POST:/payments:${key}`;
const redisKey = `idem:${createHash('sha256').update(scope).digest('hex')}`;
const fingerprint = createHash('sha256')
.update(canonicalize({ amount, currency }))
.digest('hex');
const initial = JSON.stringify({ state: 'in_progress', fingerprint });
const claimed = await redis.set(redisKey, initial, { NX: true, EX: retentionSeconds });
if (claimed !== 'OK') {
const existing = await redis.get(redisKey);
if (!existing) {
// The record expired or changed between the claim attempt and read.
// Retry the claim decision; do not assume another request completed.
} else {
const record = JSON.parse(existing);
if (record.fingerprint !== fingerprint) {
throw new Error('Idempotency key reused with different parameters');
}
if (record.state === 'completed') {
return { status: record.status, body: record.body };
}
return { status: 409, body: { error: 'Request is still in progress' } };
}
}
// Execute business work here, with database/provider safeguards of its own.
// Persist completed status/body with a state-checked atomic transition before responding.
canonicalize, the HTTP error mapping, the in-progress policy, and the completion transition are intentionally application-specific. In production, handle the expiry/read race by retrying the claim decision in a bounded way or returning a documented retry response; do not proceed with work unless this request has a valid claim. The sketch’s ordinary get and set sequence is not an atomic state transition.
Why a Redis claim is not exactly-once execution
The hardest failure is not a duplicate that arrives while the first request is visibly running. It is a failure after the business effect happened but before the completed response was safely recorded. For example, a payment provider may accept a charge, then the API process may lose its connection before saving the response in Redis. A retry cannot infer from a missing completion record whether the charge happened.
The Tool Desk
Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Exactly-once behavior across Redis, a relational database, and an external service cannot be guaranteed by a Redis lock alone. Match the recovery design to where the side effect lives:
Rank #4
- Business change in the same database: Prefer storing the idempotency record, business update, and result in one database transaction where feasible. A uniqueness constraint on the operation identity can provide a durable guard. Redis may accelerate lookups, but should not be the only protection if losing its record could duplicate a consequential update.
- External service call: If the provider supports idempotency, send a stable operation identifier there too. Persist enough local state to reconcile an ambiguous timeout by querying the provider or retrying with that same identifier; do not issue a new operation blindly.
- Multi-step workflow: Record durable progress and define recovery for each step. An outbox or equivalent pattern can coordinate database changes with asynchronous delivery, but the exact choice depends on the application’s data store and workflow.
These patterns address the boundary that the Redis claim cannot cross. They do not make every external system transactional; they make ambiguous outcomes recoverable under an explicit application-level design.
How should failures and stuck requests be handled?
Choose a policy for each failure point. A process crash can leave an in_progress record even though the worker is gone. Expiring that record eventually allows another attempt, but if the original side effect happened just before the crash, that retry can duplicate it. Conversely, keeping the record indefinitely can strand a legitimate operation. Use the business system’s durable operation status or reconciliation path to decide whether work can be resumed, marked complete, or safely retried.
- Failure before any side effect: If it is known that no work occurred, release or mark the claim according to the documented retry policy. Do not use a blind delete if another request may have acquired a newer claim.
- Failure after a side effect: Reconcile against the database or provider using the same durable operation identity, then store the actual outcome for future retries.
- Worker timeout or crash: Treat the state as uncertain until checked. A lease timeout alone is not proof that the operation did not complete.
- Redis unavailable during claim or completion: Choose whether this endpoint fails closed, waits, or relies on a durable database record. For consequential operations, bypassing idempotency and proceeding with the side effect is not a safe fallback.
Redis documents a simple lock use of SET NX EX, while cautioning that the simple pattern is discouraged for locking use cases in favor of Redlock; its guidance also discusses random lock tokens and token-checked release so an expired lock is not accidentally deleted after a newer lock is acquired. Those lock-specific concerns matter if you are implementing mutual exclusion. They do not turn a short-lived lock into a durable API response record, and lock advice should not be imported uncritically into a response-replay design. See Redis SET.
Quick wins for a faster PC:
Scan for outdated or missing drivers - takes under a minuteDriver Scan →Clear out junk files and repair common Windows errorsFree Scan →How long should an idempotency key be kept?
Retention is part of the API contract and reliability plan. Choose a window that covers the retries clients are expected to make and the consequences of treating an old retry as a new operation. Tell clients the window and advise them to retain and reuse the same key only for retries of that operation. Once a record expires, the server may have no way to distinguish a late retry from an intentional new request.
Stripe says it may remove keys once they are at least 24 hours old; after pruning, reuse can be treated as a new request. That is Stripe’s policy, not a recommended universal duration. Redis can set an expiry atomically as part of SET using options such as EX; ensure your in-progress and completed-state expiry choices do not silently allow a retry clients still consider valid. Sources: Stripe retention behavior and Redis SET expiry options.
A single TTL has trade-offs: a long-lived in-progress record can block retries after a worker dies, while a short one may expire during a slow operation and allow a concurrent duplicate. If your design uses separate in-progress and completed retention periods, implement the transition atomically and define what happens to expired or abandoned work. The appropriate durations depend on operation latency, client retry behavior, and the recovery mechanism; the sources do not establish a universally correct TTL.
What can go wrong when the Node.js Redis connection drops?
Do not assume a Redis client retry is harmless because the command is related to idempotency. Redis’s Node.js production guidance warns that automatic reconnect can queue commands while disconnected and resend them later. If a state-changing command reached Redis before the connection dropped, replaying it may produce an incorrect result when the command is not itself idempotent. The client option disableOfflineQueue discards commands that have not been executed rather than queuing them. See Redis production usage for Node.js.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Decide this behavior deliberately for the idempotency path. Disabling the offline queue can make a disconnected request fail promptly, leaving the caller to retry with the same key; it is not a universal setting for every application, and it does not resolve uncertainty if Redis executed a command but the acknowledgement was lost. Treat connection errors around claims and completion as ambiguous when appropriate, inspect durable state before repeating a business effect, and configure client command retries according to whether each operation is safe to replay.
How does API idempotency differ from Redis Streams idempotency?
These mechanisms solve related but different problems. An API idempotency record is about remembering a request’s outcome so an HTTP retry can receive the same response. A Redis lock or claim identifies a claimant or suppresses a duplicate attempt, but by itself has no saved HTTP status/body to replay. Redis Streams producer idempotency helps a producer avoid adding duplicate stream entries; it is not an HTTP response-replay facility.
| Mechanism | What it identifies | What a duplicate gets | Best fit |
|---|---|---|---|
| API idempotency record | A scoped request plus its parameters and stored outcome | A replayed saved response after completion; a documented in-progress result while running | Retryable HTTP operations where clients need a stable result |
Redis SET NX EX claim or lock |
Whether a key was newly claimed | Claim success or failure; response replay requires additional state and logic | Claiming work or coordinating a critical section, with appropriate lock safeguards |
| Redis Streams producer idempotency | Producer message identity | Duplicate stream entry suppression under the stream feature’s producer-scoped rules | Idempotent message production, not HTTP response replay |
Redis Streams supports producer-side idempotent message production using XADD with IDMP or IDMPAUTO. Retries need the same idempotent ID for detection, and tracking is producer-scoped. This can protect stream production, but the consumer-facing HTTP service still needs its own request/result contract. See Redis Streams idempotent message processing.
Quick Recap
Production checklist
- Generate a high-entropy key once per logical operation, and preserve it across client retries.
- Scope the record by authenticated principal and operation, then fingerprint only the parameters that define the operation.
- Document replay, in-progress, mismatch, validation-failure, and expiry behavior for clients.
- Use an atomic claim for the initial state and a state-checked atomic transition for completion.
- Save the response before returning it when your contract promises response replay.
- Protect consequential business changes in their own durable system; plan recovery for the gap between a side effect and response persistence.
- Set and communicate a retention window that matches your clients’ retry behavior and the cost of a duplicate.
- Choose how the Node.js Redis client behaves during disconnection, and treat uncertain command outcomes as a correctness issue rather than a harmless retry.
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




