Webhook HMAC signature verifier
When a webhook signature does not match, paste the body and the secret to find out why: the HMAC is computed in your browser and compared with the received signature. Body and secret are not sent to any server; the calculation uses the browser's own cryptography functions.
The signed text must be byte-for-byte what the receiver got. Line endings, whitespace and a trailing newline change the result. Body: 0 bytes (UTF-8)
The key given by the provider. Prefixes such as whsec_ are often part of the key, or the part after the prefix is base64; check the provider's documentation.
Paste the header value as it is; prefixes such as sha256=, v1= and the t=…,v1=… form are stripped. Hex and base64/base64url are accepted.
Secret and body never leave your device; the calculation runs in the browser and nothing is stored. Still, use a test secret rather than pasting production secrets into any web page.
When verifying
- Always verify the signature over the raw bytes received. If your framework parses the body to JSON and serialises it again, key order, whitespace and escaping can change; capture the raw body before that step.
- On the server compare signatures with a constant-time function, not ==: crypto.timingSafeEqual in Node, hmac.compare_digest in Python. A comparison that exits early can leak the signature through timing differences. This page also compares in constant time, but a result in the browser is not a security boundary.
- A signature alone does not prevent replay attacks. If the provider sends a timestamp, include it in the signature and reject requests outside a tolerance window (e.g. 5 minutes); store event IDs and ignore repeats.
- Do not keep the secret in code or logs; use an environment variable or secrets management and rotate it immediately if you suspect a leak.
This tool only shows the signature calculation and comparison; it does not verify the details of your provider's signature scheme (timestamp concatenation, key prefix, header name). Avoid pasting production secrets into a web page; use a test secret.
Would you like to build your webhook receivers to be secure, replay-safe and observable? We can design signature verification, retries and queues together.
Request a call01
How to use it
A
Paste the raw webhook body and the secret; choose the key format (text, hex, base64) and the algorithm.
B
See the computed signature as hex and base64; if you paste the signature from the header, the match is shown immediately.
C
If it does not match, go through the list of causes: raw byte differences, key format and algorithm are the most common.
02
How a webhook signature works
The sender computes an HMAC from the request body and a secret shared with the receiver and sends it in a header (e.g. X-Hub-Signature-256: sha256=…). The receiver performs the same calculation with its own copy of the secret; if the results are equal, the request came from a party that knows the secret and the body was not changed in transit.
HMAC combines a hash function (SHA-256, SHA-384, SHA-512) with a key; it provides authentication and integrity, not confidentiality. The body is not encrypted, only signed.
03
Calculation and verification
The tool converts key and body to bytes (text as UTF-8) and computes the HMAC with the browser's WebCrypto function crypto.subtle. The result is shown as hex (lower case) and base64. The calculation has been checked against the RFC 4231 test vectors.
The received signature may be hex or base64/base64url; the form whose length matches the output of the algorithm is chosen. The comparison runs over all bytes without stopping at the first difference.
04
Differences between providers
GitHub sends the signature as sha256=<hex> and signs the raw body. Stripe sends t=<time>,v1=<hex> and signs the timestamp and body joined as 't.body'; in that case put the joined text in the body box. Some providers send the signature as base64, some want the key base64 encoded.
When in doubt, enter the example request and the expected signature from the provider's documentation and confirm that the calculation matches; then carry the same steps over to your code.
FAQ
- Is my secret sent anywhere?
- No. The calculation runs on your device with the browser's built-in WebCrypto function; neither the key nor the body is sent to a server or stored. Even so, use a test secret instead of pasting live system secrets.
- Why does the signature not match when I pasted the body exactly?
- The text box turns line endings into LF and can hide invisible characters. If the provider signed the body with CRLF, choose CRLF in the line ending option. A trailing newline and the whitespace inside the JSON must match too. The most reliable way is to log the raw body on your server and copy it from there.
- What do I do if the key is base64?
- Choose Base64 as the key format; the tool decodes the key to bytes first. With some providers such as Stripe the part after the whsec_ prefix is the base64-encoded key: remove the prefix and enter the rest as Base64. Treating the whole text including the prefix as the key gives a wrong result.
- Why a constant-time comparison instead of ==?
- == or === stops at the first differing byte, which can let an attacker guess the signature bytes from response times. A constant-time comparison spends the same time on all bytes. On the server use crypto.timingSafeEqual (Node) or hmac.compare_digest (Python); both inputs must have equal length.
- Which should I choose instead of HMAC-SHA256?
- The one in your provider's documentation. In practice almost all webhooks use SHA-256; SHA-384 and SHA-512 give longer output and appear at some providers. If you are designing a new system, HMAC-SHA256 is sufficient. Older SHA-1 based signatures are not offered in this tool.
Let's build your webhook integrations securely
We design webhook receivers between CRM, ERP, payment and logistics systems with signature verification, replay protection and retries. Let's talk through your current flow in a free discovery call.