ModernCalcs

Webhook Signature Verifier

Verify an HMAC-SHA256 webhook signature against a payload and secret — GitHub, Stripe, or generic format.

Webhook Signature Verifier: Real HMAC, Provider-Aware

Every major webhook provider signs its payloads with HMAC-SHA256 so receivers can confirm a request genuinely came from them — but each provider formats the signature header slightly differently. This tool computes a real HMAC-SHA256 digest via the browser's native Web Crypto API and compares it using the exact format each provider expects, rather than a generic one-size-fits-all check.

Formula
GitHub: sha256=HMAC-SHA256(payload, secret) | Stripe: v1=HMAC-SHA256(`${t}.${payload}`, secret)

Stripe includes the timestamp in the signed message specifically to enable replay-attack detection via timestamp age.

Why Byte-Exact Payloads Matter

HMAC is a cryptographic function over the exact bytes you give it — if your webhook handler re-serializes the JSON body before verifying (changing key order, whitespace, or number formatting), the computed signature won't match even though the data is semantically identical. Always verify against the raw request body, before any JSON.parse/stringify round-trip.

GitHub vs. Stripe Signing Conventions

GitHub's X-Hub-Signature-256 header signs only the payload bytes. Stripe's Stripe-Signature header signs a string combining the timestamp and payload (t.payload), and includes that timestamp in the header itself — this lets a receiver both verify authenticity and reject stale/replayed webhook deliveries by checking how old the timestamp is.

Real Cryptography, Client-Side

The HMAC-SHA256 computation uses the Web Crypto API's crypto.subtle.sign, the same standards-based implementation browsers use for TLS and other cryptographic operations — not a hand-rolled hash function.

Practical Examples

Debugging a Failed GitHub Webhook Verification

Your server rejects a webhook GitHub says it sent.

  • 1.Provider: GitHub
  • 2.Paste the exact raw payload your server received
  • 3.Paste your webhook secret and the X-Hub-Signature-256 value
  • 4.Compare the computed vs. provided signature

Providers Supported

  • GitHub (sha256=)
  • Stripe (t=,v1=)
  • Generic HMAC-SHA256 (hex)
  • Generic HMAC-SHA256 (base64)

Good Use Cases

  • Debugging why your webhook handler rejects valid requests
  • Verifying you're computing the signature correctly before deploying
  • Confirming a suspicious webhook delivery's authenticity
  • Learning how a specific provider's signing scheme actually works

Frequently Asked Questions

How is the signature actually computed and compared?

Using the browser's native Web Crypto API (crypto.subtle) to compute a real HMAC-SHA256 digest of the payload with your secret, formatted to match the selected provider's convention, then compared against the signature you provide.

Why does the raw payload matter so much?

HMAC verification is byte-exact — even a single extra whitespace character, a re-serialized JSON body, or a changed line ending will produce a completely different signature. Always use the exact raw request body as received, not a reformatted or re-parsed version of it.

How does Stripe's format differ from GitHub's?

GitHub signs just the raw payload and sends sha256= in the X-Hub-Signature-256 header. Stripe signs a concatenation of the timestamp and payload (`${timestamp}.${payload}`) and sends t=,v1= in the Stripe-Signature header — including the timestamp specifically to let receivers reject old/replayed requests.

What does the 'timestamp age' note mean for Stripe signatures?

Stripe recommends rejecting webhook events where the timestamp is more than 5 minutes (300 seconds) old, as a defense against replay attacks — this tool shows you that age so you can see whether a given signature would pass that freshness check.

Is my secret sent anywhere?

No — HMAC computation happens entirely in your browser via the Web Crypto API; your secret and payload never leave your machine.