Model the operation, not merely the request

A timeout is not proof that an external mutation failed: the client can lose the reply while the remote service may have received the work. Treat the business action as a durable operation with an identity of its own, rather than allowing an HTTP attempt to be the only record of intent.

For connectivity failures, Stripe advises resubmitting the original key with an unchanged parameter set until the client can obtain a response. This is a way to recover an answer to one logical attempt; it is not an exactly-once guarantee for the surrounding workflow.

Stripe records the first execution-started POST result under its idempotency key, preserving both its HTTP status and response payload. A reuse whose parameters differ is rejected as a mismatch, so a retry path must retain the exact request representation needed for comparison.

Keep an application-owned operation record

The following record is a proposed application convention, not a provider-prescribed schema. Persist it before dispatching the external request and update it transactionally as evidence arrives.

  • Operation identity and intent: a local immutable identifier, actor or workflow reference, and the intended business effect.
  • Request snapshot: the provider route, canonicalized parameter representation or safely stored fingerprint, and the generated idempotency key.
  • Attempt history: dispatch times, transport failures, received status/body, and the most recent recovery decision.
  • Reconciliation evidence: webhook event identifiers, matching provider-object references, operator notes, and a final local disposition.

Use locally meaningful labels such as awaiting_reply, indeterminate_500, confirmed, and stopped_for_review. These labels express application policy; they should not be mistaken for Stripe response states.

Generate the key client-side with sufficient randomness, keep it free of sensitive identifiers, and retain it with the operation record. Stripe permits keys with a maximum length of 255 characters.

Use evidence before choosing a new operation

Network ambiguity and a received 500 require different handling. Stripe characterizes a 500 outcome as indeterminate, and repeating an execution-started request under its existing key commonly returns the stored error rather than resolving whether side effects occurred.

For some 500 cases, Stripe may later reconcile changes and send webhooks for created objects. Supplying a local correlation value in metadata can help connect a later event to the durable operation record, but the application still needs a review path when evidence remains inconclusive.

By contrast, validation failures or concurrent-execution conflicts that stop a request before endpoint execution do not create a stored idempotency result. Once the defect is addressed, the application can decide whether the correction is a new logical operation and issue a fresh key accordingly.

Ambiguous-result recovery decisions

This table is a bounded application policy built on the documented behaviors above. “Stop” means cease automated dispatches and preserve the record for reconciliation or review; it does not assert that the remote outcome is known.

Illustrative recovery matrix for one durable operation
SituationLocal stateEvidence to collectPermitted recovery actionBounded stop
Reply lost after dispatchawaiting_replyOriginal key, immutable request snapshot, and any later responseReuse the original key only with an identical parameter set to seek the prior result.After the local retry window, stop automatic sends and reconcile from retained evidence.
Received HTTP 500indeterminate_500Stored response, correlated events, object references, and local metadata correlation valueDo not create a replacement operation solely because of the 500; reconcile first.At the review deadline, route the unresolved record to an operator or business-specific exception process.
Rejected before executionneeds_correctionValidation or concurrency indication and the corrected intended requestCorrect the defect, then treat changed input as a new logical attempt with a fresh key.Stop when correction requires unavailable input, authorization, or a policy decision.
Key may have been removedretention_boundaryKey age, operation record, prior responses, and reconciliation evidenceDo not assume reuse is safe: after Stripe has removed a key, reuse can initiate another request.Block automatic redispatch and require an explicit duplicate-risk decision.

Respect the retention and error boundaries

Stripe allows automatic idempotency-key cleanup after a key has reached 24 hours of age, without defining a guaranteed deletion moment. If cleanup has occurred, a later use of that key is handled as a new request, which is why local retention and escalation windows should be more conservative than an assumed expiry clock.

A received 4xx response also needs classification rather than a blanket retry rule. An execution-started POST can retain a 400 result for its key, while certain failures can arise before the idempotency layer. When changing the request to fix an error, use a fresh key rather than altering the request beneath the old one.

Make the policy reviewable

Choose and document a finite retry budget, a backoff schedule, a reconciliation deadline, and an escalation owner. Those are application decisions. They should be calibrated to the business cost of a duplicate, delayed completion, and manual review—not presented as guarantees supplied by Stripe.

Review this design if the cited Stripe guidance changes, or if a provider version or integration change affects the request path. The supplied material supports idempotency and error-recovery behavior, but it cannot establish that a particular local state machine, webhook consumer, or reconciliation process is complete for every workflow.