Your Stripe event deliveries show 401, and the response body is {"code":401,"message":"Missing authorization header"} or an "Invalid JWT" message. Your function code never ran.
Supabase: "By default, Edge Functions require a valid JWT in the authorization header" (Supabase docs). Supabase's 401 guide says both the "Missing authorization header" and invalid-token messages "come from the legacy auth verification check", which "runs before your code. When it fails, your function never executes" (Supabase docs). Stripe doesn't have your user tokens, so it can't pass that check. Stripe lists 401 and 403 as access restrictions on the destination URL (Stripe docs).
In supabase/config.toml, as in Supabase's own config docs (source):
[functions.stripe-webhook]
verify_jwt = false
Or deploy with the CLI flag (Supabase CLI):
supabase functions deploy stripe-webhook --no-verify-jwt
Supabase's 401 guide also lists turning JWT verification off in the function's details in the Dashboard (source).
Supabase warns that turning the check off "will allow anyone to invoke your Edge Function without a valid JWT" (source). Supabase's Stripe example handles this by verifying the signature with the raw body; its source says "Stripe verifies the request via its signature, so deploy with verify_jwt = false" (Supabase example source):
import Stripe from 'npm:stripe'
const stripe = new Stripe(Deno.env.get('STRIPE_API_KEY') as string)
const cryptoProvider = Stripe.createSubtleCryptoProvider()
Deno.serve(async (req) => {
const signature = req.headers.get('Stripe-Signature')
const body = await req.text() // raw body, not req.json()
let event
try {
event = await stripe.webhooks.constructEventAsync(
body, signature!, Deno.env.get('STRIPE_WEBHOOK_SIGNING_SECRET')!, undefined, cryptoProvider
)
} catch (err) {
return new Response(err.message, { status: 400 })
}
// handle event.type, then return 200 quickly
return Response.json({ ok: true })
})
Adapted from Supabase's example, which wraps the same logic in its own server helper; check the linked source for the current version.
If the body is a message you wrote (or empty), your own code returned the 401. Supabase says to search your function for 401 and trace the condition (source). Building with Lovable or Bolt? See the Supabase guide for AI-built apps.
Paste your webhook URL. WebhookProof sends 3 harmless test requests and tells you whether Stripe can reach it and whether it rejects unsigned events. If it finds a problem, the $9 fix pack gives you the exact copy-paste fix for your stack (refundable for 14 days).
Run the free checkEdge Functions require a valid JWT by default, and Stripe cannot send one. The "Missing authorization header" 401 comes from Supabase's built-in check before your code runs.
Set verify_jwt = false under [functions.your-function-name] in supabase/config.toml, or deploy with supabase functions deploy your-function-name --no-verify-jwt.
Only if the function verifies the Stripe-Signature header with your whsec_ secret and returns 400 when verification fails. Otherwise anyone can call it.
Stripe webhook not working? Check these 7 things in order · A customer paid but didn't get access. What went wrong? · No signatures found matching the expected signature for payload · Stripe webhook failing with a 307 or 308 redirect
Last reviewed 2026-10-18. WebhookProof is an independent tool, not affiliated with Stripe, Inc.. Quotes are from the linked documentation.