HMAC and webhook signatures

Runs in your browser; nothing is uploaded

Compute an HMAC (SHA-256, SHA-1, SHA-512) of a message, or check a webhook's signature from GitHub, Stripe, Shopify or Slack against its raw body and secret. Runs in your browser; the secret is never sent.

Raw body

Signature

Nothing yet

Enter the secret and the raw body. Paste the signature header too, to check it.
Ready
Ln 1, Col 1

How webhook signatures work

A webhook is a request another service sends to your server when something happens: a payment succeeds, a commit is pushed. Since anyone can send a request to your webhook URL, senders sign each one. They compute an HMAC of the request body with a secret only you and they know, and put it in a header. You compute the same HMAC and compare.

Each sender has its own recipe. GitHub sends sha256= and the hex HMAC-SHA256 of the body in X-Hub-Signature-256. Stripe signs the timestamp, a dot and the body, and sends t=…,v1=… in Stripe-Signature. Shopify sends the Base64 HMAC-SHA256 of the body. Slack signs v0:timestamp:body. Pick the sender and this tool follows its recipe.

Almost every failed check has the same cause: the body was changed before it was signed. The signature covers the exact bytes received, so the raw body must be used. Parsing the JSON and serializing it again changes spacing and key order, and the signature no longer matches.

How to use this tool

  1. 1Pick the sender, or Plain HMAC to sign any message with a chosen hash and encoding.
  2. 2Enter the secret and paste the raw request body.
  3. 3To check a webhook you received, paste its signature header; the result says whether it's genuine and, for Stripe and Slack, how old it is.
  4. 4Copy the signature, or the Node.js code that checks it on your server.

Troubleshooting

The signature doesn't match
Use the raw body: in Express, express.raw({ type: "application/json" }) for the webhook route, not express.json(). Check the secret is the endpoint's signing secret, not an API key.
The timestamp is too old
Stripe and Slack reject signatures older than 5 minutes so a captured request can't be replayed. Resend the event, and check your server's clock.
GitHub sends two signature headers
Use X-Hub-Signature-256. X-Hub-Signature is the older SHA-1 version, kept for compatibility.

Frequently asked questions

Is my secret sent anywhere?

No. The HMAC is computed with your browser's Web Crypto, and the secret isn't put in share links or saved.

Why compare signatures in constant time?

A comparison that stops at the first different character takes slightly longer the more characters match, which can leak the signature. Node's crypto.timingSafeEqual doesn't.

Can I test my webhook endpoint?

API tests on testingapis.com can send signed requests to your endpoint and check how it responds.