This error means the signature you computed from the request doesn't match the v1 value Stripe sent. In almost every case one of these is true:
express.json() (or a framework) parsed and re-serialized it, the bytes changed. Verify against the raw body.whsec_ signing secret, not your sk_ API key.stripe listen) prints yet another one.app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const event = stripe.webhooks.constructEvent(req.body, req.headers['stripe-signature'], process.env.STRIPE_WEBHOOK_SECRET);
res.sendStatus(200);
});Register this route before any global app.use(express.json()).
export async function POST(req) {
const body = await req.text(); // raw text, not req.json()
const event = stripe.webhooks.constructEvent(body, req.headers.get('stripe-signature'), process.env.STRIPE_WEBHOOK_SECRET);
return new Response('ok');
}
payload = request.get_data() # raw bytes, not request.json event = stripe.Webhook.construct_event(payload, request.headers['Stripe-Signature'], endpoint_secret)
Still stuck on this error? 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.
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).