A customer writes in: they paid, and they still can't get in. Stripe shows a successful payment. Your app shows a locked screen.
In apps built with Lovable or Bolt, the fix depends on how the builder connected Stripe, because the two take different routes. This post covers how each one decides a customer has paid, where that breaks, and what to paste back into the builder. Every claim links to the doc it comes from (see Sources at the end).
Step 0: find out which setup you have
Lovable offers two routes. Its docs say "most projects should use built-in payments", where "Lovable provisions the Stripe or Paddle account and manages keys, webhooks, and test and live environments for you." The other route is "Connect your own Stripe account", where you supply the API key. A project "can use one or the other, not both at the same time."
Bolt has a single Stripe integration that "requires Supabase as your database provider". When you apply it, Bolt "automatically creates the Supabase edge functions to handle" checkout session creation, "payment webhook processing", and subscription management.
If you're on Lovable built-in payments, Lovable runs the webhook side, so start in its Payments view. The rest of this post is about the setups where the payment wiring sits in your project: Lovable with your own Stripe account, and Bolt.
Lovable with your own Stripe account: no webhooks by default
Lovable's docs are explicit: "By default, the integration does not use webhooks: your app checks payment and subscription status directly with Stripe."
That gives two common failure modes.
1. One-time purchases granted only on the success page
For a one-time purchase, the buyer pays in Stripe Checkout and then "returns to a payment success page in your app". If the code that grants access only runs when that page loads, a buyer who closes the tab never gets access.
Stripe warns about exactly this: "You can't rely on triggering fulfillment only from your checkout landing page, because it's not guaranteed customers visit that page. For example, a customer can pay successfully and then lose their internet connection before your landing page loads." Stripe's fix is a webhook that calls the same fulfillment function, with the landing page as a second, faster path.
Lovable supports that when you ask for it. It "writes the webhook function, and you finish the setup in your Stripe dashboard": create an event destination for the function's URL, choose the events, and add the signing secret when Lovable asks.
Prompt to paste into Lovable:
Add a Stripe webhook function that handles
checkout.session.completedandcheckout.session.async_payment_succeeded, verifies theStripe-Signatureheader against the signing secret, and grants access through the same function the success page uses. Make that function safe to run twice for the same Checkout Session ID.
2. Subscriptions matched by email
For subscriptions, "subscriptions are matched to users by email address. A user's subscription is recognized when the email they sign in with matches the email on their Stripe customer record." If a customer pays as jane@work.com and logs in as jane@gmail.com, the check finds no subscription.
Check: in Stripe, open the customer record and compare its email with the one the user signs in with. If they differ, change one of them, or ask Lovable to link the Stripe customer ID to the user at checkout rather than relying on the email match.
Bolt: the webhook function exists, so check that it runs
Bolt's docs say it "automatically handles webhook endpoint registration and signature verification. You don't need to manually configure webhook endpoints in your Stripe dashboard." With the plumbing generated for you, the useful question is whether events are actually arriving and succeeding.
Check: in Stripe, open Workbench → Webhooks, select the endpoint, and open Event deliveries. Stripe's docs say this tab lists each event as Delivered, Pending or Failed, along with "the HTTP status code of the delivery attempt".
Then work from the status code.
Reading the status code (both builders)
These apply whenever your webhook is a Supabase Edge Function, which is how Bolt builds it, and how Lovable builds it on your own Supabase project.
401 with "Missing authorization header" or "Invalid Token...": Supabase's troubleshooting guide says these messages "come from the legacy auth verification check", which "runs before your code. When it fails, your function never executes." Stripe sends no Supabase token, so the platform rejects the event. Fix: turn off JWT verification for the webhook function only, then verify the Stripe signature inside it.
The Stripe webhook Edge Function returns 401 before it runs. Set
verify_jwt = falsefor that function only insupabase/config.toml, keep Stripe signature verification withconstructEventAsyncon the raw body (await req.text()), and redeploy.
400 with a signature error: the function runs but the signature doesn't match. The most common cause, per Stripe, is "using the wrong endpoint secret", for example a test secret on the live endpoint (next section).
200, but still no access: the event arrived and your function returned success without granting access. Check that the endpoint subscribes to the event your code handles (checkout.session.completed for one-time payments), and look in the function logs for the database write.
No delivery at all: the live-mode endpoint may not exist, or may not subscribe to that event type.
Test vs live: the go-live trap
Both builders tell you to start with test keys. Bolt: "Use test keys for development. Test keys start with sk_test_." Stripe's docs explain why switching modes takes more than a new key: "Each mode has its own set of API keys, and objects in one mode aren't accessible to the other." Webhook secrets differ too: "If you use the same endpoint for both test and live API keys, the secret is different for each one."
Lovable's go-live steps cover the price side: "Price IDs differ between test and live mode, so ask Lovable to switch your app to the live price IDs." If you added a webhook, also paste in the live endpoint's whsec_ secret. The test one won't verify live events.
Fixing customers who already paid
Once the endpoint works, recover the missed events. Stripe's docs say a Dashboard Resend works "for up to 15 days after the event creation", and the CLI's stripe events resend works for 30. In live mode Stripe also retries failed events "for up to three days". Make sure the grant is safe to run twice before you resend, because Stripe says your fulfillment function "might be called multiple times, possibly concurrently, for the same Checkout Session."
Checklist
- [ ] Know which route you're on: Lovable built-in, Lovable with your own Stripe, or Bolt
- [ ] Access is granted from a webhook, not only from the success page
- [ ] Subscriptions: the login email matches the Stripe customer email (or you match on customer ID)
- [ ] Event deliveries show
200, not401or400 - [ ] Live mode: live key, live price IDs, live endpoint, live
whsec_ - [ ] Granting access twice for the same Checkout Session does nothing extra
WebhookProof's free checker sends 3 harmless requests to your webhook URL and tells you whether Stripe can reach it (401s, redirects, login walls) and whether it accepts unsigned events. Each issue comes with a prompt to paste into your builder. More detail in the guide: Stripe webhooks with Supabase, Lovable and Bolt.
WebhookProof is an independent tool, not affiliated with Stripe, Inc., Lovable, Bolt or Supabase, Inc. Quotes are from the linked documentation as read on 2026-10-18.
Sources
- Lovable, Connect your own Stripe account (built-in vs own account, no webhooks by default, success page, email matching, webhook setup, live price IDs): https://docs.lovable.dev/integrations/stripe
- Bolt, Stripe for payments (Supabase requirement, auto-created edge functions, automatic webhook registration and verification,
sk_test_keys): https://support.bolt.new/integrations/stripe - Stripe, Fulfill orders, hosted Checkout (don't rely on the landing page,
fulfill_checkoutmay be called multiple times, async payment events): https://docs.stripe.com/checkout/fulfillment?payment-ui=stripe-hosted - Stripe, Receive Stripe events in your webhook endpoint (Event deliveries tab, per-mode secrets, retries, resend windows): https://docs.stripe.com/webhooks
- Stripe, Manage webhook endpoints (wrong endpoint secret is the most common signature error): https://docs.stripe.com/webhooks/signature
- Stripe, API keys (sandbox vs live mode, objects not shared across modes): https://docs.stripe.com/keys
- Supabase, Edge Function 401 error response (legacy JWT check runs before your code): https://supabase.com/docs/guides/troubleshooting/edge-function-401-error-response
- Supabase, Function Configuration (
verify_jwt = falseper function): https://supabase.com/docs/guides/functions/function-configuration
Top comments (0)