SaaS makers · Checkout retries
Stripe timed out creating a Checkout Session. Should I start a new one?
For the same API v1 creation attempt, retry the unchanged request with its original idempotency key. A worked $19 order shows where that protection ends and why fulfillment needs its own safeguard.
GimmeTheSauce editorial ·

A network timeout leaves the result unknown. If you are retrying the same Stripe API v1 request to create a Checkout Session, reuse its original idempotency key and identical parameters within the key’s retention window. Creating a fresh key can create another checkout for the same purchase. Receiving a session ID tells you a session exists. You still need payment confirmation before delivering the purchase. Stripe’s idempotency rules
Give the purchase an identity before contacting Stripe
Suppose your SaaS sells a $19 pack of 100 export credits. When the buyer starts checkout, create a local order. Store the offer, quantity, currency, one randomly generated operation key and the order’s pending state before calling Stripe. If two workers receive the same order, they must read the same stored key and request parameters. Protect order creation against a double-click too. Two independent local orders with two independent keys can still create two valid sessions.
Put an opaque local order reference in the session metadata so you can reconcile an eventual Stripe event with the order. Keep credentials, customer details and sensitive identifiers out of keys. Persist the returned session ID with the order.
Follow one lost response
Here is a hypothetical sandbox sequence. The two creation attempts happen a minute apart.
| What you observe | What your app should record |
|---|---|
| Order A sends its creation request with key K. The connection drops before the response arrives. | Order pending. Creation outcome unknown. No credits granted. |
| A worker repeats the same request with K and receives session S. | Store S on order A and use its checkout URL. Payment still pending. |
| The buyer pays once. Your server retrieves S and verifies a paid $19 order for the expected pack. | Record the payment and make order A eligible for delivery. |
| Two workers try to fulfill A at the same time. | Commit one grant of 100 credits. Return the existing outcome to the other worker. |
Handle payment events on the server and verify webhook signatures. Retrieve the session and its line items. For this paid pack, match the order, currency, amount and paid status before granting credits. A delayed payment method can complete checkout while payment is still pending. Wait for confirmed success.
For a local credits ledger, one implementation is a unique grant keyed by the order ID. Insert that grant and update the credit balance in the same database transaction. A uniqueness conflict means another worker already inserted it. A read-then-write check without that constraint can let both workers observe “unfulfilled” and each add 100. For external delivery, use a durable delivery job and an idempotent downstream operation too. Stripe explicitly asks integrations to handle repeated and concurrent fulfillment. Stripe’s Checkout fulfillment guide
Know when to stop retrying
- Same request, lost response. Retry with the stored key and bounded backoff. Do not generate one key per HTTP attempt.
- The buyer changes the pack. Changed parameters are a new purchase decision. Reconcile the previous session and whether it is still payable before offering a replacement. Stripe checks parameters when you reuse a key.
- A server error or an old unresolved order. For API v1, the first result after execution begins can be cached, including a 500. Stripe can prune a key once it is at least 24 hours old. Reusing a pruned key starts a new request. Inspect request logs, events and any known session before retrying an old order. Never infer a failed payment solely from your own timeout. Stripe’s network and server error guidance
Test before you put the retry button in front of customers
In a sandbox, have your test worker deliberately discard the first successful session response before saving its ID. Restart the worker and replay from the persisted order. Assert that both responses identify the same session. Complete a test payment and invoke your delivery function concurrently twice. Check the ledger for one paid $19 order, one grant and a balance increase of 100. Repeat with an unpaid session and expect zero credits.
If your checkout is also changing price, rehearse the new purchase and legacy renewal separately. Bring the lost-response trace and database rule to Guillermo Rauch’s AI mentor to review the retry procedure ↗. Use invented order labels rather than live keys or payment details.