Stripe computes the signature over the exact bytes it sent. express.json() parses the body into an object, and turning it back into a string almost never reproduces the same bytes, so verification fails.
Webhook payload must be provided as a string or a Buffer (https://nodejs.org/api/buffer.html) instance representing the _raw_ request body.Payload was provided as a parsed JavaScript object instead. Signature verification is impossible without access to the original signed material.
stripe-node throws this when the signature doesn't match and the body you passed isn't a string, Buffer or Uint8Array. In Express that means req.body is already a parsed object: express.json() (or bodyParser.json()) ran before your webhook route, often through a global app.use(express.json()) registered above it. Older stripe-node versions (v11.0 and earlier, tested) report the same mistake as “No signatures found matching the expected signature for payload”. If you see No webhook payload was provided. instead, req.body is undefined: no body parser ran on the route at all, so add express.raw() as below.
// webhook route FIRST, with raw body
app.post('/webhook', express.raw({ type: 'application/json' }), handler);
// then JSON for everything else
app.use(express.json());
Fixing Express is step one. The guide covers every common Stripe webhook failure with copy-paste fixes. The kit is a self-hosted Cloudflare Worker relay that verifies Stripe signatures, then retries, replays or dead-letters each event instead of dropping it.
Fix Your Stripe Webhooks guide · Self-Hosted Webhook Relay Kit. Code LAUNCH40 = 40% off until Sun Oct 11, 6:40 AM MT.
app.use(express.json({
verify: (req, res, buf) => { if (req.originalUrl === '/webhook') req.rawBody = buf; }
}));
// then: stripe.webhooks.constructEvent(req.rawBody, sig, secret)
Check that req.body (or req.rawBody) is a Buffer before calling constructEvent. If it's an object, the raw body has already been lost.
Python's Stripe library doesn't raise this type error. If you verify a parsed body (a dict or Pydantic parameter, or json.dumps(await request.json())) you just get “No signatures found matching…”. Verify the raw bytes:
@app.post("/webhooks/stripe")
async def stripe_webhook(request: Request):
payload = await request.body() # raw bytes, not a parsed model
event = stripe.Webhook.construct_event(payload, request.headers.get("stripe-signature"), endpoint_secret)
Full FastAPI and Django handlers: stripe.SignatureVerificationError in Python. Using Next.js? See Next.js signature errors.
Free download: Webhook debugging cheat sheet.
Still failing? Paste your payload, header and secret into the free Stripe signature checker. It runs in your browser and tells you which cause it is.
All five common causes with copy-paste fixes: Fix Your Stripe Webhooks guide ($9). Retries, replay and a dead-letter list in front of your endpoint: the self-hosted relay kit ($19).