Webhook Signature Lab
This local lab uses its own documented, provider-neutral profile: UTF-8 decimal Unix seconds, one ASCII dot, then the exact body bytes are authenticated with HMAC-SHA256. Its header is t=<seconds>,v1=<64 lowercase hex>. Text mode encodes the textarea as UTF-8; choose hex or a local file when CRLF or arbitrary bytes must be exact. Verification checks the MAC, ± time window and a small in-tab seen set. It is a learning/debugging profile, not a Stripe/GitHub-compatible header or a production webhook endpoint.
Key features
- Sign exact text, hex or local file bytes with Web Crypto HMAC-SHA256
- Strict timestamped header and signing-input byte count/preview
- Verify a 32-byte digest using a fixed-length byte loop without content-dependent early exit
- Distinguish tampering, expired timestamp, excessive future clock and repeated valid header in this tab
- Keep the secret in page memory only, with no local storage, form submission or API call
How to use
- Load the demo or choose text, hex or a local body file, then enter a temporary UTF-8 or hex key.
- Leave the signing timestamp blank for current Unix seconds or enter a fixed test value; click Sign.
- Copy or inspect the resulting t=...,v1=... header and exact timestamp-dot-body input rule.
- Verify with the same raw bytes and key; optionally set test clock and tolerance.
- Edit one body byte, advance the test clock or verify twice to inspect mismatch, expiry and in-tab replay.
Use cases
- Reproduce a webhook signature mismatch caused by CRLF versus LF
- Test the 300-second tolerance boundary against a fixed clock
- Demonstrate why a valid MAC alone does not stop in-window replay
- Inspect the exact byte prefix a receiver should authenticate
Frequently asked questions
Does this match a particular webhook provider?
No. This tool defines its own strict t=<Unix seconds>,v1=<lowercase hex> profile and authenticates ASCII timestamp + '.' + raw body bytes. Real providers may use different headers, encodings and replay rules. Follow that provider's official specification in production.
Why can pasted JSON verify differently from the received request?
Signatures cover bytes, not parsed JSON meaning. Spaces, key order, encoding and CRLF/LF matter. Browser textareas normalize line endings; upload the original binary or use hex mode to reproduce received bytes exactly.
Is a timestamp enough to stop replay?
No. The ± window rejects old/far-future deliveries, but a valid message can be resent inside it. This page remembers accepted headers in one tab, up to 256 entries; closing or clearing the tab loses them. A real receiver needs a shared durable event-ID or signature cache with expiry.
Is the comparison truly constant time?
The digest loop always checks 32 byte positions without stopping at the first differing byte. JavaScript engines and browsers do not guarantee physical constant-time execution; do not treat this demo as a side-channel certification.
Can I sign an empty body?
Yes. The signed input is still the timestamp and dot, followed by zero body bytes. File mode requires a selected file; text and hex modes may be empty.
Is my secret uploaded or saved?
No. The tool has no endpoint for body or key data and does not use localStorage. The secret remains in this tab's React state until cleared or closed; browsers and extensions remain outside the tool's control.
Privacy
Body bytes, header and secret are processed in browser memory. No signing input or secret is sent to the app server or stored automatically. Clear the lab after using a real credential.
Comments & questions