Switch to light mode

Webhook Signature Verification Setup Guide (2026): Stripe, GitHub & HMAC

- 7 min read

Server verifying an incoming webhook request signature against a stored secret

Verifying Webhook Signatures

A webhook endpoint is a public URL that accepts POST requests from the internet. Without verification, anyone who finds the URL can send a fake “payment succeeded” or “deployment completed” event and your application will act on it as if it were real. Signature verification is what turns a webhook endpoint from an open door into something that only trusts the provider it’s actually paired with.

How It Works

Most providers (Stripe, GitHub, and most others that offer webhooks) sign every event using HMAC. When they send a webhook, they include a signature header computed from the raw request body and a shared secret only you and the provider know. Your endpoint recomputes that same signature using the secret, and compares it to what was sent. If they match, the request genuinely came from the provider and hasn’t been tampered with in transit.

Prerequisites

  • The webhook secret from your provider’s dashboard (separate from your API key)
  • Access to the raw, unparsed request body in your endpoint code

That last point causes more failed verifications than anything else. Most web frameworks parse the request body into JSON automatically before your code ever sees it, but HMAC verification requires the exact raw bytes the provider signed. If your framework re-serializes the body before you compute the signature, verification will fail even for genuine requests.

Setup Steps

  1. Store the webhook secret as an environment variable, never hardcoded and never the same value as your API key.
  2. Configure your route to capture the raw request body before any JSON parsing middleware touches it.
  3. On each incoming request, read the signature from the header the provider specifies (for example, Stripe-Signature or X-Hub-Signature-256).
  4. Compute an HMAC-SHA256 hash of the raw body using your webhook secret.
  5. Compare your computed signature to the one in the header using a constant-time comparison function, not a standard === or == string check.
  6. Reject the request (typically a 400 response) if the signatures don’t match, before any business logic runs.

Why Constant-Time Comparison Matters

A standard string comparison returns as soon as it finds the first mismatched character, which means it takes measurably longer to compare strings that match further before diverging. That timing difference is small, but over enough requests it can be used to guess a valid signature one byte at a time. Every mainstream language has a constant-time comparison function built in or in a standard crypto library specifically to avoid this.

Common Mistakes

  • Verifying against the parsed/re-serialized body instead of the raw bytes. The signed payload and the one you verify against must be byte-for-byte identical.
  • Trusting the request before verification. Any database writes, emails, or side effects should happen strictly after the signature check passes, not before.
  • No replay protection. Signature verification confirms authenticity, not freshness. Providers typically include a timestamp in the payload or signature header specifically so you can reject requests older than a few minutes, closing the door on a captured, valid signature being replayed later.
  • One secret shared across environments. Use separate webhook secrets for staging and production endpoints, just like API keys.

Once verification is in place, treat it as a non-negotiable gate: no webhook handler logic should run before the signature check has passed.

© 2024 Shawn Mayzes. All rights reserved.