HMAC Generator & Webhook Signature Verifier
Enter a secret and a message to get its HMAC in hex, Base64 and Base64url, or paste the signature a webhook sent and see whether it matches. Presets for GitHub, Stripe, Slack and Shopify build the exact string each service signs. The HMAC is computed by your browser's built-in WebCrypto, so the secret and the payload stay on this page.
read on this device, nothing is uploaded
How to use
- Pick a scheme. Plain HMAC signs the message as given; the GitHub, Stripe, Slack and Shopify presets set SHA-256 and build the exact string that service signs.
- Enter the secret key and say how it is written: plain UTF-8 text (most webhook secrets, including Stripe's whsec_… string), hex or Base64.
- Paste the message, or drop a file to sign its exact bytes. For a real webhook, save the raw request body to a file: copying JSON out of a log or a debugger often changes whitespace or line endings.
- For Stripe and Slack, the timestamp is part of what is signed. Paste the whole Stripe-Signature header and the timestamp is read from it; for Slack, copy X-Slack-Request-Timestamp into the timestamp field.
- Optionally paste the signature you received, then click Compute HMAC. The result appears in hex, Base64 and Base64url, along with the header value the sender would use, and a second status line says whether the signature matches.
Examples
X-Hub-Signature-256: sha256=757107ea0eb2509fc211221cce984b8a37570b6d7586c22c46f4379c8b043e17
5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843
effcdf6ae5eb2fa2d27416d5f184df9c259a7c79
1700000000.{"id":"evt_1"}Supported formats
- Algorithms: HMAC-SHA1, HMAC-SHA256, HMAC-SHA384 and HMAC-SHA512, all computed by the browser's WebCrypto API.
- Key: UTF-8 text, hex (spaces, colons and 0x are ignored) or Base64 / Base64url. Keys longer than the hash block size are hashed first, as HMAC requires.
- Message: text from the box (UTF-8, with LF or CRLF line endings and an option to drop one trailing newline) or the exact bytes of a local file of any type.
- Expected signature: hex, Base64 or Base64url, with or without a sha1=, sha256=, sha512=, v0= or v1= prefix. For Stripe, the full t=…,v1=… header.
- GitHub: X-Hub-Signature-256 is sha256= plus the lowercase hex HMAC-SHA256 of the raw body. The older X-Hub-Signature header is the same with SHA-1 and sha1=.
- Stripe: Stripe-Signature carries t (a Unix timestamp) and one or more v1 signatures. Each v1 is the hex HMAC-SHA256 of t, a dot, and the raw body, keyed with the whole endpoint secret including whsec_.
- Slack: X-Slack-Signature is v0= plus the hex HMAC-SHA256 of v0:{timestamp}:{body}, using the X-Slack-Request-Timestamp header and the app's signing secret.
- Shopify: X-Shopify-Hmac-Sha256 is the Base64 HMAC-SHA256 of the raw body, keyed with the app's client secret.
Privacy
This tool runs entirely in your browser. Nothing you enter or open is sent anywhere. Privacy policy.
Limitations
- Signatures cover exact bytes. The text box gives LF line endings (that is how browsers hand over typed text); a body that was pretty-printed, re-serialised by a JSON parser or saved with a trailing newline will not match. Load the raw body as a file when in doubt.
- Schemes that sign more than the body are not covered by a preset. Twilio, for example, signs the full URL followed by the sorted POST parameters with HMAC-SHA1. You can still build that string yourself and use Plain HMAC.
- Standard Webhooks / Svix style secrets (whsec_ followed by Base64) are keyed with the decoded bytes, not the text; paste the part after whsec_ with the Base64 key format and build the id.timestamp.body string yourself.
- HMAC-MD5 and HMAC-SHA224 are not offered, because WebCrypto does not support them.
- The whole message is held in memory, so files of several hundred megabytes may be slow or fail on phones.
- The timestamp check is informational. Stripe's libraries reject events more than 5 minutes old by default and Slack recommends the same, but this page only reports the age.
FAQ
Why does my webhook signature not match?
Almost always the bytes differ from what the sender signed. Common causes: the body was parsed as JSON and written back out (key order, spaces and Unicode escapes change), a trailing newline was added when saving, Windows CRLF line endings replaced LF, or the wrong secret was used (test-mode and live-mode secrets differ, and each Stripe endpoint has its own). For Stripe and Slack, the timestamp must be the one from the same request. On your server, verify against the raw request body before any parsing.
Is the secret key sent anywhere?
No. The HMAC is computed by crypto.subtle in your browser and the page makes no network request with your key, message or signature. The key is also kept out of the status messages on the page.
What is the difference between hex and Base64 output?
They are two spellings of the same bytes. Hex uses two characters per byte (64 characters for SHA-256), Base64 packs them into 44 characters with = padding, and Base64url swaps + and / for - and _ and drops the padding so it is safe in URLs. GitHub, Stripe and Slack use lowercase hex; Shopify uses Base64.
Why compare signatures in constant time?
A server that stops comparing at the first wrong character answers slightly faster for worse guesses, which in theory lets an attacker forge a signature one byte at a time. Use your language's constant-time compare, such as crypto.timingSafeEqual in Node, hmac.compare_digest in Python or hash_equals in PHP. This page compares the same way, though in a browser it makes no practical difference because you already hold the secret.
Is HMAC-SHA1 still safe?
HMAC-SHA1 has no known practical attack, because HMAC does not depend on the collision resistance that SHA-1 has lost. It is still better to use HMAC-SHA256 for new designs, and GitHub recommends X-Hub-Signature-256 over the older SHA-1 header.
Is HMAC the same as hashing the key and message together?
No. HMAC runs the hash twice with the key mixed in through two padded blocks, which protects against length-extension attacks that break naive constructions such as SHA-256(key + message). The result is not the same as either plain hash.