HMAC Webhook Signature Playground

Last reviewed on 4 May 2026.

Sign or verify webhook payloads. Pick a known scheme — Stripe, GitHub, Slack, Shopify — or define your own. The tool shows the exact signing string and the resulting signature so you can debug an integration end-to-end. Everything runs in your browser.

Privacy: This tool runs entirely in your browser using the Web Crypto API. The body, secret, and signature are never sent to any server. View source to verify; the implementation is in this single HTML file with no external dependencies.

Input

Each preset fills in the signing rules below. Switch to Custom to override them.
The exact bytes sent in the request body. Whitespace and key order matter — sign and verify must use identical bytes. The shared secret. Treated as UTF-8 bytes for HMAC computation.
Used in schemes that include a timestamp in the signing string (Stripe, Slack). Required when the preset uses one.
Use {body} for the raw body and {timestamp} for the timestamp. Examples: {body}, {timestamp}.{body}, v0:{timestamp}:{body}.

Result

In Sign mode. Switch to Verify to check a signature against expected.

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.