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.
- In your own Stripe dashboard, open the webhook endpoint and confirm a signing secret exists. Test and live secrets are different.
- In your own handler, confirm verification runs before any write, and the body is raw.
- 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.