Generate an HMAC
Enter the secret key and the message, and HMAC-SHA256, SHA-384, SHA-512, SHA-1, and MD5 appear together, so there is no algorithm to pick first. Paste the signature a webhook sent to verify it. Your key never leaves this tab.
How to use it#
- Type or paste the secret key. Leave the key format on Text unless the key is meant to be decoded from hex or Base64 first.
- Paste the message exactly as it was signed, such as the raw body of a webhook request.
- Every HMAC appears as you type. Switch between hex and Base64 to match how the signature you have is written.
- To verify a signature, paste it into the compare box. The Signature verdict says whether it matches, and the row it matches turns green.
What an HMAC proves that a hash does not#
A plain hash tells you a message has not changed, but anyone can compute one, so an attacker who alters the message simply hashes it again. An HMAC mixes a secret key into the hash, so only someone holding the key can produce the right value. A receiver with the same key recomputes it and compares, which proves both that the message is intact and that it came from someone who knows the secret.
That is why payment providers, chat platforms, and code hosts sign their webhooks with HMAC-SHA256. The sender puts the HMAC of the request body in a header, and your server checks it before trusting the request. The same construction signs HS256 tokens, which you can build with the JWT generator.
HMAC, defined in RFC 2104, is not simply the hash of the key joined to the message. It hashes twice, with the key padded and mixed in on each pass, which is what makes it safe where a single keyed hash is open to length extension attacks. Use the hash generator when there is no secret involved, such as checking a download.
Checking a webhook signature by hand#
The signature is computed over the exact bytes the sender sent. A body reformatted by a JSON viewer, with its keys reordered or its spacing changed, is a different message and gives a different HMAC, even though it means the same thing. Copy the raw body from your request log, not a pretty-printed version of it.
Many providers sign more than the body. Some join a timestamp and a full stop in front of it, others add the request URL, and a few sign a list of header values. Their documentation gives the exact string. Build that string in the message box and the HMAC will match.
Headers often carry more than the value: an algorithm in front, as in sha256=5d41..., a version and a comma, or a timestamp and several signatures together. Paste the whole header value and the compare box picks out each piece. It accepts hex in either case, Base64, and the URL-safe Base64 a JWT uses, and it tries all five algorithms, so you do not need to know in advance which one the sender used.
Text keys, hex keys, and Base64 keys#
Most services hand you a secret as a string and use that string as it is, its characters encoded as UTF-8. That is the Text setting, and it is the right choice even when the secret happens to look like hex or Base64, because the service signs with the characters rather than with what they would decode to.
A few services document that the secret must be decoded first, such as a key generated as random bytes and written out in hex. The prefix is where people go wrong. A Stripe secret starts with whsec_ and is used whole, prefix included, as Text. Webhooks sent in the Standard Webhooks format also start with whsec_, but there the part after the prefix is Base64 to be decoded. Choose Hex or Base64 only when the documentation says so. Picking the wrong one gives a valid looking HMAC that never matches.
Signatures from GitHub, Stripe, Shopify, and Slack#
GitHub sends an X-Hub-Signature-256 header, which reads sha256= followed by the hex HMAC-SHA256 of the raw body, signed with the secret you set on the webhook. Shopify sends X-Shopify-Hmac-Sha256, the same HMAC of the raw body written in Base64, signed with the app's client secret.
Stripe and Slack sign a timestamp along with the body. For Stripe, the message is the t value from the Stripe-Signature header, a full stop, and the raw body, and the signature is its v1 value, though the whole header can be pasted. For Slack, the message is v0:, the X-Slack-Request-Timestamp value, a colon, and the raw body, and the X-Slack-Signature header is compared as it is. Both use HMAC-SHA256 in hex, with the signing secret as Text.
Frequently asked questions#
Is my secret key sent anywhere?
#
No. The HMAC is computed in this tab by your browser, using its built-in Web Crypto for the SHA family. The key and the message are never transmitted, so a production webhook secret can be tested here without being exposed.
Is my key text, hex, or Base64?
#
Almost always text. A secret shown as a string in a dashboard is used as those characters, even if it looks like hex. Choose Hex or Base64 only when the service says the key must be decoded before use, and include only the part it says to decode, without any prefix.
How do I check a webhook signature?
#
Enter your webhook secret as the key, paste the raw request body as the message, and paste the signature from the request header into the compare box. If the green row names an algorithm, the signature is genuine. If nothing matches, the usual cause is a body that was reformatted, or a provider that signs a timestamp or other values along with the body.
Which algorithm should I use?
#
HMAC-SHA256 for anything new, and for matching almost every webhook. HMAC-SHA512 is equally sound with a longer output. HMAC-SHA1 and HMAC-MD5 are here to match older systems; unlike plain SHA-1 and MD5, they are not broken as HMACs, but there is no reason to choose them for new work.
Why does my HMAC not match the one I was given?
#
Something in the input differs. The most common causes are a trailing newline on the message, a body that was pretty-printed, the key entered in the wrong format, or the wrong algorithm. Check that the message is byte for byte what was signed, including line endings, and that the signature is being compared in the same encoding, hex or Base64.
How do I get the same HMAC in code?
#
Every language has it built in or one import away. In Node, crypto.createHmac with sha256, the key, and the message, then digest as hex or base64. In Python, hmac.new with the key and message as bytes and hashlib.sha256, then hexdigest. In PHP, hash_hmac with sha256. Compare signatures with a constant time check, such as crypto.timingSafeEqual, hmac.compare_digest, or hash_equals, rather than with ==, which leaks how many characters matched. If the value from your code differs from the one here, the input differs, usually by a newline or by the key format.
Can I use an HMAC to store passwords?
#
No. An HMAC is fast by design, which is exactly what a password store must not be. Use a slow, salted password hash instead, which the bcrypt generator makes and checks.