Stripe signs every webhook with HMAC-SHA256 over timestamp.payload using your endpoint's signing secret (whsec_...). If your handler rejects the event, it's almost always one of: wrong secret, a modified/re-serialized body (frameworks that auto-parse JSON before you verify), or comparing against the wrong signature scheme (v0 vs v1).
Privacy: this runs 100% in your browser with the Web Crypto API. Nothing you type is sent to any server — you can verify that by checking your browser's network tab while using it.
Check your signature
whsec_.... Copy-pasting the secret from a different endpoint or from test mode into production is the single most common cause.express.json(), or any framework middleware that parses the body before your webhook route sees it, re-serializes the JSON — which changes key order or whitespace and breaks the byte-for-byte signature. You need the raw body, untouched, for the signed route specifically.v0 (legacy) and v1. Stripe's own libraries check v1; a hand-rolled check comparing against v0 will always fail.If this pointed at the problem but you want someone to actually read your webhook handler code and confirm there's nothing else wrong — signature verification is rule #1 of 7 in our standalone checklist and in the full audit.