Skip to main content

Real architecture · Senior

Stripe: predictable APIs with idempotency keys

Real production architecture. Key points: Idempotent Operations, API Design Fundamentals, and Cloud Design Patterns.

17 min read · Source: Stripe · Content reviewed on October 22, 2026

Short answer

Real production architecture. Key points: Idempotent Operations, API Design Fundamentals, and Cloud Design Patterns. Every call between two nodes crosses a network, and networks fail in exotic ways, even the ones that are statistically reliable. Between a client and a server there are three distinct failures: the initial connection may not even be established; the call may drop midway, while…

01 · CONTEXT

The response that does not arrive

Every call between two nodes crosses a network, and networks fail in exotic ways, even the ones that are statistically reliable. Between a client and a server there are three distinct failures: the initial connection may not even be established; the call may drop midway, while the server is carrying out the operation, leaving the work in limbo; or the call may succeed and the connection drop before the server notifies the client.

Only the first failure is harmless: the client knows nothing happened and can retry. In the other two, the success of the operation is ambiguous from the client's point of view, which does not know whether retrying is safe. This holds for any distributed system, and the article points out that even the Stripe API, with a single other server calling it, already is one.

  1. The client sends the charge request to the API server.

    Architecture diagram: Charge with no response: the client does not know what happened
    CLClientsends the request
    CPAPI server
    DBCharges database
  2. The server executes the operation and writes the effect to the database: the charge really happened.

    Architecture diagram: Charge with no response: the client does not know what happened
    CLClient
    CPAPI server
    DBCharges databaseeffect recorded
  3. The connection drops before the response arrives. From the client's point of view, nothing indicates whether the charge was made.

    Architecture diagram: Charge with no response: the client does not know what happened
    CLClientno response
    CPAPI server
    DBCharges database
  4. If the client retries with no protection, the server executes again and the charge happens twice.

    Architecture diagram: Charge with no response: the client does not know what happened
    CLClient
    CPAPI server
    DBCharges databaseduplicated effect

The simplest way out is to design the server so that retrying is always safe. An endpoint with this property is called idempotente: it can be called any number of times with the side effects occurring only once. Faced with any error, the client retries until it gets a verifiable success, and its state converges with the server's.

02 · IDEMPOTENT BY NATURE

When retrying is already safe

The article uses as an example the API of a hypothetical DNS provider that creates a subdomain with a PUT. All the information needed to create the record goes in the call, so the client can invoke it as many times as it likes; if the server notices it is a duplicate because the domain already exists, it ignores the request and responds with success. By HTTP semantics, PUT and DELETE are idempotent, and PUT creates or replaces the entire resource with the contents of the body.

Stripe's documentation applies the same reasoning. In API v1, GET and DELETE are idempotent by definition: repeating them does not repeat the effects of the original operation, and sending an idempotency key on them changes nothing. POST requests, on the other hand, which change state, accept the key.

Type of operationExemploIdempotency key?
PUT with the full stateCreate a DNS record with all fieldsUnnecessary, retrying is already safe
GET and DELETERead or delete a resourceNo effect, idempotent by definition
POST that changes stateCharge a customerRequired, retrying would charge again

03 · THE IDEMPOTENCY KEY

Exactly once, even with retries

And when the operation must happen only once? The article's example is a charge endpoint: calling it twice by accident would charge the customer twice. The idempotency key solves this. When making the request, the client generates a unique ID that identifies only that operation and sends it along with the normal body; the server correlates the ID with the request state on its side. If the client notices a failure, it retries with the same ID, and it is up to the server to decide what to do.

FalhaWhat the server does on a retry
Failure to connectSees the ID for the first time and processes normally
Failure midway through the operationIf an ACID database rolled everything back, retries in full; otherwise recovers the state and continues
Failure on the responseResponds with the cached result of the operation that succeeded

Note that the article does not prescribe a single behavior: what happens in the middle case depends on the implementation, which is why the choice of storing the state of each request matters so much.

  1. The client generates a unique key for the operation and sends it along with the normal request body.

    Architecture diagram: Idempotency layer in front of the operation
    CLClientgenerates the key
    CPAPI with idempotency layer
    DBKey registry
    CPOperation execution
  2. The idempotency layer checks the registry. The key has never been seen, so the request proceeds to execution.

    Architecture diagram: Idempotency layer in front of the operation
    CLClient
    CPAPI with idempotency layer
    DBKey registrynew key
    CPOperation execution
  3. The operation runs and the server stores the status and response body in the registry, after the endpoint execution begins (API v1 rule).

    Architecture diagram: Idempotency layer in front of the operation
    CLClient
    CPAPI with idempotency layer
    DBKey registryresponse saved
    CPOperation executionexecutes once
  4. The response is lost on the way and the client is left not knowing the result.

    Architecture diagram: Idempotency layer in front of the operation
    CLClientno response
    CPAPI with idempotency layer
    DBKey registry
    CPOperation execution
  5. The client retries with the same key. The layer finds the saved response and returns it, without calling the operation again.

    Architecture diagram: Idempotency layer in front of the operation
    CLClient
    CPAPI with idempotency layer
    DBKey registryknown key
    CPOperation execution
  6. If the retry arrived with parameters different from the originals, the layer would reject it with an error instead of executing.

    Architecture diagram: Idempotency layer in front of the operation
    CLClient
    CPAPI with idempotency layerrecusa
    DBKey registry
    CPOperation execution

