Stripe webhook signature verification
Stripe webhooks are how your app learns that a payment succeeded, a subscription was canceled, or a refund went through. If the endpoint does not verify the signature on incoming requests, anyone can fake those events and unlock paid features without paying.
#What goes wrong
An AI-generated webhook handler often parses the JSON body directly and trusts whatever it receives. The signature header is ignored. A determined attacker can mark themselves as a paid customer with a single curl command.
#Why it matters
Skipping signature verification is the single most common Stripe integration mistake. There are public writeups of attackers using forged checkout.session.completed events to grant themselves lifetime access to paid apps. The fix is a single line, but it has to be there.
#How Heimdall checks for this
Heimdall reads your Stripe webhook handler and checks for a call to stripe.webhooks.constructEvent with the signature header and your webhook secret. It also flags handlers that read the request body before verification, since that breaks signature checking entirely.
#How to fix it
Read the raw request body, pass it to stripe.webhooks.constructEvent along with the Stripe-Signature header and the STRIPE_WEBHOOK_SECRET environment variable, and only act on the event after the call returns successfully. In Next.js App Router, use request.text() to get the raw body, not request.json().
Frequently asked questions
What if I am behind a proxy that modifies the body?
Do I need to handle every event type?
Should webhooks be idempotent?
Run this check on your own repo
Heimdall scans your GitHub repo for this and 16 other issues in under a minute.
