Generate and verify HMAC-SHA-256, SHA-512, SHA-384 & SHA-1 signatures using the Web Crypto API — 100% client-side.
HMAC (Hash-based Message Authentication Code) combines a cryptographic hash function with a secret key:
HMAC(K, m) = H((K⊕opad) ∥ H((K⊕ipad) ∥ m))
. Unlike a plain hash, it provides both integrity and authenticity — only someone with the key can produce or verify the signature.
GitHub, Stripe, Shopify and most major APIs use HMAC-SHA-256 to sign webhook payloads. The platform signs the raw request body with a shared secret and sends the result in a header like
X-Hub-Signature-256
. Your server recomputes the HMAC and compares.
JSON Web Tokens use HMAC-SHA-256 (HS256) to sign the header + payload. The signature is computed over
base64url(header) + "." + base64url(payload)
with a secret key, then appended as the third JWT segment. This tool lets you reproduce that signature manually.
This tool uses
crypto.subtle.verify()
for signature verification, which performs constant-time comparison internally — preventing timing side-channel attacks that could leak whether signatures partially match. Never use string equality (
===
) to compare HMACs in production.
| Platform / Protocol | HMAC variant | What it signs | Header / field name |
|---|---|---|---|
| GitHub Webhooks | HMAC-SHA256 | Raw request body | X-Hub-Signature-256: sha256=<hex> |
| Stripe Webhooks | HMAC-SHA256 | Timestamp + raw body (replay protection built in) | Stripe-Signature: t=<ts>,v1=<hex> |
| Shopify Webhooks | HMAC-SHA256 | Raw request body | X-Shopify-Hmac-Sha256: <base64> |
| AWS Signature V4 | HMAC-SHA256 (chained) | Date → region → service → request — four nested HMACs | Authorization: AWS4-HMAC-SHA256 Credential=... |
| JWT (HS256) | HMAC-SHA256 | Base64URL(header) + "." + Base64URL(payload) | .<signature> — the third dot-segment of the token |
| TOTP / HOTP (2FA) | HMAC-SHA1 (HOTP) or HMAC-SHA256/SHA512 (TOTP) | Counter or timestamp | The 6-digit code is derived from the last 4 bytes of the HMAC output |
| OAuth 1.0a | HMAC-SHA1 | Normalized request string | oauth_signature parameter |
JSON parsers normalise whitespace, reorder keys, and drop unknown fields. If you sign the parsed-then-re-serialised body instead of the original bytes, your HMAC will not match what the sender computed. Buffer the raw request body before it hits any JSON parser. In Express this means express.raw() on the webhook route, not express.json().
A regular string comparison (===, ==) exits early on the first differing byte. An attacker can measure response latency to learn how many characters of their crafted signature match the expected value. Use crypto.timingSafeEqual() in Node, hmac.compare_digest() in Python, hash_equals() in PHP. This is not optional.
An HMAC doesn't expire. If you verify only the signature, an attacker who intercepts a valid signed request can replay it minutes or days later. Stripe's webhook format includes a timestamp in the signed payload and rejects requests older than 5 minutes. Build the same pattern into your own webhook handlers.
The HMAC spec (RFC 2104) says keys shorter than the hash output length provide reduced security. For HMAC-SHA256, use a key that's at least 32 bytes / 256 bits. In practice, use 32 bytes of cryptographically random data. A human-readable string used as a key is likely too short and has low entropy.
HMAC proves who signed a message and that it hasn't been modified. It does not hide the message content. If your payload contains sensitive data, you still need encryption separately. HMAC + plaintext != confidentiality. For confidentiality + integrity together, use AES-GCM or ChaCha20-Poly1305, which include authentication built in.
HMAC requires both parties to share the same secret key. Anyone who can verify an HMAC-signed JWT can also forge one. If you're issuing JWTs to external parties, use RS256 or ES256 (asymmetric) instead — your private key signs, and the public key verifies without being able to forge. HMAC-signed JWTs only make sense for internal, single-service scenarios.
| Property | HMAC-SHA256 | HMAC-SHA512 | HMAC-SHA1 |
|---|---|---|---|
| Output size | 32 bytes / 64 hex chars | 64 bytes / 128 hex chars | 20 bytes / 40 hex chars |
| Security level | 128-bit (collision), 256-bit (preimage) | 256-bit (collision), 512-bit (preimage) | Deprecated for new use |
| Speed on 64-bit CPU | Good | Often faster (larger block = fewer rounds per byte) | Fastest |
| Ecosystem support | Universal — every library, every platform | Universal | Universal (legacy) |
| Use for new work? | Yes — default choice | Yes — prefer for high-value signing | No — only for TOTP/HOTP backward compat |
H((K XOR opad) || H((K XOR ipad) || message)). The double-pass construction closes off length-extension attacks that affect raw SHA-256 and MD5. With a single-pass construction H(key || message), an attacker who knows the hash but not the key can compute a valid hash for any extension of the message. The outer hash with a different key derivation breaks this.HKDF(masterKey, salt, "webhook-signing") produces a deterministic but purpose-specific subkey. Each derived key is independent.