Verify a webhook signature
Paste a webhook request and type its signing secret to see whether the signature is right and, if it is not, why: another secret, a body that was laid out again on the way, or a timestamp that has expired. The check runs in your browser; the secret and the request are not sent anywhere.
Webhook Signature Verifier
or drop a file here. It stays on this device.
- Runs on your deviceNo upload. Your text stays in your browser.
- Free, no accountNo sign-up, nothing added to your data. Limits: a body of up to 25 MB, headers of up to 65,536 characters, a time window of 1 to 86,400 seconds.
- FormatsHeaders, raw body and secret in; a verdict with its reasons and a report (JSON) out
- Works onCurrent Chrome, Edge, Firefox and Safari, desktop and mobile.
How it works
- Paste the request's headers, or only the one that carries the signature. A whole request can be pasted too: it is split at its first empty line, and what follows is the body.
- Paste the raw body, or open it from a file when its exact bytes matter: a file is used byte for byte.
- Type the signing secret. The scheme is found from the headers; for a header of your own, open Scheme and time window, choose Another HMAC header and name it.
- Press Verify the signature. The verdict comes with its reasons, the check in detail, the body laid out for reading and a report to download.
Questions
Which schemes are checked, and what does each sign?
Stripe (Stripe-Signature with t= and v1=): the timestamp, a full stop, then the raw body. GitHub (X-Hub-Signature-256 with sha256=): the raw body. Slack (X-Slack-Signature with v0=, and X-Slack-Request-Timestamp): v0, a colon, the timestamp, a colon, then the raw body. Standard Webhooks, which Svix sends (Webhook-Signature or Svix-Signature with v1, and the id and timestamp headers of the same family): the message id, a full stop, the timestamp, a full stop, then the raw body, keyed with the bytes that the base64 text of the secret stands for. Shopify (X-Shopify-Hmac-Sha256): the raw body, with the signature written as base64. All five are HMAC-SHA256. For any other header that carries an HMAC of the raw body, choose Another HMAC header, name it, and pick SHA-256, SHA-1 or SHA-512; there the secret is used as UTF-8 text.
What does Valid mean?
That the HMAC of the signed content, made with the secret you typed, is one of the signatures in the request, written the way the sender writes it, and that the timestamp, where the scheme has one, passes that sender's own rule at the moment of the check. For Stripe, the timestamp is no older than the window: one ahead of this device's clock is accepted, as Stripe's libraries accept it, and the result says it is ahead. For Slack and Standard Webhooks, it is no further from now than the window, older or ahead. Everything else is Not valid, with the reason. Valid says that the sender held the secret; it says nothing more about who that was.
My signature fails although the JSON is the same. Why?
A signature is made over bytes, not over meaning. The same JSON laid out again, with other spaces or line breaks or a final line break, has other bytes and another signature. That happens when a body is copied from a page that shows it formatted, or when a server reads the JSON and writes it out again before it checks. This tool never changes the body: the layout for reading is a separate view (made for a body of up to 2 MB), and the check always uses the bytes as given. When no signature matches, it also tries the body without its final line break (or with one added), with its line breaks written as CR LF, and with the spaces and line breaks between its JSON parts removed, and says which of these the signature belongs to. Such a finding is never a pass.
What happens with an old request?
It is refused as expired, and the result says how old the timestamp is, what the window is, when the request was signed and when it was checked, by this device's clock. It also says whether the signature itself matches, so an expired request can be told from a wrong secret. Stripe's libraries refuse only a timestamp that is too old; Slack's and those of Standard Webhooks also refuse one that is further ahead than the window, and so does this tool. Times are compared in whole seconds. The window is 300 seconds unless you set another value from 1 to 86,400.
Pasted text or a file: which is the body?
One at a time. Text in a page always has LF line breaks and is turned into bytes as UTF-8, so a pasted body is exact when it was sent that way, as a JSON body on one line is. A body with CR LF line breaks, or one that is not UTF-8 text, has to be opened from a file: a file is used byte for byte and is never read from the field, which only shows it. The line under the body field says which body is in use and how many bytes it has. If the request has a Content-Length header that says otherwise, the result points it out.
What is in the report, and what is left out?
A JSON file with the verdict, the reasons and findings, the scheme, the signatures, the timestamp and window, the headers, and the body with its size and SHA-256. The values of Authorization, Proxy-Authorization, Cookie and Set-Cookie headers are left out; their names stay. Ticking the box next to the download puts them into that one report, and the next check starts without them again. The secret is never in the report.
Where does the secret go?
Nowhere. It stays in its field on this page, a password field until you press Show the secret. The page does not write it to the browser's storage or into its own address, and a link to this tool cannot carry, set or show it. It makes the key for the check on this device. Closing or reloading the tab empties the field.
What is not checked here?
Signatures made with a public key, such as Ed25519 and the v1a entries of Standard Webhooks, and schemes that sign the address of the receiver together with the body. Receiving requests at an address, keeping them and sending them again are not part of this tool.
Does it work offline?
Once the page has loaded, yes. Nothing is fetched while you check, so you can disconnect first.
Is my text uploaded?
No. The page loads a small script into your browser and every operation runs on your device. You can check this yourself in the browser's network panel: after the page has loaded there are no upload requests. Closing the tab discards the text.
Do I need an account or to pay?
No. These basic tools are free and add nothing to your data. The only limits are the tool's own: a body of up to 25 MB, headers of up to 65,536 characters, a time window of 1 to 86,400 seconds. Within them, the practical limit is your device's memory.
What does this page keep in my browser?
Nothing you open or type. While the tab is open, the tool pages keep a short list of the steps taken in it, in the tab's session storage: the step (page opened, started, succeeded, first success, used again or failed), the tool's name, the time, how many files were used and, for a failure, an error code; never a file name, the contents of a file or your text. It holds at most the latest 200 steps, and the browser deletes it when the tab closes. The page never sends it. Where a Report a problem button is shown, you can attach the last 20 steps to your report; nothing is attached unless you tick that box. In local storage the page keeps only choices you made: a Light or Dark theme until you choose System again, the language you picked with the EN / 中文 switch if it differs from your browser's language, and this tool in your recently used list if you switched that list on at the tools page.
Questions or problems
Found a problem or missing feature? Tell us. Please describe the steps; do not send confidential data.