Encoding & Crypto Runs in your browser

Webhook Signature Verifier — HMAC Checker

Check whether a webhook payload really came from Stripe, GitHub, Shopify or Slack by recomputing its HMAC signature with your signing secret. The digest is computed by your browser's own Web Crypto engine, so the secret never leaves the page.

Secret never leaves your browser Native Web Crypto Four provider presets

Expected signature

 

Signed string

This is the exact string the provider signs. When a signature will not verify, this is usually where the mismatch is — not the secret.

 

What Webhook Signature Verifier does

Your signing secret stays local

The digest is computed by crypto.subtle in your own browser. A webhook signing secret is a long-lived credential for your whole endpoint — pasting one into a server-side tool is a genuine risk, not a theoretical one.

Shows the string that gets signed

Stripe signs {timestamp}.{body} and Slack signs v0:{timestamp}:{body} — not the bare payload. Seeing the exact signed string is usually what explains a mismatch.

Replay window checked

For providers that sign a timestamp, a payload older than the documented tolerance is flagged even when the digest matches — because a real endpoint would reject it.

Presets that match the docs

Each provider's header name, digest algorithm, encoding and prefix are built in, so you are not re-reading four sets of documentation to find out whether it is hex or base64.

Supported formats

The tool reads raw text, so whatever your stack produces will work.

HMAC SHA-256HMAC SHA-1Hex digestBase64 digestStripe-SignatureX-Hub-Signature-256X-Shopify-Hmac-Sha256X-Slack-Signature

How it works

  1. Pick the provider

    Choosing Stripe, GitHub, Shopify or Slack sets the header name, algorithm and signed-string format for you. Two generic presets cover everything else.

  2. Paste the secret and the raw body

    Use the body exactly as received — byte for byte. Re-indenting the JSON changes the bytes and the signature will not match.

  3. Paste the signature header

    The computed digest is compared against the one you were sent, and the verdict appears immediately.

Cheatsheet

The tokens you reach for most, at a glance.

StripeStripe-Signature: t=…,v1=… — signs {timestamp}.{body}, hex
GitHubX-Hub-Signature-256: sha256=… — signs the raw body, hex
ShopifyX-Shopify-Hmac-Sha256 — signs the raw body, bare base64
SlackX-Slack-Signature: v0=… — signs v0:{timestamp}:{body}, hex
ToleranceStripe and Slack both reject payloads older than 5 minutes
Raw bodySign the bytes received, never a re-serialised object

How people use it

Your endpoint rejects a live webhook

Confirm whether the signature is genuinely wrong or your handler is signing the wrong string — the two failures look identical in a log.

Testing a handler locally

Generate a valid digest for a body you control and use it to exercise your verification code before going near production.

Confirming a suspicious payload

An unsigned or wrongly-signed request to a public webhook URL is worth knowing about. Verification is what separates a real event from a forged one.

Frequently asked questions

Is my signing secret sent anywhere?

No. The HMAC is computed by your browser's built-in Web Crypto engine (crypto.subtle) on this page. There is no request and no log. The secret is not stored either — reloading clears it.

The digest does not match. What is usually wrong?

Almost always the body. Frameworks that parse JSON before your handler runs give you a re-serialised object, not the bytes that were signed — a single changed space breaks the digest. Capture the raw body and compare it against the signed string shown here.

Why does Stripe include a timestamp?

To stop replay attacks. Because the timestamp is part of the signed string, an attacker cannot resend a captured payload later without invalidating the signature. Stripe rejects anything more than five minutes old, and this tool flags it.

Why compare digests in constant time?

A plain === returns as soon as two strings differ, and the timing difference can leak the correct signature one character at a time. The comparison here — and the one in your handler — should examine every character regardless.

Which algorithms are supported?

HMAC with SHA-256 and SHA-1, in hex or base64. SHA-1 is included only because some older providers still send it; do not choose it for a new integration.

Can I verify a provider that is not listed?

Use one of the generic presets if the provider signs the raw body with HMAC-SHA256 or SHA-1. If it folds a timestamp or a URL into the signed string, the digest here will not match — check its documentation for the exact format.

Last updated 2026-08-22