The client generates the key. How to generate it is up to the client, but the documentation suggests UUIDs v4 or another random string with enough entropy to avoid collisions. Keys can be up to 255 characters and should not carry sensitive data, such as emails or personal identifiers. A second strategy is to derive the key from an object tied to the user, such as a shopping cart ID, which protects directly against double submission.

04 · WHAT THE SERVER DECIDES

Execute, return, or reject

For each retry the server has to choose between returning what it already has, rejecting, or actually executing. Stripe's documentation defines the API v1 cases, which the table below gathers.

Retry situationWhat happens
Same key and same parameters, after execution has begunReturns the saved status and body of the first request
Same key with different parametersError, to prevent misuse of the key
Invalid parameters or conflict with another request in progressNothing is saved, and the request can be retried
Conflict with another request, possibly using the same keyResponse with status 409, conflict
Key already removed and reusedMay execute a new request

The client can tell a response replayed by the server by looking for the Idempotent-Replayed header with a true value.

05 · KEY LIFECYCLE

Retry window and reconciliation

The key does not live forever. Stripe retains keys for at least 24 hours and may remove them after that, and reusing a removed key may execute a new request. That is why the guidance is to limit retries of an unresolved operation to the first 24 hours after the original request, always with the same key and the same parameters.

If the result is still unknown after that window, do not retry, neither with the old key nor with a new one. Instead, fetch the relevant object, inspect the request in the dashboard, and use webhook events to reconcile the result.

Another practical rule: if retries span separate SDK calls or process restarts, the application should generate and persist the operation's key and supply it on every attempt.

  1. Before the first attempt, the application generates the operation key and persists it.

    Architecture diagram: Key lifecycle and reconciliation
    CPIntegrator application
    DBPersisted keyskey stored
    CPPayments API
    CPWebhook handler
    DBLocal records
  2. The request goes unanswered. While the window allows, the application retries with the same key and the same parameters.

    Architecture diagram: Key lifecycle and reconciliation
    CPIntegrator applicationno response
    DBPersisted keys
    CPPayments API
    CPWebhook handler
    DBLocal records
  3. If the process restarts midway, the application reads the persisted key instead of creating another.

    Architecture diagram: Key lifecycle and reconciliation
    CPIntegrator application
    DBPersisted keysreaproveita
    CPPayments API
    CPWebhook handler
    DBLocal records
  4. Once the window passes with no known result, the application stops retrying, neither with the old key nor with a new one.

    Architecture diagram: Key lifecycle and reconciliation
    CPIntegrator applicationstops retrying
    DBPersisted keys
    CPPayments API
    CPWebhook handler
    DBLocal records
  5. Reconciliation fetches the object and reads the webhook events, matching what happened with the local records.

    Architecture diagram: Key lifecycle and reconciliation
    CPIntegrator application
    DBPersisted keys
    CPPayments API
    CPWebhook handlerreads events
    DBLocal recordsstate reconciled

06 · RETRY POLITELY

Exponential backoff, jitter, and the three rules

Retrying also has a cost. If the server is in an incident with real unavailability, retries do not get through and can worsen the degradation. That is why the recommendation is exponential backoff: the client waits briefly after the first failure and, as the operation keeps failing, waits a time proportional to 2^n, where n is the number of failures. Even so, if many clients failed at almost the same time, the retry schedules can line up and hit the server together, the so-called thundering herd problem. The fix is to add a random jitter to each client's wait.

Stripe's Ruby library does this on its own: it automatically retries on failure, with the same idempotency key, increasing wait times, and jitter.

Stripe-Should-Retry headerWhat the client does
VerdadeiroRetries using exponential backoff, so as not to overload the API
FalsoDoes not retry
AusenteDecides based on the status code and other information in the response

Frequently asked questions

What is Stripe: predictable APIs with idempotency keys?

Real production architecture. Key points: Idempotent Operations, API Design Fundamentals, and Cloud Design Patterns.

What does the Stripe: predictable APIs with idempotency keys design cover?

It is organized in these sections: The response that does not arrive, When retrying is already safe, Exactly once, even with retries, Execute, return, or reject, Retry window and reconciliation, and Exponential backoff, jitter, and the three rules.

How long is Stripe: predictable APIs with idempotency keys and what level is it?

It takes about 17 minutes to read and is rated Senior level.

Now design it yourself

Run this case as an interview in the System Design school: you make the decisions, the tutor challenges them and you see how your design scores.