Short answer
Most often, the upgrade only runs on the success page after checkout. Stripe says you can’t rely on that page, because customers don’t always reach it. Grant access from a webhook instead, and check the webhook’s delivery log in the Dashboard for failed deliveries.
Where it breaks, and the fix
01
The upgrade runs only on the success page
If the customer closes the tab or loses connection after paying, the success page never loads and nothing upgrades them. Stripe calls webhooks required for subscriptions and for payment methods that succeed later. Handle checkout.session.completed and check that the session’s payment_status isn’t unpaid before granting access. For delayed methods, also handle checkout.session.async_payment_succeeded. For subscriptions, grant access on invoice.paid once the subscription status is active.
02
The webhook never arrives or fails
Open the endpoint in the Dashboard and look at its event deliveries. Check the endpoint is subscribed to the events you handle, is publicly reachable, accepts POST and doesn’t redirect, since Stripe counts a redirect as a failure. A test-mode endpoint never sees live payments. In live mode Stripe retries a failed delivery for up to 3 days, so fix it and resend the event.
03
The handler can’t tell which user paid, or times out
If the handler can’t match the payment to a user in your database, it has nothing to upgrade. Pass your user ID as client_reference_id (or in metadata) when you create the Checkout Session, and read it back from the event. Return a 2xx response quickly, before slow work, so the delivery doesn’t time out. Stripe can send the same event more than once, so make the upgrade safe to run twice.
Checked against the vendor’s docs on : Stripe docs: Fulfill orders (Checkout); Stripe docs: Using webhooks with subscriptions; Stripe docs: Receive Stripe events in your webhook endpoint; Stripe API: Create a Checkout Session (client_reference_id).
Rather hand it over?
Fixed scope, fixed price, paid upfront. The work happens in writing; no call needed.
- from $450App Fix TicketOne reproducible bug in an existing mobile or web app, diagnosed and fixed.See scope and start
- from $450AI-Built App RescueA Lovable, Bolt or Cursor app checked end to end in 3 days, then its worst problems fixed at a fixed price.No issues worth fixing, no charge. If the Launch Check finds nothing rated medium or higher, Simon refunds the full $450. You keep the report either way.See scope and start
Related: Stripe webhook signature error