Webhook Signature Verifier
Check an HMAC webhook signature against your own payload and secret, and find out which detail is wrong when it does not match.
Everything on this page runs in your browser. Nothing you paste is uploaded, logged, or written into the URL — only the settings are, so a configured tool can still be linked to. That matters more here than on most pages: a webhook signing secret is a live credential, and it never leaves this tab or enters the address bar.
HMAC-SHA256 over <body>, hex-encoded, equals the signature you pasted.
362cee32e8301db259bb7057ebd6d6692896e58a80217714516bdd456ba865d2
{"id":"evt_5f2b","type":"invoice.paid","created":1785312000,"data":{"invoice":"in_9931","amount":2500,"currency":"usd"}}- Bytes signed
- 120
- Body bytes
- 120
- Secret bytes
- 35
- Hash
- HMAC-SHA256
- Digest length
- 32 bytes
- Signature candidates found in the header
- 1
- passthe signature matches HMAC-SHA256 over <body>, hex-encoded
The bug is nearly always the body, not the maths
HMAC is signed over bytes, and a web framework's first act is usually to turn those bytes into an object. By the time your handler runs, the body has been parsed and re-serialised, and re-serialising JSON changes it: key order can move, whitespace disappears, 1.0 becomes 1, and a non-ASCII character may come back as an escape. Every one of those changes the digest completely. That is why the sweep above tries a re-serialised body — when that is the variant that matches, the fix is to capture the raw bytes before any parser touches them, which almost every framework supports and almost no example shows.
What a matching signature does and does not prove
It proves whoever built the request had the secret. It does not prove the request is recent, which is why the timestamp is inside the signed string in the better schemes — without it, an attacker who captures one valid request can send it a thousand times. It does not prove the request is the first of its kind, so your handler still needs to be idempotent on the event id. And it says nothing about whether the event it describes actually happened: treat a webhook as a hint that something changed and fetch the current state, rather than as a statement of fact you write straight into your database.