Idempotency keys let an API recognize that a retry is the same operation, not a second request to create another charge, order or account. The client assigns one key to one intended mutation and reuses that key when the outcome is uncertain. The server can then replay or continue the original result without repeating its side effects.

The key helps with duplicate-safe retries. It does not prove that the operation succeeded, replace status reconciliation or make every error safe to repeat.

The failure an idempotency key is designed for

Imagine an application sends “create this payment” and the connection closes before the response arrives. Three outcomes are possible:

  1. The server never received the request.
  2. The server rejected it before execution.
  3. The server completed the mutation, but the response was lost.

Sending a fresh request cannot distinguish those cases. If the first request succeeded, a new request may create a second payment. Retrying with the same idempotency key gives the provider a way to connect the attempt to the original operation.

Stripe’s idempotent request documentation recommends a unique key such as a UUID and says the same key must keep the same parameters. It also warns against putting email addresses or other personal data in the key.

One key represents one business operation

The safest rule is simple: generate the key when the business operation is created, persist it beside that operation, and reuse it for every network attempt that is meant to complete the same work.

For example:

  • Checkout order_7421 gets one payment-creation key.
  • A refund for that order gets a different key.
  • A later corrected payment amount is a new operation and gets a new key, but only after the first outcome has been reconciled.

A random key created inside each retry loop defeats the protection. Every attempt looks new. A single global key is worse: unrelated operations collide.

Keys also need scope. Stripe API v2 considers the key together with the API, account or sandbox and a time window. Other providers use different scopes and retention periods. Do not copy one vendor’s window into a general API client.

Reuse the key and the parameters

An idempotent retry repeats the same intent. That means the endpoint, account, currency, amount and other material parameters must match.

Stripe API v1 compares incoming parameters with the original request and returns an error if the same key is reused with different parameters. That guard matters. Without it, one token could refer to two incompatible operations.

If validation fails before execution begins, correct the request only after you know the original did not run. If the first outcome is unknown, query the provider’s object, request log or event stream before changing the parameters or issuing a new key.

This is the same recovery principle used around payment capture: unknown is a state to reconcile, not permission to try a second mutation.

Idempotency is not the same as retry policy

A key answers “is this the same operation?” A retry policy answers “should the client try again now?”

Stripe’s low-level error guide separates content, network and server errors:

  • A network timeout can leave the outcome unknown. Retry the same operation with the same key and parameters within the provider’s supported window.
  • A rate limit needs delay and backoff. Repeating immediately can extend the incident.
  • A validation error needs a correction, not a blind retry.
  • A server error can still have side effects. Reconcile before using a new key.

The provider may expose more specific guidance. Stripe documents a Stripe-Should-Retry response header and recommends exponential backoff. The existing OpenAI API rate-limit guide shows why backoff, quota checks and retry safety are separate decisions.

API behavior is provider- and version-specific

“Idempotent” does not guarantee that every provider replays the same bytes forever.

Stripe documents an important version difference:

Behavior API v1 API v2
Mutations covered POST requests POST and DELETE requests
Documented replay window At least 24 hours 30 days
Successful first request Saved response is returned No new side effect; an updated response can be returned
Failed first request The saved result can include an error Failed or partial work may be retried without repeating completed side effects

Those rules belong to Stripe, and they can change with API versions. Read the documentation for the actual endpoint and version you call.

HTTP method semantics also do not remove application work. GET is intended to be safe and idempotent; PUT and DELETE are generally idempotent by method semantics. But a client still needs timeouts, backoff, authentication handling and reconciliation when a provider’s implementation or a downstream system fails.

A durable retry workflow

A reliable mutation path keeps the operation record in your own database:

  1. Create a local operation ID before calling the provider.
  2. Generate and persist one idempotency key for that operation.
  3. Save the normalized request parameters or a fingerprint.
  4. Send the request with the key.
  5. Record the response, provider object ID and request ID.
  6. On a timeout, retry with the same key and parameters.
  7. On a definite validation failure, correct the operation deliberately.
  8. On an unresolved outcome, retrieve provider state and consume signed events before starting anything new.

This design survives process restarts because the key is stored outside application memory. It also gives support staff something concrete to trace.

Do not store secrets inside the key. Do not log full request bodies when they contain payment details or personal information. The key can be opaque while the operation record holds the authorized internal references.

Webhooks still need deduplication

An idempotency key protects an outbound mutation. It does not deduplicate incoming webhooks.

Webhook deliveries normally carry their own event or delivery ID. Store that identifier behind a unique constraint and acknowledge the delivery only after the event is safely recorded. Then use a worker to apply the business change. The webhooks vs polling guide explains why providers can deliver the same event more than once and why polling remains useful for reconciliation.

Likewise, a webhook confirming an operation is evidence about provider state; it is not the idempotency key itself.

Common mistakes

  • Generating a new key for every retry.
  • Reusing one key for different parameters.
  • Assuming a key works indefinitely.
  • Retrying every 4xx response.
  • Treating a cached error as proof that nothing happened.
  • Skipping provider-state reconciliation after the retry window.
  • Using customer data as the key.
  • Deduplicating webhooks with the outbound mutation key.

The underlying discipline is to separate intent, transport attempts and confirmed state. One business intent can have several network attempts but should still resolve to one provider-side operation.