Free tool

Webhook signature checker

Paste a webhook's raw body, its signature header and the signing secret to see whether they match, and why not when they don't. It works with Stripe, GitHub, Vercel, Slack and EventSend.

Runs in your browser Nothing sent or stored No sign-up

Paste the body exactly as the request carried it, before any JSON parsing.

The check runs in this tab with your browser's Web Crypto API. Nothing you paste is sent to EventSend or anywhere else, and nothing is stored.

Reference

How each provider signs its webhooks

Every scheme is an HMAC of the raw body. What differs is what gets signed alongside it, and where the signature travels.

Stripe
Header
Stripe-Signature
Signed
timestamp.body
Algorithm
HMAC-SHA256, hex
Window
5 minutes by default
GitHub
Header
X-Hub-Signature-256
Signed
body
Algorithm
HMAC-SHA256, hex
Window
None
Vercel
Header
x-vercel-signature
Signed
body
Algorithm
HMAC-SHA1, hex
Window
None
Slack
Header
X-Slack-Signature + X-Slack-Request-Timestamp
Signed
v0:timestamp:body
Algorithm
HMAC-SHA256, hex
Window
5 minutes, as Slack recommends
EventSend
Header
X-EventSend-Signature + X-EventSend-Timestamp
Signed
timestamp.body
Algorithm
HMAC-SHA256, hex
Window
5 minutes in the docs' examples
Stripe secret
The endpoint's signing secret, starting with whsec_. Every endpoint has its own, test and live mode differ, and stripe listen prints its own.
GitHub secret
The secret you set on the webhook.
Vercel secret
The secret shown when an account webhook is created, the integration's Client Secret for integration webhooks, or the drain's signature secret for log drains.
Slack secret
Your Slack app's signing secret.
EventSend secret
Your organization's signing secret, in Organization settings.

Troubleshooting

Why verification fails

A signature covers every byte of the body. Almost every failure is a byte that changed, or a secret that belongs to something else.

The body changed before the check

Frameworks parse JSON early. Serializing it again changes whitespace and can reorder keys, so verify the raw bytes: in Express, register the webhook route before express.json().

The secret belongs to something else

Each Stripe endpoint has its own secret, test and live mode differ, and stripe listen prints a different one again. Using an API key instead of a signing secret fails too.

The timestamp is outside the window

Stripe, Slack and EventSend sign a timestamp to stop replays, and their verifiers reject requests more than about five minutes off. A drifting server clock causes this too: keep it synced with NTP.

Copying added or removed characters

A trailing line break, converted line endings or a space around the secret is enough. The checker tries each of these and tells you when one explains the mismatch.

The header was cut short

Stripe's header carries a t= timestamp and one or more v1= signatures. Pass the whole value to your verifier, not just the signature part.

The body was decoded first

Slack's slash commands and interactivity, and GitHub webhooks set to form encoding, send a form-encoded body. That raw string is what's signed, not the JSON inside it.

Questions

Good questions.

Is it safe to paste my webhook secret here?
The check runs entirely in your browser with the Web Crypto API. The page has no form, sends nothing you paste in any request, and stores nothing, which you can confirm in your browser's network panel. If you'd rather not paste a live secret, use a test-mode secret or rotate the secret afterwards.
What does "No signatures found matching the expected signature for payload" mean?
Stripe's libraries raise it when no v1 signature in the Stripe-Signature header matches the one computed from the body and your secret. Stripe says the most common cause is the wrong endpoint secret; the other is a body your framework changed before verification, such as JSON middleware that ran first. The checker above tests your values and points to the likely cause.
Why does my GitHub webhook signature not match?
GitHub signs the raw request body with HMAC-SHA256, using the webhook's secret, and sends sha256= plus the hex digest in X-Hub-Signature-256. A mismatch usually means a different secret, or a body that was parsed and serialized again. X-Hub-Signature is the older SHA-1 version, kept for backward compatibility.
How does Slack sign requests?
Slack joins the version, the X-Slack-Request-Timestamp value and the raw body with colons, as v0:timestamp:body, signs that with your app's signing secret using HMAC-SHA256, and sends v0= plus the hex digest in X-Slack-Signature. Slack recommends rejecting requests more than five minutes old. Slash commands and interactivity arrive form-encoded, and that raw body is what's signed.
Which secret does Vercel sign webhooks with?
Account webhooks use the secret shown when you create the webhook, integration webhooks use the integration's Client Secret, and log drains use the drain's signature secret. Vercel signs the raw body with HMAC-SHA1 and sends the hex digest in x-vercel-signature.
Do I need to verify signatures if I use EventSend?
Not for the providers EventSend connects: its Stripe, GitHub and Vercel sources verify every request's signature before accepting it. Deliveries EventSend sends to your own webhook are signed too, with X-EventSend-Signature, so your endpoint can verify them the same way.

Skip the plumbing

Let EventSend verify them for you.

EventSend's Stripe, GitHub and Vercel sources check every signature before an event is accepted, then route it to Slack, Discord, Telegram, your own webhook or your phone.