Hi everyone,
I’m trying to submit my app to the App Store but the automated check “Verifies webhooks with HMAC signatures” consistently fails. I’ve spent days debugging this and I’m stuck.
App Details
- Framework: Remix with @shopify/shopify-app-remix v3.7.0
- Hosting: Railway (paid plan)
What’s Passing
- Immediately authenticates after install
- Immediately redirects to app UI after authentication
- Provides mandatory compliance webhooks
- Uses a valid TLS certificate
What’s Failing
- Verifies webhooks with HMAC signatures
The Strange Part is No requests appear in my server logs when the HMAC check runs. The check takes 3-5 minutes and then fails, but my logs show zero webhook requests during that time. However:
- Real webhooks from Shopify work fine (APP_UNINSTALLED returns 200)
- Manual testing confirms my endpoints return 401 for invalid HMAC
- My app is accessible (other automated checks pass)
My Implementation
I’m using authenticate.webhook() from @shopify/shopify-app-remix:
import { authenticate } from "../shopify.server"; export const action = async ({ request }) => { try { const { topic, shop, payload } = await authenticate.webhook(request);
// Process webhook... return new Response("OK", { status: 200 });
} catch (error) {
console.error("Webhook authentication failed:", error.message); return new Response("Unauthorized", { status: 401 });
}
};
I also created a catch-all /webhooks endpoint with manual HMAC verification:
import crypto from "crypto";
export const action = async ({ request }) => {
const rawBody = await request.text();
const hmacHeader = request.headers.get("x-shopify-hmac-sha256");
if (!hmacHeader) { return new Response("Unauthorized", { status: 401 }); }
const generatedHash = crypto .createHmac("sha256", process.env.SHOPIFY_API_SECRET) .update(rawBody, "utf8") .digest("base64");
const hmacBuffer = Buffer.from(hmacHeader, "base64"); const generatedBuffer = Buffer.from(generatedHash, "base64"); const isValid = hmacBuffer.length === generatedBuffer.length && crypto.timingSafeEqual(hmacBuffer, generatedBuffer);
if (!isValid) { return new Response("Unauthorized", { status: 401 }); }
return new Response("OK", { status: 200 });
};
My shopify.app.toml
[webhooks]
api_version = "2025-01"
[[webhooks.subscriptions]]
topics = [ "app/uninstalled" ]
uri = "/webhooks/app/uninstalled"
[[webhooks.subscriptions]]
topics = [ "app/scopes_update" ]
uri = "/webhooks/app/scopes_update"
[[webhooks.subscriptions]]
uri = "/webhooks/compliance"
compliance_topics = [ "customers/data_request", "customers/redact", "shop/redact" ]
What I’ve Tried
- Using authenticate.webhook() from the official Shopify library
- Manual HMAC verification with crypto
- Creating a catch-all /webhooks endpoint
- Returning proper 401 for invalid signatures, 200 for valid
- Verified my SHOPIFY_API_SECRET matches the dashboard (32 characters)
- Running shopify app deploy to sync configuration
- Confirmed app is running and accessible during check
Has anyone else experienced the automated checker not reaching their endpoints? 2. What specific URL does the HMAC checker test? 3. Is there any way to get diagnostic information about why the check failed?
Any help would be greatly appreciated. I’ve seen other threads about this issue but no clear solutions. Thanks!
