Stripe webhook · server log

No signatures found matching the expected signature

what it looks like
StripeSignatureVerificationError: No signatures found matching the expected signature for payload. Are you passing the raw request body you received from Stripe?

What it means

The signature is computed over the exact bytes Stripe sent. If your code parses the body as JSON and stringifies it again, or anything changes whitespace or encoding, the HMAC no longer matches. If the body is untouched, the secret is wrong (see the next entry).

Common causes

  • Calling req.json() and then JSON.stringify() before constructEvent
  • A wrapper, logger or middleware that reads and re-serialises the body
  • The right body but the wrong whsec_ secret

How to fix it

  1. In an App Router route handler, read the body once with await req.text()
  2. Pass that string, the stripe-signature header and STRIPE_WEBHOOK_SECRET to constructEvent
  3. Only JSON-parse after verification (event is already parsed for you)
  4. If it still fails, check which whsec_ secret is in use
src/app/api/stripe/webhook/route.ts
// src/app/api/stripe/webhook/route.ts
import Stripe from "stripe";

export const runtime = "nodejs";

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);

export async function POST(req: Request) {
  const sig = req.headers.get("stripe-signature");
  if (!sig) return new Response("Missing signature", { status: 400 });
  const body = await req.text(); // raw body: never req.json()
  let event: Stripe.Event;
  try {
    event = stripe.webhooks.constructEvent(body, sig, process.env.STRIPE_WEBHOOK_SECRET!);
  } catch (err) {
    return new Response(`Invalid signature: ${(err as Error).message}`, { status: 400 });
  }
  // ...handle event.type...
  return Response.json({ received: true });
}
verify the fix
stripe listen --forward-to localhost:3000/api/stripe/webhook
# put the whsec_... it prints in .env.local, then restart npm run dev
stripe trigger checkout.session.completed

pass if stripe listen prints [200] for every forwarded event, not 400 or 500.

works with stripe-node 23.0.0 · Next.js 16.4.3 App Routerlast verified CLI steps: Stripe CLI 1.55.2, Oct 27, 2026

prevent it · stripe-webhooks.mdc (free Cursor Rules Pack): Reads the body once with await req.text() and verifies it before anything else.

Prompt for Cursor or Claude Code

My Stripe webhook fails with 'No signatures found matching the expected signature'. Open src/app/api/stripe/webhook/route.ts and make sure the body is read once with await req.text() and passed unmodified to stripe.webhooks.constructEvent with the stripe-signature header. Remove any req.json(), JSON.stringify or body-parsing wrapper before verification. Don't change the event handling.

Not quite your error? Paste yours into the free VibeBoot Doctor or browse the Stripe webhook fixer.

next: skip the next one

Skip the debugging. The VibeBoot SaaS Starter ships with this already handled: a signature-verified webhook (raw body + constructEvent) that re-fetches the subscription from Stripe before saving it, with Vitest tests. Plus auth and a database on Next.js 16.4.3. $49, one-time.

14-day money-back guarantee · lifetime updates

$ fixed it? get the next one first

New fixes for what AI agents break, by email.

When we add a fix or a Cursor rule for a new Next.js, Auth.js, Prisma or Stripe break, it goes out in the VibeBoot newsletter. Free, occasional, unsubscribe in one click.

Subscribe free