Verify Stripe webhook signatures (Next.js, Supabase Edge Functions)
Why an unsigned Stripe webhook lets anyone fake a payment, and how to verify the Stripe-Signature header correctly.
If your webhook handler marks an order paid whenever a POST arrives, anyone who finds the URL can send a fake checkout.session.completed event and unlock your product for free. Stripe signs every webhook; your job is to check the signature before trusting the body.
What to check
- Use the raw request body. Signature verification fails if a framework has already parsed and re-serialized the JSON. In Next.js App Router use
await req.text(); in a Supabase Edge Function (Deno) useawait req.text()too. - Verify with the endpoint secret (
whsec_…) from the Stripe Dashboard for that specific endpoint, stored as a server-side secret, never in the frontend. - Node:
stripe.webhooks.constructEvent(rawBody, sig, secret). Deno/edge:await stripe.webhooks.constructEventAsync(rawBody, sig, secret, undefined, Stripe.createSubtleCryptoProvider()). - Return 400 on failure and do no work.
- Make it idempotent. Stripe can deliver the same event more than once; store the event id and skip repeats.
- Don't trust amounts from the client. Decide what was bought from the Checkout Session / price id on the server.
How to test
Use the Stripe CLI: stripe listen --forward-to localhost:3000/api/stripe/webhook then stripe trigger checkout.session.completed. Then send the same endpoint a plain curl -X POST with a made-up JSON body. It must be rejected.
Audit my Stripe webhook handler. Verify the Stripe-Signature header using the raw request body and the endpoint secret from a server-side env var (constructEvent in Node, constructEventAsync with SubtleCrypto on Deno/edge). Reject with 400 if verification fails, make processing idempotent by event id, and derive what was purchased from the Checkout Session on the server. Show the full diff.
Related: Pre-launch checklist · Hide API keys
Scan your app free
Paste your URL. ShipLatch checks for leaked keys, exposed /.env and /.git, and flags Supabase/Firebase so you know what to lock down. The free scan finds the issues; the $29 Fix Pack gives you fix prompts written for your app and re-scans that prove each fix worked.
Want the whole pre-launch process in one place? Get App Security for Vibe Coders, our $9 PDF playbook.