HMAC Verifier Online — Verify SHA-1, SHA-256, SHA-384, SHA-512

Verify an HMAC signature against a message and shared secret. Choose hex or Base64 input, pick the hash algorithm, and confirm with a fixed-iteration compare. Powered by the Web Crypto API — secrets stay in your browser.

Expected hex length: 64
infoThe compare runs a fixed-iteration XOR over the full string for best-effort timing safety. JavaScript engines cannot guarantee true constant-time execution — for production HMAC verification, do it server-side with crypto.timingSafeEqual (Node), hmac.compare_digest (Python), or subtle.ConstantTimeCompare (Go).

Fill in the secret, message, and tag, then click Verify.

What is HMAC verification?

HMAC verification recomputes the HMAC of a message using the same secret and algorithm, then compares the result to a provided tag. A match proves two things: the message was not modified and the signer holds the shared secret. This is how webhook receivers, JWT validators, and signed-URL gateways confirm a request is genuine before processing it.

This verifier uses the browser native crypto.subtle.sign for HMAC-SHA1, HMAC-SHA256, HMAC-SHA384, and HMAC-SHA512, then runs a fixed-iteration XOR comparison — the closest to constant-time JavaScript allows. Everything happens in your browser; the secret never touches our servers.

How to verify an HMAC online — 4 steps

  1. Paste the secret. The same shared secret used to sign the message — for webhooks this is the secret you configured in the provider dashboard.
  2. Paste the tag and message. Strip any prefix like sha256= from webhook headers. The message must be the exact bytes that were signed (webhook raw body, JWT header.payload, AWS canonical request).
  3. Pick algorithm and format. HMAC-SHA256 + hex matches GitHub and Stripe. HMAC-SHA256 + Base64URL matches JWT HS256. HMAC-SHA1 + hex covers Twilio and legacy systems.
  4. Click Verify. Valid confirms the tag matches; Invalid shows the computed value next to your provided value so you can spot the mismatch.

Sample input

Secret:    mysecretkey
Message:   {"event":"order.created","id":42,"amount":1999}
Algorithm: HMAC-SHA256
Format:    hex
Tag:       e7c5f4d2b1a8...   (paste the tag from your X-Signature-256 header)

Result: Valid (if tag matches recomputed HMAC) or Invalid

Equivalent server-side check (Node):
  const expected = crypto.createHmac('sha256', secret)
    .update(rawBody).digest('hex');
  const ok = crypto.timingSafeEqual(
    Buffer.from(expected, 'hex'),
    Buffer.from(provided, 'hex')
  );

Four Algorithms

HMAC-SHA1, HMAC-SHA256, HMAC-SHA384, HMAC-SHA512 — every Web Crypto variant covers webhooks, JWT, AWS SigV4, and legacy interop.

Diff On Mismatch

When tags do not match, both the computed and provided values are shown side by side so you can spot encoding, prefix, or whitespace bugs immediately.

Best-Effort Safe Compare

A fixed-iteration XOR loop walks the entire string regardless of where bytes differ — closest to constant time the browser allows. Production should still verify server-side.

Common use cases

  • check_circleWebhook signing — verify X-Hub-Signature-256 (GitHub), Stripe-Signature, or any HMAC webhook header before processing the payload
  • check_circleJWT HS256 — confirm the third Base64URL segment matches HMAC-SHA256(header.payload, secret)
  • check_circleAPI request authentication — verify HMAC of canonical request (method + path + body + timestamp) before honouring a signed request
  • check_circleAWS S3 presigned URL signature — recompute the StringToSign HMAC to confirm a signed URL is unaltered
  • check_circleGitHub webhook receivers — replicate octokit verification when debugging payloads in staging
  • check_circleStripe webhook receivers — match the t= timestamp + payload HMAC against the v1= scheme in Stripe-Signature
  • check_circleTwilio request validation — verify HMAC-SHA1 over the URL plus sorted form parameters before trusting an inbound webhook
  • check_circleReproducing CI failures — paste the exact body and secret to confirm whether the signature your code generated is actually correct

HMAC vs hash vs digital signature

