-
Notifications
You must be signed in to change notification settings - Fork 196
Description
Issue summary
The Shopify SDK uses global mutable exports for adapter registration. When multiple adapters are
imported in the same Node.js process, they silently overwrite each other with no warnings or errors,
causing runtime failures that are difficult to debug.
Before opening this issue, I have:
- Upgraded to the latest version of the relevant packages
@shopify/*package and version:- @shopify/shopify-api: 12.1.0
- @shopify/shopify-app-react-router: 1.0.1
- Node version: 22.x
- Operating system: WSL Ubuntu 22
- Set
{ logger: { level: LogSeverity.Debug } }in my configuration, when applicable - Found a reliable way to reproduce the problem that indicates it's a problem with the package
- Looked for similar issues in this repository
- Checked that this isn't an issue with a Shopify API
- If it is, please create a post in the Shopify community forums or report it to Shopify Partner Support
We discovered this after hours of debugging webhook authentication failures. The error manifested as
missing_hmac in HMAC validation, but the root cause was the Node adapter's inability to parse Web API
Headers objects - a completely unrelated symptom.
The Shopify SDK uses global mutable exports for adapter registration (setAbstractConvertRequestFunc,
etc.). This creates conflicts in applications that need different execution contexts within the same
Node.js process or codebase.
Impact: Impossible to use different adapters for different contexts (e.g., Web-API adapter for
webhooks + Node adapter for background jobs) even when architecturally sound separation exists.
Steps to reproduce the problem
// File 1: web-handler.ts
import "@shopify/shopify-api/adapters/web-api";
import { shopifyApp } from "@shopify/shopify-app-react-router/server";
const shopify = shopifyApp({ /* config */ });
// ✅ Web-API adapter registered
// File 2: worker-handler.ts
import "@shopify/shopify-api/adapters/node";
import { shopifyApi } from "@shopify/shopify-api";
const api = shopifyApi({ /* config */ });
// ❌ Node adapter OVERWRITES Web-API adapter globally
// Main app entry
import "./web-handler"; // Registers Web-API adapter
import "./worker-handler"; // Overwrites with Node adapter
// Result: Webhooks fail because they now use Node adapter instead of Web-APIExpected behavior
At minimum (Bug fix):
- Warn when an adapter is overwritten: "Warning: Web-API adapter is being replaced by Node adapter"
- Error when conflicting adapters detected in same process
- Document that only one adapter per process is supported
Ideally (Feature enhancement):
Instance-based configuration to support multiple contexts:
import { Shopify, WebApiAdapter, NodeAdapter } from '@shopify/shopify-api';
// Web context
const webShopify = new Shopify({
...config,
adapter: new WebApiAdapter()
});
// Worker context
const workerShopify = new Shopify({
...config,
adapter: new NodeAdapter()
})Actual behavior
- Silent failure: No warnings when adapters overwrite each other
- Last-import-wins: Module load order determines which adapter is active globally
- Unexpected side effects: Importing unrelated code can break webhooks
- Difficult debugging: Error appears unrelated to adapter conflict
Root Cause
The SDK uses global singleton pattern for adapters:
// From @shopify/shopify-api/runtime/http/index.mjs
let abstractConvertRequest = () => { throw new Error(...) };
function setAbstractConvertRequestFunc(func) {
abstractConvertRequest = func; // Overwrites previous adapter
}
This mutable global state means:
- Only one adapter can exist per process
- Import order matters (fragile)
- No isolation between contexts
Affects common application patterns:
- ✅ Monorepos with web + worker processes sharing code
- ✅ Admin panels controlling background job lifecycle
- ✅ Any shared codebase needing multiple execution contexts
- ✅ Route-based code splitting that lazy-loads worker code
Debug logs
After adding extensive logging to the internal packages. Please don't mind the remix, I forgot to change the name in shopify.web.toml:
[WEB-API ADAPTER INDEX] Loading web-api adapter module (ESM)
11:36:26 │ remix │ [WEB-API ADAPTER INDEX] Setting abstractConvertRequestFunc (ESM)
11:36:26 │ remix │ [HTTP INDEX] setAbstractConvertRequestFunc called { funcType: 'function', funcName: 'webApiConvertRequest' }
11:36:26 │ remix │ [HTTP INDEX] abstractConvertRequest is now { type: 'function', name: 'webApiConvertRequest' }
11:36:26 │ remix │ [WEB-API ADAPTER INDEX] abstractConvertRequestFunc set to: function
11:36:26 │ remix │ [shopify-api/INFO] version 12.1.0, environment React Router
11:36:27 │ remix │ [HTTP INDEX] setAbstractConvertRequestFunc called { funcType: 'function', funcName: 'nodeConvertRequest' }
11:36:27 │ remix │ [HTTP INDEX] abstractConvertRequest is now { type: 'function', name: 'nodeConvertRequest' }
11:36:27 │ remix │ [shopify-api/INFO] version 12.1.0, environment Node v25.0.0
11:36:27 │ remix │ [shopify-api/INFO] Future flag customerAddressDefaultFix is disabled.
11:36:27 │ remix │
11:36:27 │ remix │ Enable this flag to change the CustomerAddress classes to expose a 'is_default' property instead of
'default' when fetching data.
11:36:27 │ remix │
11:36:27 │ remix │ [shopify-api/INFO] Future flag unstable_managedPricingSupport is disabled.
11:36:27 │ remix │
11:36:27 │ remix │ Enable this flag to support managed pricing, so apps can check for payments without needing a billing
config. Learn more at https://shopify.dev/docs/apps/launch/billing/managed-pricing
11:36:27 │ remix │
11:36:29 │ remix │ [SHOPIFY AUTH] About to validate webhook { hasRawBody: true, rawBodyLength: 1108 }
11:36:29 │ remix │ [SHOPIFY AUTH] About to validate webhook { hasRawBody: true, rawBodyLength: 6556 }
11:36:29 │ remix │ [SHOPIFY AUTH] About to validate webhook { hasRawBody: true, rawBodyLength: 6556 }
11:36:29 │ remix │ [HMAC VALIDATOR] adapterArgs received {
11:36:29 │ remix │ adapterArgsKeys: [ 'rawRequest' ],
11:36:29 │ remix │ hasRawRequest: true,
11:36:29 │ remix │ rawRequestType: 'object',
11:36:29 │ remix │ rawRequestHeaders: Headers {
11:36:29 │ remix │ host: 'conducting-delayed-participating-jennifer.trycloudflare.com',
11:36:29 │ remix │ 'user-agent': 'Shopify-Captain-Hook',
11:36:29 │ remix │ 'content-length': '1108',
11:36:29 │ remix │ accept: '*/*',
11:36:29 │ remix │ 'accept-encoding': 'gzip',
11:36:29 │ remix │ 'cdn-loop': 'cloudflare; loops=1; subreqs=1',
11:36:29 │ remix │ 'cf-connecting-ip': '34.60.69.191',
11:36:29 │ remix │ 'cf-ew-via': '15',
11:36:29 │ remix │ 'cf-ipcountry': 'US',
11:36:29 │ remix │ 'cf-ray': '995fa0f4a6335d77-HKG',
11:36:29 │ remix │ 'cf-visitor': '{"scheme":"https"}',
11:36:29 │ remix │ 'cf-warp-tag-id': 'c93d8b82-344b-4041-a6b2-5a46b0c3e135',
11:36:29 │ remix │ 'cf-worker': 'trycloudflare.com',
11:36:29 │ remix │ connection: 'close',
11:36:29 │ remix │ 'content-type': 'application/json',
11:36:29 │ remix │ 'x-forwarded-for': '34.60.69.191',
11:36:29 │ remix │ 'x-forwarded-proto': 'https',
11:36:29 │ remix │ 'x-shopify-api-version': '2026-01',
11:36:29 │ remix │ 'x-shopify-event-id': 'b7dde891-878a-4507-8bfa-d1dc7c32fd95',
11:36:29 │ remix │ 'x-shopify-hmac-sha256': 'q2TnqNH1e+o/Y9lzG53Fx8g32guDgBZau4aop/A9nxA=',
11:36:29 │ remix │ 'x-shopify-shop-domain': 'v4teststore.myshopify.com',
11:36:29 │ remix │ 'x-shopify-topic': 'customers/update',
11:36:29 │ remix │ 'x-shopify-triggered-at': '2025-10-29T03:36:26.313660046Z',
11:36:29 │ remix │ 'x-shopify-webhook-id': '903e6674-4612-4998-aaff-d3e72c3ec2da'
11:36:29 │ remix │ },
11:36:29 │ remix │ rawRequestHeadersType: 'object'
11:36:29 │ remix │ }
11:36:29 │ remix │ [HMAC VALIDATOR] About to call abstractConvertRequest { funcType: 'function', funcName: 'nodeConvertRequest'
}
11:36:29 │ remix │ [HMAC VALIDATOR] adapterArgs received {
11:36:29 │ remix │ adapterArgsKeys: [ 'rawRequest' ],
11:36:29 │ remix │ hasRawRequest: true,
11:36:29 │ remix │ rawRequestType: 'object',
11:36:29 │ remix │ rawRequestHeaders: Headers {
11:36:29 │ remix │ host: 'conducting-delayed-participating-jennifer.trycloudflare.com',
11:36:29 │ remix │ 'user-agent': 'Shopify-Captain-Hook',
11:36:29 │ remix │ 'content-length': '6556',
11:36:29 │ remix │ accept: '*/*',
11:36:29 │ remix │ 'accept-encoding': 'gzip',
11:36:29 │ remix │ 'cdn-loop': 'cloudflare; loops=1; subreqs=1',
11:36:29 │ remix │ 'cf-connecting-ip': '34.173.83.235',
11:36:29 │ remix │ 'cf-ew-via': '15',
11:36:29 │ remix │ 'cf-ipcountry': 'US',
11:36:29 │ remix │ 'cf-ray': '995fa0f6b3c6125f-HKG',
11:36:29 │ remix │ 'cf-visitor': '{"scheme":"https"}',
11:36:29 │ remix │ 'cf-warp-tag-id': 'c93d8b82-344b-4041-a6b2-5a46b0c3e135',
11:36:29 │ remix │ 'cf-worker': 'trycloudflare.com',
11:36:29 │ remix │ connection: 'close',
11:36:29 │ remix │ 'content-type': 'application/json',
11:36:29 │ remix │ 'x-forwarded-for': '34.173.83.235',
11:36:29 │ remix │ 'x-forwarded-proto': 'https',
11:36:29 │ remix │ 'x-shopify-api-version': '2026-01',
11:36:29 │ remix │ 'x-shopify-event-id': '688298c9-ae21-455f-ba96-14d957c65fb7',
11:36:29 │ remix │ 'x-shopify-hmac-sha256': '5Y57Wmf0giw1LMgW29z/OxTtqtwY1YV9V8/Jnf7XesA=',
11:36:29 │ remix │ 'x-shopify-order-id': '6772084605217',
11:36:29 │ remix │ 'x-shopify-shop-domain': 'v4teststore.myshopify.com',
11:36:29 │ remix │ 'x-shopify-test': 'false',
11:36:29 │ remix │ 'x-shopify-topic': 'orders/updated',
11:36:29 │ remix │ 'x-shopify-triggered-at': '2025-10-29T03:36:26.154497409Z',
11:36:29 │ remix │ 'x-shopify-webhook-id': 'b0c7e1b6-534b-4062-93e0-624f11081aa3'
11:36:29 │ remix │ },
11:36:29 │ remix │ rawRequestHeadersType: 'object'
11:36:29 │ remix │ }
11:36:29 │ remix │ [HMAC VALIDATOR] About to call abstractConvertRequest { funcType: 'function', funcName: 'nodeConvertRequest'
}
11:36:29 │ remix │ [HMAC VALIDATOR] adapterArgs received {
11:36:29 │ remix │ adapterArgsKeys: [ 'rawRequest' ],
11:36:29 │ remix │ hasRawRequest: true,
11:36:29 │ remix │ rawRequestType: 'object',
11:36:29 │ remix │ rawRequestHeaders: Headers {
11:36:29 │ remix │ host: 'conducting-delayed-participating-jennifer.trycloudflare.com',
11:36:29 │ remix │ 'user-agent': 'Shopify-Captain-Hook',
11:36:29 │ remix │ 'content-length': '6556',
11:36:29 │ remix │ accept: '*/*',
11:36:29 │ remix │ 'accept-encoding': 'gzip',
11:36:29 │ remix │ 'cdn-loop': 'cloudflare; loops=1; subreqs=1',
11:36:29 │ remix │ 'cf-connecting-ip': '35.232.119.78',
11:36:29 │ remix │ 'cf-ew-via': '15',
11:36:29 │ remix │ 'cf-ipcountry': 'US',
11:36:29 │ remix │ 'cf-ray': '995fa0fb77205c90-HKG',
11:36:29 │ remix │ 'cf-visitor': '{"scheme":"https"}',
11:36:29 │ remix │ 'cf-warp-tag-id': 'c93d8b82-344b-4041-a6b2-5a46b0c3e135',
11:36:29 │ remix │ 'cf-worker': 'trycloudflare.com',
11:36:29 │ remix │ connection: 'close',
11:36:29 │ remix │ 'content-type': 'application/json',
11:36:29 │ remix │ 'x-forwarded-for': '35.232.119.78',
11:36:29 │ remix │ 'x-forwarded-proto': 'https',
11:36:29 │ remix │ 'x-shopify-api-version': '2026-01',
11:36:29 │ remix │ 'x-shopify-event-id': '2a6cb17f-f9e8-4dd4-a02d-7df2445a92ca',
11:36:29 │ remix │ 'x-shopify-hmac-sha256': '5Y57Wmf0giw1LMgW29z/OxTtqtwY1YV9V8/Jnf7XesA=',
11:36:29 │ remix │ 'x-shopify-order-id': '6772084605217',
11:36:29 │ remix │ 'x-shopify-shop-domain': 'v4teststore.myshopify.com',
11:36:29 │ remix │ 'x-shopify-test': 'false',
11:36:29 │ remix │ 'x-shopify-topic': 'orders/create',
11:36:29 │ remix │ 'x-shopify-triggered-at': '2025-10-29T03:36:26.827751118Z',
11:36:29 │ remix │ 'x-shopify-webhook-id': 'e4ce5547-19de-46e4-bc15-d8e8ad28bd5f'
11:36:29 │ remix │ },
11:36:29 │ remix │ rawRequestHeadersType: 'object'
11:36:29 │ remix │ }
11:36:29 │ remix │ [HMAC VALIDATOR] About to call abstractConvertRequest { funcType: 'function', funcName: 'nodeConvertRequest'
}
11:36:29 │ remix │ [HMAC VALIDATOR] abstractConvertRequest returned { hasHeaders: true, headersKeys: [] }
11:36:29 │ remix │ [HMAC VALIDATOR] Starting validation {
11:36:29 │ remix │ rawBodyLength: 1108,
11:36:29 │ remix │ rawBodyPreview:
'{"id":9537313341729,"created_at":"2025-10-28T09:05:33-04:00","updated_at":"2025-10-28T23:36:26-04:00',
11:36:29 │ remix │ type: 'webhook'
11:36:29 │ remix │ }
11:36:29 │ remix │ [HMAC VALIDATOR] Request headers object type {
11:36:29 │ remix │ headersType: 'object',
11:36:29 │ remix │ headersConstructor: 'Object',
11:36:29 │ remix │ isObject: true,
11:36:29 │ remix │ headersKeys: [],
11:36:29 │ remix │ rawHeaders: {}
11:36:29 │ remix │ }
11:36:29 │ remix │ [HMAC VALIDATOR] HMAC header extracted { hmac: null, hasHmac: false }
11:36:29 │ remix │ [shopify-api/DEBUG] webhook request is not valid | {reason: missing_hmac}
11:36:29 │ remix │ [HMAC VALIDATOR] abstractConvertRequest returned { hasHeaders: true, headersKeys: [] }
11:36:29 │ remix │ [HMAC VALIDATOR] Starting validation {
11:36:29 │ remix │ rawBodyLength: 6556,
11:36:29 │ remix │ rawBodyPreview:
'{"id":6772084605217,"admin_graphql_api_id":"gid:\\/\\/shopify\\/Order\\/6772084605217","app_id":1354745,',
11:36:29 │ remix │ type: 'webhook'
11:36:29 │ remix │ }
11:36:29 │ remix │ [HMAC VALIDATOR] Request headers object type {
11:36:29 │ remix │ headersType: 'object',
11:36:29 │ remix │ headersConstructor: 'Object',
11:36:29 │ remix │ isObject: true,
11:36:29 │ remix │ headersKeys: [],
11:36:29 │ remix │ rawHeaders: {}
11:36:29 │ remix │ }
11:36:29 │ remix │ [HMAC VALIDATOR] HMAC header extracted { hmac: null, hasHmac: false }
11:36:29 │ remix │ [shopify-api/DEBUG] webhook request is not valid | {reason: missing_hmac}
11:36:29 │ remix │ [HMAC VALIDATOR] abstractConvertRequest returned { hasHeaders: true, headersKeys: [] }
11:36:29 │ remix │ [HMAC VALIDATOR] Starting validation {
11:36:29 │ remix │ rawBodyLength: 6556,
11:36:29 │ remix │ rawBodyPreview:
'{"id":6772084605217,"admin_graphql_api_id":"gid:\\/\\/shopify\\/Order\\/6772084605217","app_id":1354745,',
11:36:29 │ remix │ type: 'webhook'
11:36:29 │ remix │ }
11:36:29 │ remix │ [HMAC VALIDATOR] Request headers object type {
11:36:29 │ remix │ headersType: 'object',
11:36:29 │ remix │ headersConstructor: 'Object',
11:36:29 │ remix │ isObject: true,
11:36:29 │ remix │ headersKeys: [],
11:36:29 │ remix │ rawHeaders: {}
11:36:29 │ remix │ }
11:36:29 │ remix │ [HMAC VALIDATOR] HMAC header extracted { hmac: null, hasHmac: false }
11:36:29 │ remix │ [shopify-api/DEBUG] webhook request is not valid | {reason: missing_hmac}
11:36:29 │ remix │ [SHOPIFY AUTH] Webhook validation failed {
11:36:29 │ remix │ reason: 'missing_hmac',
11:36:29 │ remix │ invalidHmac: false,
11:36:29 │ remix │ checkObject: '{"valid":false,"reason":"missing_hmac"}'
11:36:29 │ remix │ }
11:36:29 │ remix │ [shopify-app/DEBUG] Webhook validation failed | {valid: false, reason: missing_hmac}
11:36:29 │ remix │ [SHOPIFY AUTH] Webhook validation failed {
11:36:29 │ remix │ reason: 'missing_hmac',
11:36:29 │ remix │ invalidHmac: false,
11:36:29 │ remix │ checkObject: '{"valid":false,"reason":"missing_hmac"}'
11:36:29 │ remix │ }
11:36:29 │ remix │ [shopify-app/DEBUG] Webhook validation failed | {valid: false, reason: missing_hmac}
11:36:29 │ remix │ [SHOPIFY AUTH] Webhook validation failed {
11:36:29 │ remix │ reason: 'missing_hmac',
11:36:29 │ remix │ invalidHmac: false,
11:36:29 │ remix │ checkObject: '{"valid":false,"reason":"missing_hmac"}'
11:36:29 │ remix │ }
11:36:29 │ remix │ [shopify-app/DEBUG] Webhook validation failed | {valid: false, reason: missing_hmac}
11:36:29 │ remix │ error: Webhook authentication failed: SDK threw a Response {"APP_ENV":"development","body":"","hmac":"q2TnqNH
1e+o/Y9lzG53Fx8g32guDgBZau4aop/A9nxA=","shop":"v4teststore.myshopify.com","status":400,"statusText":"Bad
Request","topic":"customers/update","url":"http://conducting-delayed-participating-jennifer.trycloudflare.com/webhooks/customers"}
11:36:29 │ remix │ error: Webhook authentication failed: SDK threw a Response {"APP_ENV":"development","body":"","hmac":"5Y57Wmf
0giw1LMgW29z/OxTtqtwY1YV9V8/Jnf7XesA=","shop":"v4teststore.myshopify.com","status":400,"statusText":"Bad
Request","topic":"orders/updated","url":"http://conducting-delayed-participating-jennifer.trycloudflare.com/webhooks/orders"}
11:36:29 │ remix │ error: Webhook authentication failed: SDK threw a Response {"APP_ENV":"development","body":"","hmac":"5Y57Wmf
0giw1LMgW29z/OxTtqtwY1YV9V8/Jnf7XesA=","shop":"v4teststore.myshopify.com","status":400,"statusText":"Bad
Request","topic":"orders/create","url":"http://conducting-delayed-participating-jennifer.trycloudflare.com/webhooks/orders"}
11:36:31 │ remix │ [SHOPIFY AUTH] About to validate webhook { hasRawBody: true, rawBodyLength: 6556 }
11:36:31 │ remix │ [HMAC VALIDATOR] adapterArgs received {
11:36:31 │ remix │ adapterArgsKeys: [ 'rawRequest' ],
11:36:31 │ remix │ hasRawRequest: true,
11:36:31 │ remix │ rawRequestType: 'object',
11:36:31 │ remix │ rawRequestHeaders: Headers {
11:36:31 │ remix │ host: 'conducting-delayed-participating-jennifer.trycloudflare.com',
11:36:31 │ remix │ 'user-agent': 'Shopify-Captain-Hook',
11:36:31 │ remix │ 'content-length': '6556',
11:36:31 │ remix │ accept: '*/*',
11:36:31 │ remix │ 'accept-encoding': 'gzip',
11:36:31 │ remix │ 'cdn-loop': 'cloudflare; loops=1; subreqs=1',
11:36:31 │ remix │ 'cf-connecting-ip': '136.112.30.235',
11:36:31 │ remix │ 'cf-ew-via': '15',
11:36:31 │ remix │ 'cf-ipcountry': 'US',
11:36:31 │ remix │ 'cf-ray': '995fa113e01961cc-HKG',
11:36:31 │ remix │ 'cf-visitor': '{"scheme":"https"}',
11:36:31 │ remix │ 'cf-warp-tag-id': 'c93d8b82-344b-4041-a6b2-5a46b0c3e135',
11:36:31 │ remix │ 'cf-worker': 'trycloudflare.com',
11:36:31 │ remix │ connection: 'close',
11:36:31 │ remix │ 'content-type': 'application/json',
11:36:31 │ remix │ 'x-forwarded-for': '136.112.30.235',
11:36:31 │ remix │ 'x-forwarded-proto': 'https',
11:36:31 │ remix │ 'x-shopify-api-version': '2026-01',
11:36:31 │ remix │ 'x-shopify-event-id': '2a6cb17f-f9e8-4dd4-a02d-7df2445a92ca',
11:36:31 │ remix │ 'x-shopify-hmac-sha256': '5Y57Wmf0giw1LMgW29z/OxTtqtwY1YV9V8/Jnf7XesA=',
11:36:31 │ remix │ 'x-shopify-order-id': '6772084605217',
11:36:31 │ remix │ 'x-shopify-shop-domain': 'v4teststore.myshopify.com',
11:36:31 │ remix │ 'x-shopify-test': 'false',
11:36:31 │ remix │ 'x-shopify-topic': 'orders/create',
11:36:31 │ remix │ 'x-shopify-triggered-at': '2025-10-29T03:36:26.827751118Z',
11:36:31 │ remix │ 'x-shopify-webhook-id': 'e4ce5547-19de-46e4-bc15-d8e8ad28bd5f'
11:36:31 │ remix │ },
11:36:31 │ remix │ rawRequestHeadersType: 'object'
11:36:31 │ remix │ }
11:36:31 │ remix │ [HMAC VALIDATOR] About to call abstractConvertRequest { funcType: 'function', funcName: 'nodeConvertRequest'
}
11:36:31 │ remix │ [HMAC VALIDATOR] abstractConvertRequest returned { hasHeaders: true, headersKeys: [] }
11:36:31 │ remix │ [HMAC VALIDATOR] Starting validation {
11:36:31 │ remix │ rawBodyLength: 6556,
11:36:31 │ remix │ rawBodyPreview:
'{"id":6772084605217,"admin_graphql_api_id":"gid:\\/\\/shopify\\/Order\\/6772084605217","app_id":1354745,',
11:36:31 │ remix │ type: 'webhook'
11:36:31 │ remix │ }
11:36:31 │ remix │ [HMAC VALIDATOR] Request headers object type {
11:36:31 │ remix │ headersType: 'object',
11:36:31 │ remix │ headersConstructor: 'Object',
11:36:31 │ remix │ isObject: true,
11:36:31 │ remix │ headersKeys: [],
11:36:31 │ remix │ rawHeaders: {}
11:36:31 │ remix │ }
11:36:31 │ remix │ [HMAC VALIDATOR] HMAC header extracted { hmac: null, hasHmac: false }
11:36:31 │ remix │ [shopify-api/DEBUG] webhook request is not valid | {reason: missing_hmac}
11:36:31 │ remix │ [SHOPIFY AUTH] Webhook validation failed {
11:36:31 │ remix │ reason: 'missing_hmac',
11:36:31 │ remix │ invalidHmac: false,
11:36:31 │ remix │ checkObject: '{"valid":false,"reason":"missing_hmac"}'
11:36:31 │ remix │ }
11:36:31 │ remix │ [shopify-app/DEBUG] Webhook validation failed | {valid: false, reason: missing_hmac}
11:36:31 │ remix │ error: Webhook authentication failed: SDK threw a Response {"APP_ENV":"development","body":"","hmac":"5Y57Wmf
0giw1LMgW29z/OxTtqtwY1YV9V8/Jnf7XesA=","shop":"v4teststore.myshopify.com","status":400,"statusText":"Bad
Request","topic":"orders/create","url":"http://conducting-delayed-participating-jennifer.trycloudflare.com/webhooks/orders"}
11:36:31 │ remix │ [SHOPIFY AUTH] About to validate webhook { hasRawBody: true, rawBodyLength: 6556 }
11:36:31 │ remix │ [HMAC VALIDATOR] adapterArgs received {
11:36:31 │ remix │ adapterArgsKeys: [ 'rawRequest' ],
11:36:31 │ remix │ hasRawRequest: true,
11:36:31 │ remix │ rawRequestType: 'object',
11:36:31 │ remix │ rawRequestHeaders: Headers {
11:36:31 │ remix │ host: 'conducting-delayed-participating-jennifer.trycloudflare.com',
11:36:31 │ remix │ 'user-agent': 'Shopify-Captain-Hook',
11:36:31 │ remix │ 'content-length': '6556',
11:36:31 │ remix │ accept: '*/*',
11:36:31 │ remix │ 'accept-encoding': 'gzip',
11:36:31 │ remix │ 'cdn-loop': 'cloudflare; loops=1; subreqs=1',
11:36:31 │ remix │ 'cf-connecting-ip': '34.132.238.17',
11:36:31 │ remix │ 'cf-ew-via': '15',
11:36:31 │ remix │ 'cf-ipcountry': 'US',
11:36:31 │ remix │ 'cf-ray': '995fa114e503e825-HKG',
11:36:31 │ remix │ 'cf-visitor': '{"scheme":"https"}',
11:36:31 │ remix │ 'cf-warp-tag-id': 'c93d8b82-344b-4041-a6b2-5a46b0c3e135',
11:36:31 │ remix │ 'cf-worker': 'trycloudflare.com',
11:36:31 │ remix │ connection: 'close',
11:36:31 │ remix │ 'content-type': 'application/json',
11:36:31 │ remix │ 'x-forwarded-for': '34.132.238.17',
11:36:31 │ remix │ 'x-forwarded-proto': 'https',
11:36:31 │ remix │ 'x-shopify-api-version': '2026-01',
11:36:31 │ remix │ 'x-shopify-event-id': '688298c9-ae21-455f-ba96-14d957c65fb7',
11:36:31 │ remix │ 'x-shopify-hmac-sha256': '5Y57Wmf0giw1LMgW29z/OxTtqtwY1YV9V8/Jnf7XesA=',
11:36:31 │ remix │ 'x-shopify-order-id': '6772084605217',
11:36:31 │ remix │ 'x-shopify-shop-domain': 'v4teststore.myshopify.com',
11:36:31 │ remix │ 'x-shopify-test': 'false',
11:36:31 │ remix │ 'x-shopify-topic': 'orders/updated',
11:36:31 │ remix │ 'x-shopify-triggered-at': '2025-10-29T03:36:26.154497409Z',
11:36:31 │ remix │ 'x-shopify-webhook-id': 'b0c7e1b6-534b-4062-93e0-624f11081aa3'
11:36:31 │ remix │ },
11:36:31 │ remix │ rawRequestHeadersType: 'object'
11:36:31 │ remix │ }
11:36:31 │ remix │ [HMAC VALIDATOR] About to call abstractConvertRequest { funcType: 'function', funcName: 'nodeConvertRequest'
}
11:36:31 │ remix │ [HMAC VALIDATOR] abstractConvertRequest returned { hasHeaders: true, headersKeys: [] }
11:36:31 │ remix │ [HMAC VALIDATOR] Starting validation {
11:36:31 │ remix │ rawBodyLength: 6556,
11:36:31 │ remix │ rawBodyPreview:
'{"id":6772084605217,"admin_graphql_api_id":"gid:\\/\\/shopify\\/Order\\/6772084605217","app_id":1354745,',
11:36:31 │ remix │ type: 'webhook'
11:36:31 │ remix │ }
11:36:31 │ remix │ [HMAC VALIDATOR] Request headers object type {
11:36:31 │ remix │ headersType: 'object',
11:36:31 │ remix │ headersConstructor: 'Object',
11:36:31 │ remix │ isObject: true,
11:36:31 │ remix │ headersKeys: [],
11:36:31 │ remix │ rawHeaders: {}
11:36:31 │ remix │ }
11:36:31 │ remix │ [HMAC VALIDATOR] HMAC header extracted { hmac: null, hasHmac: false }
11:36:31 │ remix │ [shopify-api/DEBUG] webhook request is not valid | {reason: missing_hmac}
11:36:31 │ remix │ [SHOPIFY AUTH] Webhook validation failed {
11:36:31 │ remix │ reason: 'missing_hmac',
11:36:31 │ remix │ invalidHmac: false,
11:36:31 │ remix │ checkObject: '{"valid":false,"reason":"missing_hmac"}'
11:36:31 │ remix │ }
11:36:31 │ remix │ [shopify-app/DEBUG] Webhook validation failed | {valid: false, reason: missing_hmac}
11:36:31 │ remix │ error: Webhook authentication failed: SDK threw a Response {"APP_ENV":"development","body":"","hmac":"5Y57Wmf
0giw1LMgW29z/OxTtqtwY1YV9V8/Jnf7XesA=","shop":"v4teststore.myshopify.com","status":400,"statusText":"Bad
Request","topic":"orders/updated","url":"http://conducting-delayed-participating-jennifer.trycloudflare.com/webhooks/orders"}
11:36:31 │ remix │ [SHOPIFY AUTH] About to validate webhook { hasRawBody: true, rawBodyLength: 1108 }
11:36:31 │ remix │ [HMAC VALIDATOR] adapterArgs received {
11:36:31 │ remix │ adapterArgsKeys: [ 'rawRequest' ],
11:36:31 │ remix │ hasRawRequest: true,
11:36:31 │ remix │ rawRequestType: 'object',
11:36:31 │ remix │ rawRequestHeaders: Headers {
11:36:31 │ remix │ host: 'conducting-delayed-participating-jennifer.trycloudflare.com',
11:36:31 │ remix │ 'user-agent': 'Shopify-Captain-Hook',
11:36:31 │ remix │ 'content-length': '1108',
11:36:31 │ remix │ accept: '*/*',
11:36:31 │ remix │ 'accept-encoding': 'gzip',
11:36:31 │ remix │ 'cdn-loop': 'cloudflare; loops=1; subreqs=1',
11:36:31 │ remix │ 'cf-connecting-ip': '35.225.95.82',
11:36:31 │ remix │ 'cf-ew-via': '15',
11:36:31 │ remix │ 'cf-ipcountry': 'US',
11:36:31 │ remix │ 'cf-ray': '995fa114c2c25d77-ORD',
11:36:31 │ remix │ 'cf-visitor': '{"scheme":"https"}',
11:36:31 │ remix │ 'cf-warp-tag-id': 'c93d8b82-344b-4041-a6b2-5a46b0c3e135',
11:36:31 │ remix │ 'cf-worker': 'trycloudflare.com',
11:36:31 │ remix │ connection: 'close',
11:36:31 │ remix │ 'content-type': 'application/json',
11:36:31 │ remix │ 'x-forwarded-for': '35.225.95.82',
11:36:31 │ remix │ 'x-forwarded-proto': 'https',
11:36:31 │ remix │ 'x-shopify-api-version': '2026-01',
11:36:31 │ remix │ 'x-shopify-event-id': 'b7dde891-878a-4507-8bfa-d1dc7c32fd95',
11:36:31 │ remix │ 'x-shopify-hmac-sha256': 'q2TnqNH1e+o/Y9lzG53Fx8g32guDgBZau4aop/A9nxA=',
11:36:31 │ remix │ 'x-shopify-shop-domain': 'v4teststore.myshopify.com',
11:36:31 │ remix │ 'x-shopify-topic': 'customers/update',
11:36:31 │ remix │ 'x-shopify-triggered-at': '2025-10-29T03:36:26.313660046Z',
11:36:31 │ remix │ 'x-shopify-webhook-id': '903e6674-4612-4998-aaff-d3e72c3ec2da'
11:36:31 │ remix │ },
11:36:31 │ remix │ rawRequestHeadersType: 'object'
11:36:31 │ remix │ }
11:36:31 │ remix │ [HMAC VALIDATOR] About to call abstractConvertRequest { funcType: 'function', funcName: 'nodeConvertRequest'
}
11:36:31 │ remix │ [HMAC VALIDATOR] abstractConvertRequest returned { hasHeaders: true, headersKeys: [] }
11:36:31 │ remix │ [HMAC VALIDATOR] Starting validation {
11:36:31 │ remix │ rawBodyLength: 1108,
11:36:31 │ remix │ rawBodyPreview:
'{"id":9537313341729,"created_at":"2025-10-28T09:05:33-04:00","updated_at":"2025-10-28T23:36:26-04:00',
11:36:31 │ remix │ type: 'webhook'
11:36:31 │ remix │ }
11:36:31 │ remix │ [HMAC VALIDATOR] Request headers object type {
11:36:31 │ remix │ headersType: 'object',
11:36:31 │ remix │ headersConstructor: 'Object',
11:36:31 │ remix │ isObject: true,
11:36:31 │ remix │ headersKeys: [],
11:36:31 │ remix │ rawHeaders: {}
11:36:31 │ remix │ }
11:36:31 │ remix │ [HMAC VALIDATOR] HMAC header extracted { hmac: null, hasHmac: false }
11:36:31 │ remix │ [shopify-api/DEBUG] webhook request is not valid | {reason: missing_hmac}
11:36:31 │ remix │ [SHOPIFY AUTH] Webhook validation failed {
11:36:31 │ remix │ reason: 'missing_hmac',
11:36:31 │ remix │ invalidHmac: false,
11:36:31 │ remix │ checkObject: '{"valid":false,"reason":"missing_hmac"}'
11:36:31 │ remix │ }
11:36:31 │ remix │ [shopify-app/DEBUG] Webhook validation failed | {valid: false, reason: missing_hmac}
11:36:31 │ remix │ error: Webhook authentication failed: SDK threw a Response {"APP_ENV":"development","body":"","hmac":"q2TnqNH
1e+o/Y9lzG53Fx8g32guDgBZau4aop/A9nxA=","shop":"v4teststore.myshopify.com","status":400,"statusText":"Bad
Request","topic":"customers/update","url":"http://conducting-delayed-participating-jennifer.trycloudflare.com/webhooks/customers"}
The global singleton pattern made this exceptionally difficult to trace because:
- No logs indicated adapter overwrite
- Import happened during route module loading (not function call)
- Error appeared in completely different code path (webhooks)
Would appreciate:
- Acknowledgment of issue
- Guidance on recommended architecture until fixed
Thank you for maintaining this SDK! 🙏