What this tool does
It computes HMAC signatures over webhook payloads using the rules from common signing schemes — and shows you the exact signing string used at each step. In Verify mode it does the same computation and tells you whether the signature you provided matches the one it computed.
The use case: you're integrating with a webhook sender, you wrote the verification code, and you're getting "signature mismatch" errors. The tool lets you paste the body and signature you actually received, paste your secret, and see exactly which step disagreed. Most webhook bugs are in the signing-string construction (wrong concatenation, parsed-and-re-serialized JSON instead of raw bytes), not in the HMAC itself.
The schemes built in
Each preset codifies one real-world scheme's signing rules. Understanding the differences is half the value.
Stripe
Header: Stripe-Signature: t=TIMESTAMP,v1=HEX. Signing string: {timestamp}.{body}. Algorithm: HMAC-SHA-256. Output: lowercase hex. The header carries the timestamp in the same string as the signature, separated by a comma. Verifiers should also reject signatures whose timestamp is outside an acceptable window (Stripe's own libraries default to five minutes), to prevent replay of valid old signatures.
GitHub
Header: X-Hub-Signature-256: sha256=HEX. Signing string: the raw request body, with no timestamp. Algorithm: HMAC-SHA-256. Output: lowercase hex with a sha256= prefix. Simpler than Stripe's but does not by itself defend against replay — protection against replay is the receiver's responsibility, typically by storing seen event IDs.
Slack
Header: X-Slack-Signature: v0=HEX with a separate X-Slack-Request-Timestamp header. Signing string: v0:{timestamp}:{body} — note the v0: prefix that's part of the signed bytes (not just a header version marker). Algorithm: HMAC-SHA-256. Output: lowercase hex. Like Stripe, expected to be paired with a timestamp window check.
Shopify
Header: X-Shopify-Hmac-Sha256: BASE64. Signing string: the raw request body. Algorithm: HMAC-SHA-256. Output: base64 — different from Stripe and GitHub. The most common Shopify webhook bug is computing the signature in hex by reflex and getting a mismatch.
Custom
Override any of the rules. Use {body} and {timestamp} placeholders in the signing-string template; pick a hash and an encoding. Useful for in-house webhook schemes that don't quite match any of the public ones.
Things this tool deliberately doesn't do
It computes one signature at a time. It doesn't speak HTTP, doesn't fetch URLs, doesn't store state between page loads. The signature it computes is correct given the inputs you provide; it doesn't try to guess which scheme an external sender used. If you don't know the scheme, the tool can't help you reverse-engineer it from a single signature alone — there's not enough information in 64 hex characters to determine which signing string produced them.
It also doesn't validate the timestamp window. A real verifier should reject signatures whose timestamp is too far in the past (replay defense) or too far in the future (clock-skew exploit). That logic belongs in your verifier, not in this debugging tool.
The mistakes that produce "signature mismatch"
Almost every webhook signature bug falls into one of these:
- Body re-serialized. The receiver parsed JSON into a dict and then re-encoded it for verification. Whitespace, key order, and number formatting all change. Always verify against the raw bytes you received, before any parsing.
- Wrong concatenation. The receiver hashed
{body}when the sender signs{timestamp}.{body}. The HMAC is correct; the signing string isn't. - Wrong encoding. Hex output compared against base64, or vice versa. The bytes are right; the string representation isn't.
- Trailing whitespace or BOM in the secret. Pasted secrets sometimes have invisible characters. Trim before use.
- Wrong character encoding for the secret. Most schemes use the secret as raw UTF-8 bytes; some use it as hex (decoded first). Check the scheme's documentation.
- String comparison instead of constant-time compare. Not a "signature mismatch" symptom but a real vulnerability — variable-time string equality leaks the prefix of the expected signature through timing differences. Use a constant-time compare in production code.
For more context
For the broader webhook design picture — signing, retries, ordering, replay protection, idempotency on the receiver — see Webhook Design and Delivery. For the related pattern of making non-idempotent operations safe to retry, see Idempotency Keys for APIs. For the broader security model that surrounds webhook handling, see API security.