HMAC Generator & Webhook Signature Verifier
Compute HMAC-SHA1, SHA-256, SHA-384 or SHA-512 signatures, and debug failing GitHub, Stripe, Shopify or Slack webhooks by checking a received signature against the raw body and your secret.
Private by design: anything you enter is processed in your browser only. It is never uploaded to or logged on our servers. Secret keys are never included in share links, only the payload, settings and received signature.
The signature is calculated over the raw request body, byte for byte. Paste it exactly as received: pretty-printed or re-serialized JSON, re-ordered keys, changed whitespace or a missing trailing newline all produce a different signature. Line breaks typed in the box are sent as \n.
0 bytes
About HMAC and webhook signatures
HMAC (Hash-based Message Authentication Code, RFC 2104) combines a secret key with a hash function such as SHA-256 to produce a tag that proves a message came from someone holding the key and was not modified in transit. Unlike a plain hash from the hash generator, an attacker who changes the message cannot compute a valid new tag without knowing the secret. JSON Web Tokens signed with HS256 use the same HMAC-SHA256 construction; inspect them with the JWT decoder.
Webhook providers use HMAC to sign the requests they send to your server. GitHub sends X-Hub-Signature-256: sha256=<hex> computed over the body. Stripe sends Stripe-Signature: t=<timestamp>,v1=<hex> where the signed payload is the timestamp, a dot and the body. Shopify sends the Base64 encoded HMAC-SHA256 of the body in X-Shopify-Hmac-Sha256. Slack signs v0:<timestamp>:<body> and sends X-Slack-Signature: v0=<hex> together with X-Slack-Request-Timestamp.
The most common reason a signature fails to verify is that the body was parsed and re-serialized by a framework before it was hashed. Always capture the raw bytes of the request body (for example $request->getContent() in Laravel or express.raw() in Node) and compare signatures with a constant-time function such as hash_equals or crypto.timingSafeEqual. Stripe and Slack also include a timestamp so you can reject requests older than a few minutes to prevent replay attacks; the Unix timestamp converter turns it into a readable date.
This tool uses the browser's Web Crypto API, so your payloads and secrets are never sent anywhere. Even so, prefer test-mode secrets when debugging and rotate any production secret that may have been exposed. When you create your own signing secret, use at least 32 random bytes, for example from the secret key generator. HMAC is for message authentication only; store user passwords with the bcrypt generator instead.
Reference Guide
How the major webhook providers sign requests (all use HMAC-SHA256):
| Provider | Header | Signed string | Encoding | Key |
|---|---|---|---|---|
| GitHub | X-Hub-Signature-256: sha256=<sig> | Raw body | Hex | Webhook secret you entered on GitHub |
| Stripe | Stripe-Signature: t=<ts>,v1=<sig> | <ts>.<body> | Hex | Endpoint secret whsec_..., used as-is |
| Shopify | X-Shopify-Hmac-Sha256: <sig> | Raw body | Base64 | App client secret |
| Slack | X-Slack-Signature: v0=<sig> | v0:<ts>:<body>, ts from X-Slack-Request-Timestamp | Hex | App signing secret |
Common reasons a signature does not match:
| Symptom | Cause and fix |
|---|---|
| Fails for every request | A JSON middleware parsed the body and you hashed the re-serialized object. Hash the raw bytes (express.raw(), $request->getContent(), request.get_data()). |
| Fails after switching environment | Wrong secret: test and live endpoints have different secrets, and the Stripe CLI (stripe listen) prints its own. |
| Fails with a correct-looking secret | The secret was hex or Base64 decoded when the provider uses it as plain text, or vice versa. Check the Key Encoding setting. |
| Stripe or Slack only | The signed string must use the timestamp from the request header, not the current time. |
| Equal values reported as different | The sha256= or v0= prefix was not stripped (or added) before comparing, or hex case differs. |
| Fails only for some payloads | Non-ASCII characters: compute the HMAC over UTF-8 bytes, exactly as received. |
Verify a GitHub signature with a constant-time comparison (never use ==):
// Node.js (rawBody is a Buffer)
const crypto = require('crypto');
const expected = Buffer.from('sha256=' + crypto.createHmac('sha256', secret).update(rawBody).digest('hex'));
const received = Buffer.from(req.get('X-Hub-Signature-256') || '');
const valid = expected.length === received.length && crypto.timingSafeEqual(expected, received);
// PHP / Laravel
$expected = 'sha256=' . hash_hmac('sha256', $request->getContent(), $secret);
$valid = hash_equals($expected, (string) $request->header('X-Hub-Signature-256'));
# Python
import hmac, hashlib
expected = 'sha256=' + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
valid = hmac.compare_digest(expected, request.headers.get('X-Hub-Signature-256', ''))
crypto.timingSafeEqual throws when the lengths differ, hence the length check first.
Example input
Preset: GitHub
Secret: demo_webhook_secret
Body: {"action":"opened","number":42,"pull_request":{"title":"Add webhook handler"},"repository":{"full_name":"octo-org/hello-world"}}
Header: X-Hub-Signature-256: sha256=52901a8f8a869d3602c425619d5fc9221f099669b907895c807b06b73c761275
This sample loads automatically when you open the tool. Use Load Sample to bring it back.
Frequently Asked Questions
HMAC (Hash-based Message Authentication Code) combines a secret key with a hash function such as SHA-256 to prove that a message came from someone who knows the key and was not modified.
The most common cause is hashing a re-serialised body instead of the raw request bytes. Whitespace, key order or encoding changes produce a completely different signature. Also check the secret, the algorithm and whether the provider signs a timestamp prefix.
Yes. Signatures are computed with the browser Web Crypto API and the key never leaves your device.
Related Tools
All Security Tools →Bcrypt Hash Generator & Checker
Hash passwords with bcrypt using a configurable cost factor...
PBKDF2 Key Derivation Generator
Derive keys from passwords with PBKDF2-HMAC-SHA1/256/512, ch...
SSH Key Generator & Fingerprint Tool
Generate Ed25519 or RSA SSH key pairs in your browser and ca...
Gherkin Formatter & Validator
Format and validate Gherkin feature files for Cucumber, Spec...
E2E Locator Generator
Paste an HTML snippet and get robust locators for Playwright...
Pairwise Test Case Generator
Generate a minimal set of test cases that covers every pair...