shipfix

Common errors · Next.js, Express, Supabase Edge Functions

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

Cause The body was parsed as JSON before verification, or the whsec_ secret belongs to another endpoint or mode.
Fix Pass the raw request text to constructEvent and use the signing secret of this exact endpoint and mode.

StripeSignatureVerificationError: No signatures found matching the expected signature for payload

Have it fixed for you, 290 EURSee the fix step by step

What you receive a diff on a branch of your repository, and a correction note: root cause, change made, how to verify it. See a sample report

Payments succeed, but the webhook returns 400 and the order never unlocks. The signature is computed on the exact raw bytes Stripe sent; anything that parses the body first breaks it.

Where it breaks

Stripesigns raw bytesYour routeawait req.json()constructEventre-serialized bodyNo signaturesfound matchingPOSTbodychangedmismatchStripesigns raw bytesYour routeawait req.json()constructEventre-serialized bodyNo signaturesfound matchingPOSTbody changedmismatch
The signature is computed on the exact raw bytes. Parsing the body as JSON first, or using another endpoint's secret, breaks the match.

Which case is yours?

  1. The framework parsed the body as JSON and you pass the re-serialised object to constructEvent.
  2. The signing secret is wrong: test vs live mode, or the secret of the Stripe CLI (stripe listen) used in production. Each endpoint has its own whsec_ value.
  3. The endpoint was never registered in the Stripe dashboard for this mode, so the events you see come from another endpoint.

The fix, step by step

  1. Next.js App Router: read the raw text, never req.json().
    export async function POST(req: Request) {
      const body = await req.text();
      const sig = req.headers.get("stripe-signature")!;
      const event = stripe.webhooks.constructEvent(body, sig, process.env.STRIPE_WEBHOOK_SECRET!);
      // handle event.type ...
      return new Response(null, { status: 200 });
    }
  2. Express: mount express.raw on the webhook route before any express.json().
    app.post("/webhook", express.raw({ type: "application/json" }), handler);
  3. Supabase Edge Function (Deno): use the async variant with the raw text.
    const body = await req.text();
    const event = await stripe.webhooks.constructEventAsync(body, sig, secret);
  4. Copy the signing secret from Developers > Webhooks > your endpoint, in the mode you run (live or test), into the host's environment variables, then redeploy.

How to check it is fixed

In the Stripe dashboard, resend a checkout.session.completed event to the endpoint: it shows 200, and your database row for that order is updated.

Delivery example for this error

app/api/webhook/route.ts · on a branch of your repository- const body = await req.json();+ const body = await req.text();+ constructEvent(body, sig, secret)
Verification checklist in the note
  • Root cause: The body was parsed as JSON before verification, or the whsec_ secret belongs to another endpoint or mode.
  • Change made: Pass the raw request text to constructEvent and use the signing secret of this exact endpoint and mode.
  • In the Stripe dashboard, resend a checkout.session.completed event to the endpoint: it shows 200, and your database row for that order is updated.

Illustrative: the real diff is written against your repository. Full sample report

Still stuck on your own code?

Send the repository and the exact error. Fix: 290 EUR all taxes included, delivered within 72 hours, refunded if not resolved.

Private repository: paste its link anyway, we reply by email within one business day with the GitHub account to invite with read access. A zip link (Google Drive, Dropbox) works too. Never send passwords or secret keys in this form.

Fix: One blocking auth, Supabase RLS or deployment problem fixed in your repository.290 EUR all taxes included · 72 hours

Secure payment by Stripe. Not resolved within 72 hours: refunded in full.

Not sure it is this error? Ask for a free written diagnosis, no card needed.