# Stripe webhook scan Handlers found: 1 | High: 2 | Medium: 1 | Low: 0 ## app/api/webhooks/stripe/route.ts | Check | Result | Line | Note | | --- | --- | --- | --- | | signature | **FAIL** (high) | 3 | Handles Stripe events without verifying the signature. Anyone who finds this URL can post a fake checkout.session.completed and get your product free. | | raw-body | **FAIL** (high) | 2 | Reads the body as parsed JSON. Signature checks need the exact raw bytes, so verification fails (or was removed to make it pass). | | idempotency | **FAIL** (medium) | | No sign of de-duplicating by event.id. Stripe retries and can deliver the same event twice, so orders, credits, or emails can double up. | | event-types | pass | 3 | Branches on event.type. | Fixes: - signature: Call stripe.webhooks.constructEvent(rawBody, signature, secret) before reading the event, with the secret from your STRIPE_WEBHOOK_SECRET env var. - raw-body: Next.js App Router: const body = await req.text(). Express: express.raw({ type: 'application/json' }) on this route only. Pages Router: export const config = { api: { bodyParser: false } }. - idempotency: Insert event.id into a processed_events table with a unique constraint first; if the insert conflicts, return 200 and stop.