Skip to content

Verify webhook signatures

Signature verification is the part of a webhook integration that fails most often and explains itself least. catchbin verifies every inbound request against the signing secret you store, and when a signature does not match it runs a diagnostic that names the likely cause.

You give an endpoint its signing secret. On every inbound request, catchbin recomputes the provider’s expected signature over the exact bytes it received and compares that to the signature in the request headers. The comparison is on bytes, not parsed JSON — two payloads that parse to the same object can still produce different digests, which is why most signature failures are really byte-level differences.

When a signature does not verify, catchbin does not just record “failed”. It checks, in order:

  1. Body byte count — the number of bytes catchbin received versus the number the provider signed. A mismatch means something rewrote the body before it reached the verifier (a middleware, a re-serialization, an added newline). This is the most common cause.
  2. Timestamp age — for schemes that sign a timestamp, how far the signed time is from now. Beyond the provider’s tolerance window, the request is rejected as too old (replay protection).
  3. Secret format — whether the stored secret has the shape the provider issues (for example, a Stripe signing secret starts with whsec_, not sk_). A well-formed-but-wrong secret is caught here.
  4. Header format — whether the signature header parses into the structure the scheme expects. A malformed header fails before any digest is compared.

Each branch points at one fix. That turns “signature verification failed” into “the body was mutated between the load balancer and your handler”.

The exact header, algorithm, and signed payload differ per provider. Match your symptom to a cause on the provider page:

  • StripeStripe-Signature, HMAC-SHA256 over {timestamp}.{raw_body}.
  • GitHubX-Hub-Signature-256, HMAC-SHA256 over the raw body.
  • ShopifyX-Shopify-Hmac-SHA256, HMAC-SHA256 over the raw body, base64-encoded.
  • Other providers (Generic) — any HMAC-SHA256, hex-encoded signature over the raw body; you configure the header name and optional prefix.

Replaying an old event to a target that verifies signatures will fail by default, because the original signed timestamp is now stale. catchbin surfaces this and offers --resign, which computes a fresh signature for the target using a secret you supply. See Replay an event.