Webhook signature tester
Compute the HMAC a provider should have sent, compare it with the header you received, and find out which half of the pair is wrong.
The signature covers the request body exactly as received — before any JSON parse or re-serialise.
Expected X-Signature
How to use it
Pick the provider's scheme, paste the signing secret and the exact body you received, and the page shows the signature that body should carry. Paste the header you actually got into the last field and it tells you whether the two agree. When they do not, the fault is in one of three places: the body is not byte-identical to what was sent, the signed string includes something you left out, or the secret belongs to a different endpoint.
The raw-body trap
This is the failure that costs an afternoon. Express with express.json(), FastAPI with a Pydantic model, Rails with its params hash — each one gives you a parsed object and throws the original bytes away. Re-serialising that object produces valid JSON that is not the JSON that was signed. Capture the raw body first: express.raw() on the webhook route, await request.body() in FastAPI, request.raw_post in Rails.
A checklist for the handler
- Verify before you parse, and reject on failure with a 401.
- Compare in constant time.
- Reject timestamps outside a few minutes to stop replays.
- Key on the event id and make the handler idempotent — retries are normal.
- Answer 2xx quickly and do the work in a queue, or the provider will time out and resend.
FAQ
- Why does my webhook signature never match?
- Almost always because the string being signed is not the one that was received. Frameworks parse JSON and hand you an object; re-serialising it changes key order and whitespace, and the digest changes with it. Capture the raw body bytes before any parsing and sign those.
- What exactly gets signed?
- It depends on the provider. GitHub signs the raw body. Stripe signs the timestamp, a dot, and the raw body. Shopify signs the raw body and sends base64 rather than hex. Picking the wrong scheme is the second most common cause of a failed check.
- Should I compare signatures with ==?
- No. Use a constant-time comparison — hmac.compare_digest in Python, crypto.timingSafeEqual in Node, hash_equals in PHP. A normal string compare returns early on the first differing byte, which leaks how much of a guess was right.
- Does a valid signature make the request safe?
- It proves the body was produced by someone holding the secret, and nothing else. You still have to reject old timestamps, or a captured request can be replayed, and you still have to make the handler idempotent, because providers retry.
- Are Virlo webhooks signed?
- Not at present. Virlo delivers webhook events unsigned, so the documented pattern is a long random value in a header of your own that you check on arrival, plus an allowlist if your endpoint can be restricted by IP. This tool is for the signed webhooks you receive from everyone else.
- Is my signing secret sent anywhere?
- No. The HMAC is computed with Web Crypto inside your browser, so the secret and the body stay in the tab. Web Crypto needs a secure context, which is why the page must be on https.
Get the data behind the tool
The Virlo API returns TikTok, Instagram Reels and YouTube Shorts data by the call — hashtags, videos, sounds, creators and trends. Pay per call, no scrapers to maintain.
Create your API key