Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Most webhook signature mismatches come down to one of four things: the verifier received a changed body, the wrong secret or header, an incorrect provider-specific signing recipe, or an unsafe comparison. Preserve the incoming body exactly and verify it with the provider’s documented method before parsing or trusting its fields. A valid signature still does not prevent replay or duplicate processing, so handle freshness, deduplication, and idempotent effects separately.

Why webhook signature verification fails

There is no universal webhook signature format. Providers may sign the raw body, a constructed string containing a timestamp, or another provider-defined input; they also differ in headers and digest encodings. Start by identifying the provider and following its current verification recipe rather than applying one generic HMAC implementation.

The request body changed before verification

A JSON object can retain the same meaning while its bytes change. Parsing and reserializing may alter whitespace, key order, escaping, Unicode representation, or encoding. A verifier that computes a digest over the new representation will not match a signature made over the original input. Stripe lists these kinds of body changes as common causes of failure. Stripe’s signature troubleshooting guide also warns that Express JSON middleware placed before the webhook route can consume or change the body needed for verification.

Shopify likewise requires the raw request body for manual verification and directs developers to run verification before body-parsing middleware. In Express, the practical principle is to capture the route’s raw body and verify it before a global JSON parser handles that route. Shopify’s manual example uses express.raw({ type: '*/*' }); Stripe’s Express guidance describes placing the webhook route before app.use(express.json()). Adapt the approach to the provider SDK and framework version you actually use. Shopify’s delivery verification guide provides its current recipe.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The same boundary problem can occur outside the application. A proxy, load balancer, API gateway, or serverless adapter may alter the body or headers, or expose a normalized body rather than the original bytes. GitHub specifically advises checking proxies and load balancers for payload or header changes; Stripe documents preserving a separate raw-body value in an API Gateway mapping. GitHub’s validation guide and Stripe’s troubleshooting guide cover these integration concerns.

The secret, endpoint, or environment is wrong

Verify the secret source as well as its value. In Stripe, a Dashboard endpoint secret and a Stripe CLI listener secret are different, even though both use the whsec_ prefix. A CLI-forwarded development event therefore will not verify against the Dashboard endpoint secret. GitHub notes that its SHA-256 signature header is not present when no webhook secret is configured, so check that a secret is set and that the receiver uses the intended one.

For Shopify, the HMAC key is the app client secret. Shopify says that after client-secret rotation, HMAC generation with the new secret can take up to an hour; account for that documented propagation window rather than relaxing verification. Slack uses the app signing secret, not its deprecated verification token. Keep secrets out of logs while debugging. Slack’s request-verification guide explains the signing secret approach.

The header, signed input, algorithm, or encoding is wrong

Providers do not all sign the same string or encode the result the same way. For example, GitHub’s preferred header contains a hex HMAC-SHA256 digest prefixed with sha256=, while its SHA-1 X-Hub-Signature is retained for legacy use. Shopify’s X-Shopify-Hmac-SHA256 value is a base64-encoded HMAC-SHA256 digest over the raw request body. Slack signs v0:{timestamp}:{raw body} and sends a hex digest prefixed with v0=. Stripe recommends its SDK’s constructEvent() path using the original body string, Stripe-Signature header, and endpoint secret. See the providers’ documentation for the exact recipe: GitHub, Shopify, Slack, and Stripe.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Check that your code reads the documented header, constructs the signed input in the documented order, uses the correct key bytes and algorithm, and handles the expected prefix and output encoding. HTTP header names are case-insensitive, but framework representations can normalize their capitalization; Slack cautions against assuming a particular header-key case. Missing, malformed, or truncated values must fail verification rather than trigger a permissive fallback.

The comparison is incorrect or unsafe

Use the provider’s maintained SDK when it fits your runtime, or a constant-time comparison helper suitable for the provider’s documented digest representation. Do not compare hex text with base64 text, or inconsistently retain or strip a prefix. GitHub warns against plain equality and its Python example uses hmac.compare_digest; Slack also recommends an HMAC comparison function.

Reject missing or malformed signatures cleanly and fail closed: no valid signature means no event dispatch. A malformed header should not cause an exception path that skips the check or accidentally accepts the request.

How the major providers differ

