Skip to main content

Stripe

Lovable Stripe Webhooks Return 401 After Every Redeploy

Lovable redeploys can turn Supabase JWT verify back on. Stripe then gets 401 before your webhook code runs. Fire a signed event at the function URL to prove it.

2026-09-21FetchSandbox Engineering

The Stripe Checkout session completed. The customer row looked fine. The webhook in the Stripe dashboard said 401 Unauthorized.

Supabase Edge Function logs were empty. The handler never ran. In r/lovable this shows up the same way: someone spends a week in chat mode on an SMS-billed SaaS, finally gets verify_jwt off, then Lovable redeploys and the toggle is on again.

That 401 is not a bad constructEvent. Stripe never sends a Supabase JWT. The platform rejected the POST before Deno started.

Short answer

Stripe webhooks must reach a function with JWT verification off. Put verify_jwt = false on that function in supabase/config.toml, and check the same toggle in the Supabase dashboard after every Lovable deploy. Then fire a signed Stripe event at the live function URL. A 401 with no function log is the JWT gate. A 400 from constructEventAsync is a secret or raw-body problem. A 200 that does not unlock the plan is a different post.

Lovable, Bolt, and other AI builders generate the handler. They do not keep the gateway setting stable across deploys.

Why Lovable Stripe webhooks 401

Lovable's Stripe docs say the Edge Function is set up automatically and that you debug in Stripe logs plus Supabase function logs. They also say webhooks are opt-in.

Supabase's own function config is blunt: Stripe does not have your user tokens, so the webhook function must be publicly invokable.

[functions.stripe-webhook]
verify_jwt = false

The official stripe-webhooks example says the same thing in a comment: deploy with verify_jwt = false, then verify Stripe-Signature on the raw body.

On r/lovable the failure is not the first toggle. It is the second deploy. Builders report:

  • JWT turns back on when Lovable updates the function
  • Stripe API versions in generated code drift after a rewrite
  • Deno.env.get("STRIPE_WEBHOOK_SECRET") is undefined even after the secret exists in Project Settings
  • SubtleCryptoProvider cannot be used in a synchronous context if the agent used constructEvent instead of constructEventAsync

Chat mode will rewrite the TypeScript ten times. It cannot see a 401 that never entered the function.

Prove the gate before you burn more credits

Do not ask Lovable "is the webhook working?" Ask the function URL.

  1. Copy the deployed URL: https://<project>.supabase.co/functions/v1/stripe-webhook
  2. Confirm JWT verify is off for that slug after the latest Lovable deploy
  3. Create a FetchSandbox Stripe twin (custom MCP connector or Cursor MCP)
  4. Register that function URL as the twin's webhook destination
  5. Run a checkout on the twin so it POSTs a Stripe-Signature event (t=, v1=)

Read the status, not the chat summary:

HTTP from the functionWhat it meansWhat to change
401 and empty function logsGateway demanded a JWT Stripe does not haveverify_jwt = false again; pin it in config.toml
400 from constructEventAsyncBody or secret is wrongRaw req.text(), STRIPE_WEBHOOK_SECRET actually loaded
200, user still on freeHandler ran; the row the UI reads did notPaid without unlock

FetchSandbox does not flip the Supabase JWT switch. The twin is how you reproduce the inbound POST without waiting on Stripe Dashboard → Send test webhook, and without another Lovable credit loop.

Arm the Stripe twin, POST checkout.session.completed to my function URL,
and tell me the HTTP status plus whether function logs show constructEvent.

A shareable run is the receipt. "Looks wired" is not.

What this does not prove

  • That live Stripe keys are correct
  • That customer.subscription.updated later keeps the role in sync
  • That a valid signature with a stale t= is rejected — that is replayed_old_signed_event on the Stripe twin
  • That preview Checkout works — Lovable still says deploy first for Stripe.js; twins can still be called from Edge Functions, as in the Cozy Closet preview run

For the broader AI-builder loop, start at integration testing for AI-built apps.

Questions about Lovable Stripe webhook 401s

Why does Stripe get 401 on a Lovable webhook?

Because Supabase verified a JWT on the function. Stripe's POST has Stripe-Signature, not a user access token. The request dies at the gateway. Function logs stay empty.

Does Lovable turn JWT back on after a redeploy?

Builders on r/lovable report the dashboard toggle returning to on after Lovable updates the Edge Function. Treat every deploy as a new check of verify_jwt for the webhook slug.

Is turning JWT off unsafe?

For that one function, yes you allow anonymous invoke. Protection is constructEventAsync on the raw body with STRIPE_WEBHOOK_SECRET. Do not disable JWT on user-facing functions.

Can FetchSandbox MCP fix the JWT toggle from Lovable chat?

No. Add the connector to mint the twin and fire the signed POST. You still set verify_jwt = false in Supabase.

Should I keep asking Lovable to rewrite the handler?

Not for a 401 with empty logs. The handler did not run. Fix the gateway, then fire one signed event, then look at 400 vs 200.