Home › Guides › No signatures found matching

Stripe webhook error: "No signatures found matching the expected signature for payload"

Matching pack: Webhooks Done Right (for Stripe) (US$19), an Agent Skill for Claude Code and Cursor with reference endpoints for Express/TypeScript and Flask/Python that verify against the raw body and process each event once. Read its test report (36 automated tests (18 Node, 18 Python), all passing, plus a clean TypeScript typecheck; locally signed fixtures, not live Stripe deliveries). Free: Stripe webhook signature checker, which runs in your browser.

Your endpoint receives the event, calls constructEvent(), and gets this error back. Stripe's docs say it means at least one of the three things you passed in is wrong: the request body, the Stripe-Signature header, or the endpoint secret.

The short answer

Source: Stripe docs, "Resolve signature verification errors" (docs.stripe.com/webhooks/signature). Checked 28 Oct 2026.

1. Check the endpoint secret

2. Pass the raw body, not parsed JSON

Stripe's docs list the usual culprits: frameworks that reorder keys, strip whitespace, convert to JSON or change the encoding before your code runs. With Express, app.use(express.json()) must come after the webhook route, because middleware runs in order. Give the webhook route its own raw parser:

// The webhook route gets the raw bytes. Register it BEFORE express.json().
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  let event;
  try {
    event = stripe.webhooks.constructEvent(
      req.body,                          // a Buffer, untouched
      req.headers['stripe-signature'],
      process.env.STRIPE_WEBHOOK_SECRET  // whsec_... for THIS endpoint
    );
  } catch (err) {
    return res.status(400).send(`Webhook Error: ${err.message}`);
  }
  res.sendStatus(200);  // reply fast, then do the slow work
});

// JSON parsing for every other route
app.use(express.json());

In a Next.js App Router route handler, read the body with await req.text(), as Stripe's own example does. For the Pages Router, Stripe's docs say to turn off bodyParser and read the raw buffer.

// app/api/webhooks/route.ts (App Router)
export async function POST(req: Request) {
  const body = await req.text();   // not req.json()
  const sig = req.headers.get('stripe-signature');
  const event = stripe.webhooks.constructEvent(body, sig!, process.env.STRIPE_WEBHOOK_SECRET!);
  // ...
}

Behind AWS API Gateway with Lambda, Stripe's docs describe a body mapping template that passes a rawBody field through to the function. Any proxy or platform that rewrites JSON bodies needs the same kind of fix.

Sources: Stripe docs, "Resolve signature verification errors" (docs.stripe.com/webhooks/signature); stripe-node example, examples/webhook-signing/nextjs/app/api/webhooks/route.ts (github.com/stripe/stripe-node). Checked 28 Oct 2026.

Log the header value you pass. It should look like t=xxx,v1=yyy, possibly with more entries. If it is empty, your code is reading the wrong header name or your proxy drops it.

A related error is a timestamp outside the tolerance zone. stripe-node rejects a signature whose timestamp is more than 300 seconds (5 minutes) from your server's clock by default, which protects against replayed requests. Keep the server clock synced rather than raising the tolerance.

Source: stripe-node source, src/Webhooks.ts, DEFAULT_TOLERANCE 300 seconds (github.com/stripe/stripe-node). Checked 28 Oct 2026.

Quick checklist

Want to check one delivery by hand? Paste the raw body, header and secret into the free Stripe webhook signature checker. It runs in your browser and sends nothing anywhere. Use a test-mode secret.

Let your agent build it the safe way

Webhooks Done Right (for Stripe) (US$19) is an Agent Skill for Claude Code and Cursor. This skill teaches Claude Code or Cursor to avoid them. It includes reference endpoints for Express/TypeScript and Flask/Python, atomic idempotency stores (including Postgres), a Postgres schema tested on a real database, and signed test fixtures. Its tests use locally signed fixtures checked with the real Stripe libraries; live deliveries from Stripe were not used.

See Webhooks Done Right (for Stripe) · Read its test report