Unverified Stripe Webhook
The Stripe webhook endpoint accepts any POST without verifying the signature header, allowing an attacker to forge subscription events, credit accounts, or cancel subscriptions.
Typical error
Webhook signature not checked
What this is
Stripe signs every webhook request with a secret you configured in the Stripe dashboard. Your endpoint is supposed to verify that signature before trusting the event. Without verification, anyone who knows the endpoint URL can POST fake events.
Consequences:
- Forge
checkout.session.completedand grant a Pro plan for free - Forge
customer.subscription.deletedand cancel another user's plan - Forge
invoice.payment_succeededand trip business logic that depends on real payment
Why AI tools ship this
The generated webhook handler looks at req.body and routes on event.type. It skips the stripe.webhooks.constructEvent() step because that requires reading the raw body, which has framework-specific gotchas.
How to detect
Search for Stripe webhook routes:
grep -rE "stripe.*webhook" --include="*.ts" --include="*.tsx" .Read each one. If it does not call stripe.webhooks.constructEvent() with the raw body and the signature header, it is unverified.
How to fix
Next.js App Router pattern:
import Stripe from 'stripe'
import { headers } from 'next/headers'
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!)
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET!
export async function POST(req: Request) {
const rawBody = await req.text()
const sig = (await headers()).get('stripe-signature')
if (!sig) {
return new Response('Missing signature', { status: 400 })
}
let event: Stripe.Event
try {
event = stripe.webhooks.constructEvent(rawBody, sig, webhookSecret)
} catch {
return new Response('Invalid signature', { status: 400 })
}
// now event is safe to trust
switch (event.type) {
case 'checkout.session.completed':
// handle
break
}
return new Response('ok', { status: 200 })
}Critical: use req.text() to read the raw body. Parsing JSON first breaks signature verification.
Related
- Glossary: auth bypass