Use this as a diagnostic map, not a substitute for each provider’s current SDK and integration documentation.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Provider Signed input and representation Common mismatch Replay or duplicate handling
GitHub Payload HMAC-SHA256 in X-Hub-Signature-256, hex with a sha256= prefix. Use the webhook secret and original payload. Missing configured secret, wrong header or algorithm, wrong secret, UTF-8 handling, or proxy/load-balancer mutation. X-GitHub-Delivery identifies a delivery; GitHub says a redelivery retains the same ID.
Shopify Raw request body HMAC-SHA256 keyed with the app client secret, base64 in X-Shopify-Hmac-SHA256. Body parser ran first, wrong encoding, or verification happened after parsing. Persist X-Shopify-Webhook-Id for delivery deduplication or make work idempotent. Event IDs can correlate deliveries from the same merchant action. New-secret HMAC generation after rotation may take up to an hour.
Slack v0:{timestamp}:{raw body}, signed with the app signing secret; hex digest prefixed with v0=. Parsed body, wrong secret, incorrect timestamp construction, header-case assumption, or comparison error. Slack’s example rejects requests whose timestamp differs from local time by more than five minutes.
Stripe Use Stripe-Signature, the original UTF-8 body string, endpoint secret, and the SDK’s constructEvent() flow. Dashboard and CLI secrets differ; body was parsed or changed; Express middleware order is wrong. The signature troubleshooting guidance focuses on verification; use Stripe’s event and retry documentation for application-level processing behavior.

Sources: GitHub validation, GitHub best practices, Shopify verification, Slack verification, and Stripe troubleshooting.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Can a valid signature still be replayed?

Yes. A valid signature shows that the provider-defined signed input matches a signature made using the expected secret. It does not, by itself, prove that the request is new or that your business operation has not already run.

Check freshness where the provider includes a timestamp

Slack signs a timestamp into its base string and recommends rejecting requests more than five minutes from local time, as in its example. Use a reliable system clock; stale or significantly skewed clocks can cause otherwise legitimate requests to fail the freshness check.

Deduplicate deliveries with the provider’s identifier

GitHub recommends using X-GitHub-Delivery to identify replayed deliveries and notes that a redelivery keeps its original identifier. Shopify distinguishes the webhook delivery ID from an event ID: use the delivery ID to deduplicate individual deliveries, while an event ID can correlate deliveries associated with the same merchant action. Persisting processed IDs lets the receiver recognize retries that arrive after a timeout or other delivery problem.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Make business effects idempotent

Deduplication and idempotency solve related but different problems. Record delivery IDs to avoid processing the same delivery twice; also design the business operation so a repeated event cannot create an unintended second charge, shipment, or record. Shopify explicitly recommends idempotent operations or persistent storage of processed webhook IDs. Treat signature verification, freshness or replay controls, deduplication, and idempotent effects as separate layers.

A safe sequence for debugging a failing check

  1. Identify the integration. Record the provider, endpoint, test or live environment, and verification library and version. Confirm the active secret in the provider’s authoritative configuration. For Stripe, distinguish a CLI listener secret from a Dashboard endpoint secret.
  2. Check the header. Confirm the expected signature header exists and matches the provider’s format. Do not silently accept requests that arrive without it.
  3. Preserve the raw body. Capture incoming bytes before JSON or form parsing and pass those bytes—or the exact representation the provider SDK requires—to verification. For diagnostics, record byte length and, if needed, a carefully protected hash or sanitized sample; do not expose secrets or sensitive payload data.
  4. Recheck the recipe. Verify the signed input, algorithm, secret/key encoding, digest encoding, prefix, and any timestamp policy against the provider’s current documentation.
  5. Inspect every transformation boundary. Check body-parser order, API Gateway mappings, serverless adapters, compression or decompression, proxies, load balancers, and forwarded headers.
  6. Test the digest implementation independently. GitHub publishes a known secret, a Hello, World! payload, and the expected signature in its validation guide. A passing known vector checks the HMAC implementation, but it does not prove your live HTTP path preserves the raw body.
  7. Keep the endpoint fail-closed. Verify first, then parse and dispatch. Apply freshness checks, durable deduplication, and idempotent business effects as appropriate for the provider.

GitHub’s delivery guidance also says a receiver should respond with a 2XX within 10 seconds or GitHub terminates the connection and considers the delivery a failure. That is a delivery-response constraint, distinct from whether the signature is correct. GitHub’s webhook best practices describe it.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.