Webhook Reliability Playbook

Shopify HMAC Verification Failed

Use this playbook when Shopify webhook verification rejects a request or Webhook Guard shows invalid events. HMAC verification must use the raw request body and the correct app secret; parsed JSON is not equivalent evidence.

HMACsecurityraw bodysecret rotation
Platform Shopify
Pipeline area Authentication and signature verification
Typical impact Valid Shopify events rejected or invalid requests blocked
Suggested owner Backend developer or security engineer with access to secret configuration and raw request handling
Diagnostic complexity Moderate
Recovery risk Moderate; retry only after confirming the request is authentic and partial processing did not occur

Verified Shopify HMAC facts

Shopify documents X-Shopify-Hmac-SHA256 as a base64 HMAC over the raw request body using the app client secret Shopify HMAC docs.

First five minutes

  • Do not log the real secret or full HMAC value.
  • Record the delivery ID, shop domain, topic, timestamp, and whether the failure occurred at Shopify inbound or Webhook Guard downstream authentication.
  • Confirm the secret source, environment, and recent rotation history.
  • Check whether middleware parsed, reserialized, or transformed the body before verification.
  • Preserve a sanitized failure sample with headers and payload length.

Evidence to collect

Evidence Where to find it What it proves What it does not prove
HMAC header Raw request headers or Webhook Guard invalid event detail Shopify sent a signature value with the delivery That your calculated value used the same raw bytes or secret
Raw request body bytes Request handler before JSON parsing or body-transforming middleware The exact bytes used for signature calculation That the configured secret is correct
Secret configuration Runtime environment, secret manager, deployment config, and rotation history Which secret the verifier used That Shopify has already switched to the new secret after rotation
Middleware order Endpoint routing and framework configuration Whether verification runs before body parsing or mutation That proxies did not alter transfer encoding or body bytes earlier

Focused failure-stage map

Fast decision tree

  1. Is the failure for Shopify inbound verification or Webhook Guard downstream API-key authentication? Diagnose the correct trust boundary first.
  2. Was the HMAC computed from raw request bytes? If no, fix body capture before investigating secrets.
  3. Does the runtime secret match the Shopify app secret for this environment? If no, fix configuration and rotation process.
  4. Was the app secret rotated recently? If yes, allow for documented propagation behavior and accept both expected secrets only when your security process explicitly supports it.
  5. If raw body and secret are correct, inspect proxy, framework, encoding, base64 decoding, and constant-time comparison behavior.

Root-cause matrix

Raw body handling

Possible cause Evidence that supports it Evidence that eliminates it Corrective action
JSON parser consumed or reserialized the body Verification code reads an object or stringified JSON instead of original bytes Verifier receives untouched Buffer or raw string before parsing Move verification before body parsing or configure raw-body capture for the webhook route.
Encoding or base64 mismatch Computed digest is hex, compared as text, or buffers have different decoded lengths Both values are decoded from base64 and compared in constant time with equal lengths Compute HMAC-SHA256 over raw bytes and compare decoded base64 buffers safely.

Secret configuration

Possible cause Evidence that supports it Evidence that eliminates it Corrective action
Wrong app secret or environment The endpoint uses staging, old, or BigCommerce credentials for a Shopify request Secret manager, deployment, and app settings agree for the same app and environment Correct the secret binding and redeploy with masked audit evidence.
Secret rotation propagation Failure started immediately after rotation and old secret still validates a sample Neither old nor new configured secret validates the same raw request Follow a rotation window and remove old-secret acceptance after verification.

Illustrative example — not a real customer incident

Example diagnosis

EvidenceObserved result
Incident evidenceThe configured app secret is correct.
Incident evidenceThe request header is present.
Incident evidenceVerification runs after JSON parsing.
Incident evidenceThe calculated signature uses reserialized JSON.
Incident evidenceThe raw request bytes are not retained.

Diagnosis

Middleware transformed the request body before HMAC verification.

Why that diagnosis follows

Shopify signs the raw request body. Once middleware parses and reserializes JSON, the byte sequence used for HMAC comparison is no longer the signed input.

Safest next action

Capture the raw request bytes before parsing and compare using a timing-safe method.

What not to do

Do not disable signature verification or log the app secret.

Detailed diagnostic procedure

  1. Confirm the exact inbound path and whether Webhook Guard or your endpoint is performing Shopify HMAC verification.
  2. Inspect framework middleware order and ensure the verifier receives the raw request body before JSON parsing.
  3. Check that the header lookup is case-insensitive and reads X-Shopify-Hmac-Sha256.
  4. Compare decoded base64 buffers with a constant-time comparison after checking lengths.
  5. If a proxy sits in front of the app, confirm it does not decompress, rewrite, or normalize the body before the app receives it.

Immediate containment

  • Do not bypass HMAC verification in production.
  • Stop printing secrets, full signatures, or unsanitized payloads in logs.
  • Keep invalid requests blocked while you test with a known Shopify delivery.

Permanent fix

  • Add route-specific raw-body handling for Shopify webhooks.
  • Add secret rotation runbooks and environment checks.
  • Add tests that fail if middleware order changes and HMAC verification uses parsed JSON.

Architecture improvement

  • Keep authentication, durable recording, and parsing as separate steps.
  • Mask secrets and sensitive headers in every log view.
  • Track invalid-signature counts separately from downstream delivery failures.

Safe recovery gate

  • I confirmed the correct application and environment secret.
  • I preserved the original raw request bytes.
  • I confirmed whether verification failed before any processing.
  • I checked recent secret rotation.
  • I am testing against one known Shopify delivery.
  • I have not weakened or disabled verification.

What Webhook Guard can and cannot prove

Can show

  • Invalid events that failed signature validation before forwarding.
  • Headers and metadata useful for investigating the failing request, with sensitive values protected by the product UI.

Cannot prove

  • It cannot validate your customer endpoint's separate authentication logic unless that endpoint reports its result.
  • It cannot make a parsed request body equivalent to the raw bytes Shopify signed.

Webhook Guard evidence for this incident

Webhook Guard Invalid Events records rejected inbound requests and keeps the rejection visible without forwarding unverified payloads to integrations.

Limitations

  • Invalid HMAC evidence cannot determine whether the wrong secret is in Shopify, Webhook Guard, or a test environment without configuration evidence.
  • Webhook Guard cannot reconstruct raw bytes if an upstream middleware or proxy has already transformed the body before verification.

Claim sources

Verify webhook deliveries · Webhooks delivery structure · Webhook Guard User Guide

User Guide

Open Invalid Events guidance

Safe Node.js Verification Shape

This example shows the important boundary: verify the raw request body before parsing it Shopify HMAC docs. Keep real secrets out of logs.

import { createHmac, timingSafeEqual } from "node:crypto"

function verifyShopifyWebhook(rawBody, hmacHeader, secret) {
  const digest = createHmac("sha256", secret).update(rawBody).digest()
  const received = Buffer.from(hmacHeader, "base64")

  if (digest.length !== received.length) return false
  return timingSafeEqual(digest, received)
}

Production runbook

  1. Identify the trust boundary.
  2. Verify raw-body capture.
  3. Verify secret and rotation state.
  4. Check encoding and comparison.
  5. Retest with one controlled delivery.

Sources and last verification

Sources and verification