Hire Lovable Xperts
Integrations & APIs

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.

Lovable Bug Taxonomy — Stripe Webhook Failures
SymptomRoot CauseSelf-fixable?
Card charged, account stays on free tierWebhook never reached server or was rejected with 4xxYes — check Stripe event log for delivery status
Stripe dashboard shows 400 on webhook deliverySTRIPE_WEBHOOK_SECRET wrong, missing, or validation commented outYes — set secret in production env, redeploy
Webhook endpoint returns 500Handler code throws before reaching the account-upgrade logicPartially — check application logs for the exception
Stripe shows successful delivery but account not upgradedBug in handler logic after signature validation — DB write failingYes — add logging after validation to trace the write
No events showing in Stripe webhook logWebhook pointed at Lovable preview URL, not production domainYes — update endpoint URL in Stripe dashboard
SubtleCryptoProvider cannot be used in a synchronous contextWebhook validation using synchronous constructEvent in an async Edge FunctionYes — switch to constructEventAsync
Test events succeed, live events failTest-mode vs live-mode key mismatch in production env varsYes — 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.

  1. Open the Stripe dashboard → Developers → Webhooks.
  2. Click your endpoint and click 'Reveal' next to Signing secret (starts with whsec_).
  3. Add it as STRIPE_WEBHOOK_SECRET to your production environment variables — not just in the Lovable editor.
  4. Verify the variable is available in the deployed environment by adding a health-check log on startup.
  5. Redeploy and trigger a test event from the Stripe dashboard using the 'Send test event' button.
What NOT to do: do not hard-code STRIPE_WEBHOOK_SECRET directly in your source code or commit it to GitHub. Do not copy the secret from a test endpoint to a live endpoint — each Stripe endpoint has its own unique secret. Set it as an environment variable in your production host's settings panel and redeploy after adding it.

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.

  1. In the Stripe dashboard, go to Developers → Webhooks and check the endpoint URL.
  2. Compare it to your actual production domain — they must match exactly, including the path (e.g. /api/stripe/webhook).
  3. If it points to a lovable.app URL, delete it and create a new endpoint pointing at your production URL.
  4. Select only the specific events your app handles — do not use 'all events' unless you have handlers for each.
  5. 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.

  1. In Stripe → Developers → Webhooks, open your endpoint and click 'Send test event'.
  2. Select the checkout.session.completed event type and send it.
  3. Check the response in the Stripe dashboard — a 200 means delivery succeeded.
  4. Check your application logs to confirm the event was received and processed.
  5. 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.

  1. Trigger a real test-mode payment using Stripe test card 4242 4242 4242 4242 in your app.
  2. Immediately check Stripe → Developers → Webhooks → Events for the checkout.session.completed event.
  3. Confirm the event shows HTTP 200 from your server in the delivery log.
  4. Check your application database to confirm the user's plan/tier was updated.
  5. Sign out and sign back in as the test user to confirm the upgraded status persists across sessions.
Use Stripe test mode (STRIPE_SECRET_KEY starting with sk_test_) for all testing. Never run live charges to test webhook configuration. Switch to live keys only after the full test-mode flow is confirmed working.

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?
The payment and the account upgrade are separate events. Stripe charges the card and records a successful payment intent, but your app only upgrades the account when it receives and processes a webhook event. Check the Stripe dashboard under Developers → Webhooks → your endpoint to see whether the event was delivered and what response your server returned. A 4xx response means the webhook was rejected.
Where do I find the webhook signing secret in Stripe?
Go to the Stripe dashboard → Developers → Webhooks, click your endpoint, and look for 'Signing secret.' Click Reveal to see the full value, which starts with whsec_. This is the value that must go into your STRIPE_WEBHOOK_SECRET environment variable in your production deployment — not the Stripe secret key, which is a different credential.
Can I use the Lovable preview URL as my Stripe webhook endpoint?
No. The Lovable preview URL (your-project.lovable.app) is not a stable endpoint — it can change between deploys and it is not reliably accessible by Stripe. Your webhook endpoint must be your real production domain, and it must remain accessible on a consistent path like /api/stripe/webhook.
How quickly can broken Stripe webhooks be fixed?
If the problem is a missing or wrong STRIPE_WEBHOOK_SECRET, or a misconfigured endpoint URL, a fix takes under an hour. If the bug is in the webhook handler code — the logic that reads the event and updates the database — it takes longer to trace and test correctly. Either way, a senior engineer can diagnose the exact problem on the same day.
What does 'SubtleCryptoProvider cannot be used in a synchronous context' mean?
This error appears in Stripe webhook handlers running in Edge Function environments (like Supabase Edge Functions or Cloudflare Workers). The standard Stripe.webhooks.constructEvent() method is synchronous but the Edge Function environment requires async crypto operations. Replace it with await Stripe.webhooks.constructEventAsync() to resolve this. This is a common generation error in Lovable-built Stripe integrations.
My test events succeed but live payments don't trigger the upgrade — why?
This almost always means you are using a test-mode STRIPE_SECRET_KEY in your production environment. Stripe live-mode events are signed with live-mode keys; your server is validating them against test-mode credentials and rejecting them. Verify that STRIPE_SECRET_KEY and STRIPE_WEBHOOK_SECRET in your production environment both start with the live-mode prefix (sk_live_ and whsec_ from the live endpoint).
Should my webhook handler be idempotent?
Yes. Stripe can deliver the same webhook event more than once if your server returns a non-200 response or takes too long to respond. If your handler processes the event twice, you could double-upgrade or create duplicate database records. Store the Stripe event ID and check for it before processing — if the ID already exists in your database, return 200 immediately without processing.
Can I resend failed webhook events from Stripe after I fix the configuration?
Yes. In the Stripe dashboard → Developers → Webhooks, open any failed event and click 'Resend.' This lets you replay historical failed events after fixing the configuration, so customers who paid during the broken period can be retroactively provisioned. For a large backlog of failed events, Stripe also offers a batch resend option via the API.

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.

Get emergency help