Payment API idempotency lets an app retry bad work so one job will not run the same pay or send step twice in a row.
An idempotency key turns a risky retry into a repeatable operation. The client chooses one unique key for one intended change, stores it with the business operation, and sends the same key and the same parameters on every retry. Stripe’s API v1 then returns the first saved result, including a server error, instead of running the request again.
That sounds simple. The difficult part is deciding where keys come from, how long they live, and what the application does when the response is uncertain.
What payment API idempotency protects
A network timeout does not tell the caller whether the server completed a request. The payment might have been created even though the client never received the response. Retrying without a stable key can therefore create a second payment, customer, transfer or refund.
An idempotency key gives the server a way to recognize that the second request represents the same intent. Stripe recommends a UUID v4 or another random value with enough entropy to avoid collisions. Its API accepts keys up to 255 characters and warns against putting email addresses or other personal identifiers in them.
The key is not a universal duplicate detector. It works only inside the provider’s defined scope and retention window. A product still needs its own business identifier, reconciliation records and a way to investigate requests that never reach a final state.
Build the key around business intent
Create the key when the application creates the business operation, not each time the HTTP client retries. Save it before the first outbound request.
| Design choice | Safer implementation | Failure it avoids |
|---|---|---|
| Key creation | One random key per intended create or update | A retry producing a fresh key |
| Persistence | Store the key beside the order, payout or refund | Losing retry identity after a process restart |
| Parameters | Reuse the exact endpoint and payload | Reusing one key for a different amount or account |
| Retry policy | Back off and keep the same key | A retry storm that creates conflicting work |
| Reconciliation | Record the provider object ID and request ID | Treating transport success as settlement success |
Do not derive a key only from an amount. Two customers can make legitimate payments for the same amount, and the same customer can repeat a purchase. A random operation ID stored against the internal order is easier to reason about.
Stripe API v1: what the server saves
Stripe says API v1 saves the status code and response body after endpoint execution begins. A later request with the same key returns that saved response, even when the first response was a 500 error.
Stripe also compares the endpoint and parameters with the first request. Reusing a key with different parameters produces an idempotency error. This is a useful guardrail: a worker cannot quietly turn a retry into a different transfer or refund.
Validation failures and concurrent conflicts are different. If execution never starts, Stripe does not save an idempotent result, so the request can be retried after the caller fixes the validation problem or waits for the conflicting request to finish.
All API v1 POST requests accept idempotency keys. GET and DELETE requests do not need them because those operations are idempotent by definition in Stripe’s API.
The 24-hour boundary matters
Stripe says API v1 keys can be removed after they are at least 24 hours old. If a pruned key is reused, the server treats the request as new.
The client should not turn that retention period into its own retry window. A payment operation that remains uncertain for a day belongs in reconciliation, not in an unbounded retry loop. Store the key and timestamps long enough to investigate the case, then query the provider or reconcile against its reports before sending any new request.
This distinction is important:
- the provider’s idempotency record stops duplicate endpoint execution inside its window;
- the product’s ledger and order database decide whether the business operation is still open;
- settlement and reconciliation confirm what actually moved.
For the wider funds flow, see Stripe Connect charge types and marketplace chargebacks.
Handle each failure class deliberately
Connection lost before any response. Retry with the same key and identical parameters. The provider can return the saved result if execution began.
Validation error. Fix the request before retrying. Do not change the payload while keeping a key that may already be associated with an executed request.
Concurrent conflict. Pause, then read the operation state or retry with the same key. Avoid launching several workers for the same internal operation.
Server error. Stripe API v1 can replay the saved 500 response for the same key. Do not keep hammering the endpoint expecting a different answer. Use the provider’s request ID and object search or reconciliation path to determine the outcome.
Key older than the provider’s retention window. Treat the operation as an exception. Confirm whether the original object exists before attempting a fresh request.
An implementation checklist
- Give every outbound money operation its own internal ID.
- Generate and persist one opaque idempotency key before the first API call.
- Keep the endpoint and parameters immutable for every retry of that operation.
- Log the provider request ID and resulting object ID without logging secrets or personal data.
- Apply bounded exponential backoff for retryable transport failures.
- Move long-running uncertainty to an exception queue instead of retrying forever.
- Reconcile the operation against provider objects, balance transactions and settlement reports.
- Test timeouts both before and after the provider begins execution.
The practical goal is not merely “no duplicate HTTP request.” It is one business intent, one provider-side operation and one ledger outcome that can be traced after a timeout.




