The journal
Idempotency keys: safe retries without duplicate actions
Understand what an idempotency key protects, reuse it for the same operation and handle timeouts, concurrency and expired keys safely.

Key takeaways
- One logical operation gets one stable key. Retries reuse it; genuinely new operations get different keys.
- The receiving API must implement idempotency. Adding a header alone does not make an action safe.
- Provider scope, retention and failure handling matter. A timeout is an unknown outcome, not proof that nothing happened.
What does an idempotency key do?
An idempotency key lets a receiving service recognise retries of the same operation and apply its documented duplicate-handling behaviour. Imagine an order-creation request succeeds, but its response disappears before your workflow receives it. Repeating that request with the same key lets a supporting API identify the retry instead of treating it as a new order.
The protection has boundaries. It depends on the endpoint, account scope, request parameters and retention policy. It does not mean every downstream action happens exactly once. A workflow can still send two emails if only its payment step supports idempotency.
Choose a key for the operation, not the network attempt
For a new checkout, create an opaque token and save it alongside the local order before sending the first request. If a connection fails, retrieve that saved token and resend the same operation with the same parameters. Generating another token inside each retry loop defeats the protection.
Two separate purchases of the same product need different keys even if their amounts match. A hash of the payload alone can accidentally merge those legitimate purchases. A deterministic key can work when it includes a stable identifier for the specific business operation; the important property is operation identity, not simply identical text.
Keep tokens free of customer names, email addresses and secrets. Log a local operation reference, the provider request identifier and the outcome separately so support staff can trace a problem without exposing the request body.

Read the receiving API's rules
Stripe's API v1 documentation says it saves the initial status and response body once endpoint execution begins, including a 500 error. A repeated key can therefore return that same error. Validation failures and concurrent-request conflicts are handled differently because execution may not have begun. Its documentation also describes pruning keys after at least 24 hours; a reused key after pruning creates a new request.
Shopify's supported GraphQL mutations have their own idempotency arguments, concurrent-request errors and a documented 24-hour retention window. Do not assume a header copied from another provider activates the feature on every mutation.
Write down the exact API version, supported endpoint, account scope, key location, parameter-mismatch response, concurrent behaviour and expiry rule before configuring automatic retries. These details are part of the integration contract, not optional implementation notes.
Separate local duplicate control from external effects
A local database can use a unique operation record to coordinate workers. Reserve the operation atomically, record a request fingerprint and track whether the work is pending, complete or awaiting investigation. Two workers that both check for a missing record and then insert without a uniqueness constraint still have a race.
If the effect and the operation record are changes in the same database, an appropriate transaction can commit them together. An HTTP call to another company is outside that transaction. Your process might crash after an external charge succeeds but before it stores the response.
That gap needs the downstream provider's idempotency support or a recovery method such as looking up the existing operation and reconciling its state. An outbox can make delivery durable, but repeated delivery still needs duplicate-safe handling at the receiver. Temporal's explanation of activity retries illustrates why retrying an activity and protecting its side effects are separate concerns.
Handle an ambiguous timeout deliberately
- Mark the local operation as unresolved rather than failed conclusively.
- Keep the original key and request parameters.
- Use the provider's documented status lookup or same-key retry behaviour while the protection remains valid.
- Respect documented retry limits and backoff. Escalate repeated uncertain responses for reconciliation.
- Before a delayed replay after expiry, check whether the original action already happened. Do not blindly create a new key.
A changed amount or destination is not a retry of the same request. Resolve the old operation's outcome before starting replacement work; otherwise both versions may eventually succeed.

Check the failure paths before enabling unattended retries
The checklist below is a planning aid. Ticking it does not call an API or prove an integration passed. In a test environment, capture both the responses and the actual side effects for each case.
Retry review checklist
For incoming webhooks, follow that provider's authentication and event-identity rules. A delivery event identifier is often more appropriate than inventing a new token for each delivery. Protect each business effect as well as acknowledging receipt.
Use our guide to retries without duplicates for the wider workflow design, and the webhook and API comparison when deciding which side initiates the work.
Common questions
Is an idempotency key a password?
No. It identifies an operation for duplicate handling. Authentication and authorisation still need their own controls.
Should every retry get a new key?
No. A retry of the same operation normally reuses its key under the receiving API's rules. A genuinely separate operation needs its own identity.
Does this guarantee exactly-once execution?
No. Review every side effect, the receiver's guarantees, key expiry and recovery from uncertain outcomes. One protected API call does not protect an entire workflow.