peekhook

How to verify a webhook signature

When a service like Stripe or GitHub sends you a webhook, it signs the request so you can prove the request really came from them and was not altered in transit. Verifying that signature is the difference between trusting a webhook and accepting anything anyone posts to your endpoint.

Why it matters

Your webhook URL is public - anything that knows it can POST to it. Without verification you have no way to tell a real event from a forged one.

Skip verification and an attacker can POST a forged {"type":"payment_intent.succeeded"} straight to your handler and trigger whatever that event does - ship the product, credit the account, unlock the feature. A forged body cannot carry a signature that matches your secret, so verification rejects it.

How the signature works

The sender computes an HMAC - a hash of the request body keyed with a shared signing secret only you and the sender know - and puts it in a header. Your side recomputes the same HMAC with the same secret over the exact raw body and compares. Match means authentic and untampered; mismatch means a wrong secret, an altered body, or a forgery.

Per provider

  • Stripe - Stripe-Signature: t=<timestamp>,v1=<hmac>. HMAC-SHA256 of "{timestamp}.{body}", hex.
  • GitHub - X-Hub-Signature-256: sha256=<hmac>. HMAC-SHA256 of the body, hex.
  • Shopify - X-Shopify-Hmac-Sha256. HMAC-SHA256 of the body, base64.
  • Slack - X-Slack-Signature: v0=<hmac>. HMAC-SHA256 of "v0:{timestamp}:{body}", hex.

The signature is computed over the raw bytes of the body. Re-serializing or pretty-printing the JSON changes the bytes and breaks the check - the single most common reason verification fails in your own code.

Verify a signature in PeekHook

  1. Send the webhook (or any signed request) to your PeekHook URL.
  2. Open the request and switch to the Verify tab.
  3. Pick the provider (or Custom for a generic HMAC), paste your signing secret, and click Verify signature.
  4. You get Signature valid or Invalid - so you can confirm your secret and payload line up before writing a single line of handler code.

Your secret stays in your browser. PeekHook computes the HMAC locally with the Web Crypto API - the signing secret is never sent to PeekHook, stored, or logged.

Get a webhook URL and try it