Validation hmac not working for app proxy

Topic summary

A developer is implementing HMAC validation for a Shopify app proxy using Cloudflare Workers but encountering issues with the official @shopify/shopify-api-js library.

Problem:

  • The shopify.utils.validateHmac() method consistently returns ‘Not valid’
  • Manual HMAC validation using a custom implementation does work successfully

Technical Details:

  • Using Cloudflare Workers as the proxy URL
  • Attempting to validate requests using the official Shopify API library
  • Code includes configuration with apiSecretKey, adminApiAccessToken, and custom store app settings
  • The manual workaround involves sorting query parameters, filtering out the signature, and computing HMAC with SHA256

Resolution:
Another user confirmed that removing all “&” characters from the string during proxy app HMAC calculation resolves the validation issue. The developer wants to use the official library to enable product API requests rather than relying on manual validation.

Summarized with AI on October 24. AI used: claude-sonnet-4-5-20250929.

Hi there,

I am looking to create a simple fetch request using Cloudflare Workers as a proxy url.

This is working to a point, however when trying to use the https://github.com/Shopify/shopify-app-js to validate the hmac, it’s not succeeding.

I have tried an alternative solution manually validating the hmac and this does work https://github.com/Shopify/shopify-api-js/issues/878#issue-1713165039

Here is my code to hopefully give an idea on what i’m doing and whether it’s wrong - it always returns ‘Not valid’:

import { shopifyApi, ApiVersion } from '@shopify/shopify-api';
import '@shopify/shopify-api/adapters/cf-worker';
import { AutoRouter, json } from 'itty-router';

const router = AutoRouter();

router.all('*', async ({ query }) => {
	const hmac = query.signature;

	const shopify = shopifyApi({
		apiSecretKey: 'Client_ID', // Removed for example
		adminApiAccessToken: 'Client_Secret', // Removed for example
		apiVersion: ApiVersion.April24,
		isCustomStoreApp: true,
		isEmbeddedApp: false,
		hostName: 'example.myshopify.com', // Removed for example
	});

	const isValid = await shopify.utils.validateHmac(query, {
		signator: 'appProxy'
	});

	if (isValid) {
		return 'Valid';
	} else {
		return 'Not valid';
	}
});

export default { ...router };

I have tried this alternative solution without the use of the noded module and it does validate successfully and returns true:

import crypto from 'node:crypto';

const paramsToObject = (entries) => {
	const result = {};
	for (const [key, value] of entries) {
		result[key] = value;
	}
	return result;
};

const verifyProxy = (request, env) => {
	const url = new URL(request.url);
	const searchParams = new URLSearchParams(url.search);
	const hmac = searchParams.get('signature');
	const entries = searchParams.entries();
	const params = paramsToObject(entries);
	const toVerify = Object.entries(params)
		.filter(([key]) => key !== 'signature')
		.map(([key, value]) => `${key}=${value}`)
		.sort((a, b) => a.localeCompare(b))
		.join('');

	const calculatedHmac = crypto.createHmac('sha256', env.SHOPIFY_API_SECRET)
		.update(toVerify)
		.digest('hex');

	return calculatedHmac === hmac;
}

export default {
	async fetch(request, env, ctx) {
		return new Response(verifyProxy(request, env));
	}
};

Am I missing something as I’d like to use the node module so I can make product requests.

Thanks in advance.

Jack

This was very helpful. All “&” had to be removed from the string for proxy app HMAC calculation. It worked.