Start watching free

Error library · Paid, but not upgraded

Payment went through but the account was not upgraded (Stripe webhook)

A customer pays. The charge shows in Stripe. Their account still says Free, and they write to you, or ask for a refund.

Why it happens

The checkout page belongs to the payment provider. Your app only learns that someone paid when the provider calls it back, through a webhook. In test mode that was wired to your preview. On the live site the webhook is often not registered for the live address, signed with a different secret, or still pointing at test mode, so the call never arrives or your app refuses it.

Check it in a minute

  1. In Stripe, open Developers, Webhooks, in live mode.
  2. Check there is an endpoint for your live address, and look at its recent deliveries.
  3. Failed deliveries (400 or 500), or no endpoint at all, mean this is it.

How to fix it

Give this to the AI that builds your app: Lovable, Bolt, Cursor, Claude Code or any other.

Customers pay on the live site but their account is not upgraded. I think the payment webhook is not reaching my app, or my app is rejecting it.

1. Find the function that handles the payment provider's webhook. Tell me its live address and which events it expects (for Stripe: checkout.session.completed and the customer.subscription events).
2. Check that it verifies the signature against the raw request body, using a signing secret read from an environment variable. Tell me that variable's name. The live endpoint has its own secret, different from the test one.
3. Check that it finds the user to upgrade from something set at checkout (client_reference_id or metadata), not from the email on the card.
4. Make it answer 200 only after the upgrade is saved, and log a clear error when it cannot find the user.
5. List what I must do in the provider's dashboard in live mode: the endpoint address, the events, and where to copy the signing secret from.

Do not print any secret values. After the fix, a real payment must upgrade the account within seconds.

What Vigilia sees of this

  • The webhook function failing: with server errors connected (Pro), a crash inside the handler, or a 500 it answers with, arrives as an incident with the route and the message. A signature it turns away with a 400 does not, unless the handler reports it.
  • Test and live mixed up: Stripe’s “No such price … exists in test mode” and “Invalid API Key provided” are recognised at once. See those pages.

What it can’t see: If the webhook was never registered for the live address, nothing calls your app, so nothing fails. Vigilia cannot see a call that never happened. The check above, in the provider’s dashboard, is the one that finds it.

Know before your users tell you

Vigilia watches your live app, explains what breaks in plain language, hands your AI the fix, and confirms it held with real visitors.

More from the error library