Check whether a webhook really came from Stripe, GitHub, Slack, Shopify, Paddle, Lemon Squeezy, the Telegram Bot API, Twilio, Square or HubSpot. Paste the raw body, the request headers and your signing secret — the provider's own scheme is recomputed with Web Crypto in your browser and compared against the signature it sent.
{{ p.note }}
Must be the exact bytes your server received. Re-serialising parsed JSON changes whitespace and key order, and the signature will no longer match.
This exact text is sent as the raw request body — the signature is computed over these bytes.
{{ headersError }}
Leave empty to just build the signed header and copy the send code below. If filled, "Send" POSTs directly from your browser — most receivers block this via CORS, so a failed send here does not mean the signature is wrong.
This must be the exact URL the provider signed — the configured notification/webhook URL, not necessarily the URL a proxy or path rewrite made the request arrive on.
Classic Twilio webhooks (voice/SMS status callbacks) — the body above must be the exact application/x-www-form-urlencoded bytes Twilio sent, and every parameter is part of the signature.
JSON payloads sign the URL alone — paste the URL exactly as received, including its ?bodySHA256=… query parameter; the JSON body itself is not re-hashed here.
Paste a body, headers and the signing secret. Verification runs automatically.
{{ result.reason }}
Compared against this machine's clock. A stale timestamp does not mean the signature is wrong — an archived or replayed payload verifies but should still be rejected in production.
Enter a payload and secret, then Build or Send.
{{ sendResponse.error }}
A webhook endpoint is a public URL, so anyone can POST to it. Most providers on this page solve that the same way: they hash the request body (or body plus a bit more) with a shared secret and put the digest in a header. Your server recomputes the digest and rejects anything that does not match. This tool does that recomputation in the browser so you can tell a genuine delivery from a forged one — and, more often, work out why a delivery you know is genuine keeps failing verification.
The usual culprit is the body. Frameworks that parse JSON before your handler runs hand you an object, and re-serialising it changes whitespace or key order, which changes the digest. You need the raw bytes: express.raw(), request.get_data(), file_get_contents('php://input'). The second culprit is the signed string: GitHub, Shopify and Lemon Squeezy sign the body alone. Stripe signs timestamp.body, Slack signs v0:timestamp:body, and Paddle signs timestamp:body. Twilio and Square go further and sign the request URL too — Twilio's form-encoded webhooks sign url + sorted-params, its JSON webhooks sign the URL alone with the body's hash folded into a query parameter, and Square signs url + body outright — get the URL wrong (a proxy rewrote the path, or you pasted the wrong environment's URL) and the digest is wrong even with the right secret and an untouched body.
HubSpot is the other outlier: v1 and v2 are a plain SHA-256 hash of the client secret glued onto the signed string, not a keyed HMAC at all — cryptographically weaker, which is why v3 (HMAC-SHA256, plus a required timestamp) is the one to prefer for a new app. That timestamp is also the one place on this page measured in milliseconds instead of seconds — get the unit wrong and the 5-minute replay window silently accepts or rejects everything.
The Telegram Bot API entry is deliberately different again. Telegram does not hash anything: setWebhook takes a secret_token and echoes it back verbatim in X-Telegram-Bot-Api-Secret-Token, so verification is a constant-time string comparison. It is listed here because it is the same job — proving a webhook is authentic — not because it shares the algorithm.
Privacy: signing secrets and bot tokens are processed with the Web Crypto API in your browser and never leave it. The one exception is the "Send test webhook" button — it POSTs the built payload and signature headers directly from your browser to whatever target URL you enter, exactly like a real webhook delivery would; nothing routes through this site. Nothing is stored between sessions.