HMAC Generator
Reproduce the signature a webhook sender computed, so you can find out whether the mismatch is the secret, the encoding, or the exact bytes being signed.
Runs in your browser
Security & crypto
Signature
Compare against a received signature
Constant-time comparison, so this page cannot leak the answer through timing either.
When the signature does not match
It is almost never the algorithm. In rough order of likelihood:
- 1 The body was re-serialised. Signatures cover the raw bytes. If your framework parsed the JSON and you re-encoded it to verify, key order and whitespace have already changed. Capture the body before any middleware touches it.
-
2
The signed payload is not just the body.
Stripe signs
timestamp.body; others prepend the method and path. Read the sender's documentation for the exact concatenation. - 3 The secret is hex or Base64, not text. A secret displayed as hex must be decoded to bytes before use. Using the printable characters as the key produces a valid-looking signature that never matches.
- 4 Encoding mismatch. Hex against Base64, or uppercase against lowercase. Compare lengths first: SHA-256 is 64 hex characters or 44 Base64 characters.
- 5 A trailing newline. Copying a payload out of a terminal usually adds one, and it changes everything.
In your own code, compare with a constant-time function —
hash_equals() in PHP,
hmac.compare_digest() in Python,
crypto.timingSafeEqual() in Node. A plain
=== leaks the signature one byte at a time.