Skip to content

Stripe: making payment retries safe with idempotency keys

Organization Stripe
Period Described in 2017; still part of Stripe’s API
Problem A network failure can leave a client unsure whether a payment happened
Architecture Client-generated idempotency keys, stored results, and retries with exponential backoff and jitter
Patterns Idempotency, retries, safe APIs

Any call across a network can fail in a way that leaves the caller unsure what happened. Some failures are clearly safe to retry, such as a connection that never opened. Others are not: the server might have created the charge, but the response was lost on the way back. If the client simply retries, the customer could be charged twice. For a payments API, that is unacceptable.

Idempotency keys. The client generates a unique ID for each operation it wants to happen exactly once and sends it in an Idempotency-Key header. If it has to retry, it sends the same key.

On the server:

  • A key it hasn’t seen means a new request, which it processes normally and records.
  • If a request failed partway, the server either completes or rolls back the work. A transactional database makes the rollback clean.
  • If the request already succeeded, the server returns the stored result instead of doing the work again.
sequenceDiagram
    participant C as Client
    participant S as Stripe API
    participant D as Database
    C->>S: POST /charges, Idempotency-Key: k1
    S->>D: Record k1, create charge
    S--xC: Response lost
    C->>S: Retry POST /charges, Idempotency-Key: k1
    S->>D: Look up k1: already succeeded
    S-->>C: Stored response, no second charge

Retry politely. Clients wait longer after each failure, roughly doubling the delay each time (exponential backoff), and add random jitter so that thousands of clients that failed at the same moment don’t all retry at the same moment and overload the server again.

The pattern is still how Stripe’s API works today. Its current documentation says:

  • Stripe’s SDKs can generate idempotency keys and retry automatically, with exponential backoff.
  • Stripe keeps keys for at least 24 hours, and a replayed response is marked with an Idempotent-Replayed: true header.
  • Reusing a key with different parameters returns an error, and a second request arriving while the first is still running gets a 409 Conflict.

The approach has been widely copied, and the IETF’s HTTP APIs working group has a draft that standardizes the Idempotency-Key header.

  • The server must store keys and results for a period, and decide how long to keep them. After that, a retry with an old key is treated as new.
  • Same key, different request is a client bug the server should detect and reject.
  • Concurrency. Two requests with the same key arriving at once must not both run; that needs a lock or a unique constraint.
  • Unknown outcomes still need reconciliation. If retries run out, the client must look up what actually happened before trying again with a new key.
  • Any API that creates or moves something of value should accept an idempotency key.
  • Let the client generate the key, so it can be reused across retries and even across a client restart.
  • Always pair retries with backoff and jitter.

Written and edited by Abhinav Taduka, enterprise software architect.