Lovable Stripe Webhook Not Firing
Stripe charges the card but the user's account stays on free tier — because the webhook that should trigger the upgrade never fired or was rejected. This is the most disorienting payment bug in Lovable apps: the charge is real, the failure is silent. This guide walks the exact checklist: where to look in the Stripe dashboard, what STRIPE_WEBHOOK_SECRET actually does, and the two most common configuration mistakes.
By Founder Name · Last verified: 2026-06-23
Why does Stripe charge the card but the account stays on free tier?
Payment intent creation and account provisioning are two separate steps. Stripe charges the card when the payment intent succeeds, but it is the webhook — specifically the `checkout.session.completed` or `payment_intent.succeeded` event — that tells your app to upgrade the user's account. If the webhook never reaches your server, or reaches it but fails signature validation, Stripe records a successful charge and your app records nothing.
Check the Stripe dashboard under Developers → Webhooks → your endpoint. The event log shows every delivery attempt, the HTTP response your server returned, and the full request and response body. That log is the single most useful debugging tool for this problem. A 4xx response from your server means Stripe delivered the event successfully but your app rejected it.
The most common rejection reason is a wrong or missing `STRIPE_WEBHOOK_SECRET`. Stripe signs every webhook event with this secret, and if your server cannot verify the signature — because the secret is wrong, missing, or the validation is commented out — it returns a 400, and Stripe marks the delivery as failed.
| Symptom | Root Cause | Self-fixable? |
|---|---|---|
| Card charged, account stays on free tier | Webhook never reached server or was rejected with 4xx | Yes — check Stripe event log for delivery status |
| Stripe dashboard shows 400 on webhook delivery | STRIPE_WEBHOOK_SECRET wrong, missing, or validation commented out | Yes — set secret in production env, redeploy |
| Webhook endpoint returns 500 | Handler code throws before reaching the account-upgrade logic | Partially — check application logs for the exception |
| Stripe shows successful delivery but account not upgraded | Bug in handler logic after signature validation — DB write failing | Yes — add logging after validation to trace the write |
| No events showing in Stripe webhook log | Webhook pointed at Lovable preview URL, not production domain | Yes — update endpoint URL in Stripe dashboard |
| SubtleCryptoProvider cannot be used in a synchronous context | Webhook validation using synchronous constructEvent in an async Edge Function | Yes — switch to constructEventAsync |
| Test events succeed, live events fail | Test-mode vs live-mode key mismatch in production env vars | Yes — verify STRIPE_SECRET_KEY is the live key in production |
What is STRIPE_WEBHOOK_SECRET and why does it matter?
STRIPE_WEBHOOK_SECRET is the signing secret Stripe generates for each webhook endpoint. When Stripe sends an event, it includes a `Stripe-Signature` header. Your server uses the webhook secret to verify the signature and confirm the event genuinely came from Stripe. If the secret is wrong or missing, every webhook event fails with a 400 error — and Stripe stops retrying after several failures.
- Open the Stripe dashboard → Developers → Webhooks.
- Click your endpoint and click 'Reveal' next to Signing secret (starts with whsec_).
- Add it as STRIPE_WEBHOOK_SECRET to your production environment variables — not just in the Lovable editor.
- Verify the variable is available in the deployed environment by adding a health-check log on startup.
- Redeploy and trigger a test event from the Stripe dashboard using the 'Send test event' button.
Is the webhook pointed at the right URL?
A common mistake in Lovable-built apps is setting the Stripe webhook endpoint to the Lovable editor preview URL — the xxx.lovable.app address — instead of your real production domain. The preview URL is not a stable endpoint and is not reliably accessible by Stripe. When you deploy to a custom domain, the webhook must be updated to point at the new URL.
- In the Stripe dashboard, go to Developers → Webhooks and check the endpoint URL.
- Compare it to your actual production domain — they must match exactly, including the path (e.g. /api/stripe/webhook).
- If it points to a lovable.app URL, delete it and create a new endpoint pointing at your production URL.
- Select only the specific events your app handles — do not use 'all events' unless you have handlers for each.
- Copy the new signing secret and update STRIPE_WEBHOOK_SECRET in your production environment.
How do I test that the webhook is actually working?
The Stripe dashboard allows you to resend any past event or send a new test event to your endpoint. Use this to verify the full round-trip: Stripe sends the event, your server receives it, validates the signature, updates the user's account, and returns a 200 response. A 200 in the Stripe log but no account upgrade means the bug is in the handler logic after signature validation.
- In Stripe → Developers → Webhooks, open your endpoint and click 'Send test event'.
- Select the checkout.session.completed event type and send it.
- Check the response in the Stripe dashboard — a 200 means delivery succeeded.
- Check your application logs to confirm the event was received and processed.
- If delivery succeeds but the account is not upgraded, add a log immediately after signature validation to confirm the event type and data your handler is receiving.
How do I confirm the payment flow is fully working end to end?
A working Stripe webhook requires three things to succeed independently: the correct endpoint URL pointing at your production domain, a valid STRIPE_WEBHOOK_SECRET set in the production environment, and correct handler logic that reads the event payload and updates your database. A test event that returns 200 only confirms the first two — the third requires an end-to-end payment simulation with a real test card.
- Trigger a real test-mode payment using Stripe test card 4242 4242 4242 4242 in your app.
- Immediately check Stripe → Developers → Webhooks → Events for the checkout.session.completed event.
- Confirm the event shows HTTP 200 from your server in the delivery log.
- Check your application database to confirm the user's plan/tier was updated.
- Sign out and sign back in as the test user to confirm the upgraded status persists across sessions.
When should I get a senior engineer involved?
If the Stripe dashboard shows successful webhook deliveries but payments are not provisioning correctly, the bug is in the application logic — the code that reads the webhook payload and updates the database. This code is often the most complex in a Lovable-built app and the hardest for the AI to generate correctly.
Payment infrastructure bugs have a direct revenue impact: every failed webhook is a customer who paid but did not receive what they bought. These are worth fixing fast and correctly, not iterating on with more prompts.
A senior engineer can trace the event from Stripe delivery through to the database write and identify the exact point of failure — whether it is a missing idempotency check, a race condition in the session update, or an async/await bug in the Edge Function handler.
Frequently asked questions
Stripe says the payment succeeded but my app still shows the free plan — what's wrong?
Where do I find the webhook signing secret in Stripe?
Can I use the Lovable preview URL as my Stripe webhook endpoint?
How quickly can broken Stripe webhooks be fixed?
What does 'SubtleCryptoProvider cannot be used in a synchronous context' mean?
My test events succeed but live payments don't trigger the upgrade — why?
Should my webhook handler be idempotent?
Can I resend failed webhook events from Stripe after I fix the configuration?
App down or leaking data? Get an expert on it within 24–48h.
Book a free 30-minute audit call. We'll diagnose what's wrong and tell you exactly what it costs to fix.