Home › Free tools › Stripe webhook signature checker
Stripe webhook signature checker
Getting No signatures found matching the expected signature for payload? Paste the raw request body, the Stripe-Signature header and the endpoint's signing secret. This page recomputes Stripe's HMAC-SHA256 signature and shows which v1 signatures match and how old the timestamp is.
Nothing you type leaves this browser. All processing happens in this page's own JavaScript. Your inputs are not sent anywhere and not saved: the page makes no network requests (its Content-Security-Policy blocks them), sets no cookies, uses no analytics and loads no third-party scripts or fonts. Once loaded, it works offline. Even so, use a test-mode secret where you can, and roll a live secret if you think it has been exposed.
How Stripe signs a webhook
- Stripe builds
signed_payloadfrom the timestamp, a.character and the raw request body. - It computes an HMAC with SHA-256, using the endpoint's signing secret as the key, and sends it as
v1=along witht=in theStripe-Signatureheader. - Your code recomputes the HMAC and compares it, in constant time, with each
v1value. Ignore other schemes such asv0. - If a signature matches, check that the timestamp is within your tolerance, to stop replayed requests.
Source: Stripe, "Verify webhook signatures manually", docs.stripe.com/webhooks#verify-manually.
Common causes of a failed check
- Parsed JSON instead of the raw body. If your framework parses the body first (for example
express.json()running before the webhook route), re-serialising it changes whitespace or key order, and the signature no longer matches. - The wrong secret. Each endpoint has its own secret, and the test-mode and live-mode secrets for the same endpoint differ. The secret printed by
stripe listen(Stripe CLI) is different from the Dashboard endpoint's secret. - Whitespace or encoding changes. A trimmed or added trailing newline, changed line endings, or a body decoded and re-encoded in something other than UTF-8 all break the match.
- An old timestamp or clock skew. Stripe signs every delivery attempt, including retries, with a fresh timestamp. Replaying a captured request later, or a server clock that is off, puts the timestamp outside your tolerance. Stripe recommends keeping your clock in sync with NTP.
- A mangled header. Pass the header value only, on one line, without the
Stripe-Signature:name.
Sources: Stripe, "Resolve signature verification errors", docs.stripe.com/webhooks/signature, and docs.stripe.com/webhooks#verify-manually.
What this checker doesn't do
- It isn't a replacement for verifying in your server with Stripe's official library.
- Browsers turn pasted Windows line endings (CRLF) in a text box into LF. If the check fails, the page also tries common variants of your body (CRLF, trimmed, re-serialised JSON) and tells you if one of those matches.
- It can't see your server's clock; the timestamp age uses this device's clock.
Fix it in your code, not just here
Webhooks Done Right (for Stripe) (US$19) is an Agent Skill for Claude Code and Cursor. Common Stripe webhook bugs include verifying a parsed body instead of the raw one, using the wrong whsec_ secret, fulfilling an order twice when an event is retried, or returning a 500 for events you don't handle. It includes reference endpoints for Express/TypeScript and Flask/Python. 22 automated tests use locally signed fixtures, not live Stripe deliveries.