Free tool
Webhook signature checker
Find out why a webhook signature does not match. Paste the secret, the raw body and the signature header to check it, or sign a test payload and send it to your own endpoint with curl.
X-Webhook-Signature: HMAC-SHA256(secret, "<t>.<raw body>"), hex
Result
Enter the secret, the body and the header to check the signature.
Exact string that was signed
Appears once the inputs are filled in.
The HMAC is computed in your browser with the Web Crypto API. Your secret and payload are not sent anywhere.
How to check a signature
- Choose the provider that sent the webhook, or Custom for any other HMAC scheme.
- Paste the signing secret for that endpoint.
- Paste the raw request body, exactly as your server received it.
- Paste the value of the signature header, and for Slack the timestamp header too.
- Read the result. If it does not match, compare the exact string that was signed with what your code signs.
To test your own receiver, switch to Sign a test payload. The tool signs the body with the current time and gives you a curl command that sends it to your endpoint with the right headers.
Supported signature schemes
| Provider | Header | What is signed |
|---|---|---|
| Oxtro | X-Webhook-Signature: t=…,v1=… | <t>.<raw body> with HMAC-SHA256, hex |
| Stripe | Stripe-Signature: t=…,v1=… | <t>.<raw body> with HMAC-SHA256, hex |
| GitHub | X-Hub-Signature-256: sha256=… | The raw body with HMAC-SHA256, hex |
| Shopify | X-Shopify-Hmac-Sha256 | The raw body with HMAC-SHA256, base64 |
| Slack | X-Slack-Signature: v0=… | v0:<timestamp>:<raw body> with HMAC-SHA256, hex |
For schemes that sign a timestamp, the tool also checks that it is within five minutes of now. Rejecting old timestamps stops an attacker from replaying a request they captured.
Why signatures fail to match
- The body was parsed and serialised again. This is the cause nine times out of ten. Frameworks that parse JSON before your handler runs give you an object, and turning it back into a string changes spacing or key order. Read the raw bytes instead, for example with
express.raw()in Express or$request->getContent()in Laravel. - The wrong secret. Each endpoint usually has its own secret, and test and live modes have different ones. A rotated secret also invalidates old requests.
- Encoding mix-ups. Compare hex with hex and base64 with base64. Some providers prefix the signature, such as
sha256=orv0=, which must be removed first. - The timestamp is not part of the signed string. For Oxtro, Stripe and Slack the signed string starts with the timestamp from the header, not only the body.
- Character encoding. Sign the body as UTF-8 bytes. Converting it to another encoding changes non-ASCII characters such as accented letters and emojis.
Verify signatures in your code
Compare signatures with a constant-time function, such as crypto.timingSafeEqual in Node.js or hash_equals in PHP, so the time a comparison takes does not reveal how much of a forged signature was correct. The webhooks guide has a complete Node.js receiver for Oxtro events, and the PHP and Python examples show the same check.
Frequently asked questions
How do I verify a webhook HMAC signature?
Compute an HMAC of the exact string the provider signed, using your signing secret as the key and the same hash function, usually SHA-256. Encode it the way the provider does, hex or base64, and compare it with the signature from the header using a constant-time comparison.
Why does my Stripe webhook signature not match?
Usually because the body was parsed before verification. Stripe signs the raw bytes it sent, prefixed with the timestamp from the Stripe-Signature header. Also check you use the signing secret of that endpoint, which starts with whsec_, and not your API key.
Is it safe to paste my webhook secret here?
The HMAC is computed in your browser with the Web Crypto API, and nothing is sent to a server. For production secrets, prefer testing with a test-mode secret, or rotate the secret afterwards.
Can I test a webhook endpoint without the real provider?
Yes. Switch to Sign a test payload, enter your endpoint and body, and run the curl command the tool builds. Your endpoint receives a correctly signed request.
Made by Oxtro
Receive WhatsApp messages as signed webhooks.
Oxtro posts an event to your server for every WhatsApp message received, every delivery and read receipt, every connection change and every verification code, each signed with HMAC-SHA256 and retried if your server does not answer.
- Incoming messages, delivery and read status, connection and OTP events
- Signed with a timestamp to block replayed requests
- Retried with backoff when your endpoint fails