Skip to content
VibeSafeDaily

LibraryPayments

The Stripe webhook accepts an unsigned body

Cataloged October 16, 2026 · Lovable · Bolt · Cursor · v0 · Replit

Symptom

A payment shows as paid, or a paid feature unlocks, when a request hits the webhook URL without a valid Stripe signature.

Root cause

Stripe documents that every webhook includes a Stripe-Signature header, and that you verify it with the raw body and the endpoint's whsec_ secret. If a framework parses the body first, verification fails or, worse, gets skipped. Supabase's Edge Function guide shows the same pattern: auth: 'none' and verify_jwt = false for Stripe, then constructEventAsync with the raw body before any database work. Lovable's Deep scan groups this under payments: prices, fulfillment, or paid features a customer can change or reach without paying.

Fix prompt

In this project's webhook handler only:

1. Read the request body as raw text. Do not JSON.parse it before verification.
2. Read the Stripe-Signature header and STRIPE_WEBHOOK_SECRET from server env.
3. Verify with the official Stripe library (constructEvent, or constructEventAsync on Deno with a SubtleCryptoProvider) before any unlock or database write.
4. Return 400 when verification throws.
5. Take the price and the product id from the verified Stripe event, not from a client-supplied amount.
6. Keep the webhook secret out of the client bundle.

Do not log the signing secret.

Paste this into the coding tool that has your own project open.

2-minute self-check

These steps are for an app you own. Do not run them against someone else's project.

  1. In your own Stripe dashboard, open the webhook endpoint and confirm a signing secret exists. Test and live secrets are different.
  2. In your own handler, confirm verification runs before any write, and the body is raw.
  3. From the Stripe dashboard, send a test event to your own endpoint. A tampered copy of that request, with the signature header removed, should get 400 and should not unlock anything.

Sources