PropertyHash (SHA-256)HMACDigital Signature (RSA / Ed25519)
Key requiredNoneShared secretPrivate key (asymmetric)
Verifier needsSame inputSame secretPublic key
Detects tamperingYes (if hash trusted)YesYes
Proves identityNoYes (to secret holders)Yes (publicly)
Non-repudiationNoNoYes
SpeedFastest~2× hash100–1000× slower

If you are debugging a signed webhook, JWT, or AWS request — HMAC verification is the right tool. If you need to verify a software release or an SSH session, you need digital signatures (RSA, ECDSA, or Ed25519) instead.

Need to generate an HMAC?

Pair the verifier with the HMAC generator and other crypto-secure tools — all browser-side.

Frequently Asked Questions

What does HMAC verification do?

HMAC verification recomputes the HMAC of a message using the same secret and algorithm, then compares it to a provided tag. If they match, the message has not been modified and was signed by someone holding the secret. This is how webhook receivers, JWT validators, and signed-URL gateways confirm a request is genuine before acting on it.

Is the comparison really constant-time?

No — and we are upfront about it. JavaScript engines intern strings, branch prediction adapts, JIT optimisations vary by call shape, and garbage collection introduces non-uniform pauses. Our compare runs a fixed-iteration XOR loop over the full string length, which is the closest you can get in browser JS, but it is not a substitute for a true constant-time compare. For production verification, do it server-side using crypto.timingSafeEqual (Node), hmac.compare_digest (Python), subtle.ConstantTimeCompare (Go), or the equivalent in your runtime.

Why does my GitHub webhook fail to verify?

Three usual culprits. (1) GitHub signs the raw request body bytes — if your framework parses JSON before exposing the body, the bytes you HMAC are different from the bytes GitHub signed. (2) The X-Hub-Signature-256 header is prefixed with "sha256=" which you must strip before comparison. (3) Encoding mismatches — GitHub uses lowercase hex; do not mix in Base64 or uppercase. Paste the raw body and the hex tag (without the prefix) into this tool to confirm your secret matches.

Hex or Base64 — which should I paste?

Match the format of the system that produced the tag. GitHub, Stripe, and most webhook providers use lowercase hex. JWT HS256 uses Base64URL (which we accept as Base64 — replace - with + and _ with /, and pad with = if needed). AWS SigV4 uses lowercase hex. The dropdown must match the format you paste, or the tag will never validate.

What is the security risk of timing attacks?

A naive string equality (a === b) returns false at the first differing character, so an attacker who can measure response time can probe the tag one byte at a time — this is how the 2010 Boston Symes timing attack on Google Keyczar worked. Constant-time compare always touches every byte regardless of where the mismatch is, so timing leaks no information about which bytes match. The attack is hardest over the public internet (jitter masks microsecond differences) but realistic on local networks and microservices.

Are my secrets uploaded to your server?

No. Verification runs entirely in your browser — the secret, message, and provided tag never touch any server. Open DevTools → Network and click Verify; you will see zero requests fire. The Web Crypto SubtleCrypto API runs in your browser native code, so the computation is fast and private.

Can I verify a JWT here?

Partially. Split the JWT on dots into header.payload.signature. Set message to "header.payload" (the two Base64URL parts joined by a dot, kept as Base64URL — do not decode). Set the provided tag to the third part (the signature). Pick HMAC-SHA256 + Base64. Convert any "-" to "+" and "_" to "/" and pad with "=" to make standard Base64. For full JWT validation including expiry and issuer claims, use a dedicated library like jose.

What if Valid shows but my server rejects it?

Most often a body-encoding mismatch. Webhook providers sign raw bytes (often UTF-8 JSON with no extra whitespace), but your framework may pretty-print, sort keys, or re-encode before re-signing. Always sign and verify the raw request body verbatim. Other causes: clock-skew on time-bound signatures (Stripe attaches a timestamp), header parsing dropping leading "sha256=" prefix, or a stale secret after rotation.

HMAC Verifier Online — Verify SHA-1, SHA-256, SHA-384, SHA-512 Signatures