Stripe checkout.session.expired: rebuild the session, do not retry it
Guide Stripe checkout.session.expired: rebuild the session, do not retry it: Check # Stripe checkout.session.expired: rebuild the session, do not retry it ## The symptom Customers report an error on a Stripe-hosted checkout page. Use this when you hit exactly this in Stripe checkout.session.expired. Not for different errors or different tools.
TL;DR: Check # Stripe checkout.session.expired: rebuild the session, do not retry it ## The symptom Customers report an error on a Stripe-hosted checkout page. Your logs show
Stripe checkout.session.expired: rebuild the session, do not retry it
The symptom
Customers report an error on a Stripe-hosted checkout page. Your logs show checkout.session.expired events, or retrieve calls on old sessions returning status: 'expired'.
Confirm the cause
A session expires at expires_at, default 24 hours after creation. Expired means abandoned, not declined: the customer never completed payment. Check session.expires_at vs now. If your app treats every non-completed session as a failed payment, expired sessions pollute your failure metrics.
The fix
An expired session is dead. You cannot reopen it; create a new one:
if (event.type === 'checkout.session.expired') {
const session = event.data.object;
await db.orders.markAbandoned(session.client_reference_id, { reason: 'expired' });
await sendRecoveryEmail(session.customer_email, buildNewCheckoutLink(session));
}expires_at rules:
- You can set a custom expiry between 30 minutes and 24 hours. Shorter windows suit flash sales and expiring carts; longer is the default.
- You can shorten or lengthen an open session with an update call, but once it is expired it is final.
Two pipeline details:
- The
expiredevent is not enabled on every webhook endpoint by default. Addcheckout.session.expiredin the dashboard webhook settings or you will never see it. - Log expired and failed payments separately. Expiry is a marketing/recovery event (send a fresh link). Failure is a payments event (fix the payment method). Mixing them sends the wrong email.
Verify the fix
Create a session with expires_at 30 minutes out, wait it out (or simulate via the event), and confirm your handler marks the order abandoned and the recovery email contains a fresh working session link. Confirm the old session id returns expired and cannot be reused.
When to use
You hit exactly this: Stripe checkout.session.expired: rebuild the session, do not retry it in Stripe checkout.session.expired.
When not to use
A different error, or the same symptom in a different tool. This page only covers the failure above.
Compatibility
Stripe checkout.session.expired.