Guides · Free check · $9 fix pack

Guides

Supabase Edge Function 401 on your Stripe webhook

83-second walkthrough, captions on. The failing report shown comes from a local demo endpoint, labeled DEMO ENDPOINT on screen. MP4 · Captions (VTT)

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.

Why it happens

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).

Fix 1: turn off the JWT check for the webhook function only

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).

Fix 2 (not optional): verify the Stripe signature

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.

Still 401 after that?

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.

Check yours in 10 seconds (free).

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 check

FAQ

Why does my Supabase Edge Function return 401 to Stripe?

Edge 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.

How do I turn off JWT verification for one Supabase function?

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.

Is it safe to disable verify_jwt for a Stripe webhook?

Only if the function verifies the Stripe-Signature header with your whsec_ secret and returns 400 when verification fails. Otherwise anyone can call it.

Sources

More guides

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.