Topics covering webhook creation & management, event handling, Pub/Sub, and Eventbridge, in Shopify apps.
Hi,
I am a newbie on shopify app. I am confusing with step Implement HMAC signature. I setup mandatory webhook like this in my code, can some one guide me how to verify webhook please
Thank you so much!
const shopify = shopifyApp({ apiKey: process.env.SHOPIFY_API_KEY, apiSecretKey: process.env.SHOPIFY_API_SECRET || "", apiVersion: LATEST_API_VERSION, scopes: process.env.SCOPES?.split(","), appUrl: process.env.SHOPIFY_APP_URL || "", authPathPrefix: "/auth", sessionStorage: new PrismaSessionStorage(prisma), distribution: AppDistribution.AppStore, restResources, webhooks: { APP_UNINSTALLED: { deliveryMethod: DeliveryMethod.Http, callbackUrl: "/webhooks", }, CUSTOMERS_DATA_REQUEST: { deliveryMethod: DeliveryMethod.Http, callbackUrl: "/webhooks", callback: async (topic, shop, body, webhookId) => { const payload = JSON.parse(body); } }, CUSTOMERS_REDACT: { deliveryMethod: DeliveryMethod.Http, callbackUrl: "/webhooks", callback: async (topic, shop, body, webhookId) => { const payload = JSON.parse(body); } }, SHOP_REDACT: { deliveryMethod: DeliveryMethod.Http, callbackUrl: "/webhooks", callback: async (topic, shop, body, webhookId) => { const payload = JSON.parse(body); } }, }, hooks: { afterAuth: async ({ session }) => { shopify.registerWebhooks({ session }); }, }, future: { v3_webhookAdminContext: true, v3_authenticatePublic: true, unstable_newEmbeddedAuthStrategy: true, }, ...(process.env.SHOP_CUSTOM_DOMAIN ? { customShopDomains: [process.env.SHOP_CUSTOM_DOMAIN] } : {}), });
import type { ActionFunctionArgs } from "@remix-run/node"; import { authenticate } from "../shopify.server"; import db from "../db.server"; export const action = async ({ request }: ActionFunctionArgs) => { const { topic, shop, session, admin, payload } = await authenticate.webhook( request ); if (!admin) { // The admin context isn't returned if the webhook fired after a shop was uninstalled. throw new Response(); } switch (topic) { case "APP_UNINSTALLED": if (session) { await db.session.deleteMany({ where: { shop } }); } break; case "CUSTOMERS_DATA_REQUEST": case "CUSTOMERS_REDACT": case "SHOP_REDACT": default: throw new Response("Unhandled webhook topic", { status: 404 }); } throw new Response(); };
Hey, is this solved ? Currently stuck on the same issue.
Hi @abhishek1108 , Were you able to solve this? We are stuck at the same step. It'll be great if you can help.
Were you able to solve this? We are stuck at the same step. It'll be great if you can help for me.
I found a solution and reported it here: https://community.shopify.com/c/shopify-apps/how-do-i-implement-hmac-signature-for-webhook-verificat...
Hi @abhishek1108 @appaza, If you are using Remix template, try using the latest version (I'm using @shopify/shopify-app-remix: "^2.7.0") version.
They have solved this issue for mandatory webhooks.
Refer these ->
https://github.com/Shopify/shopify-api-js/issues/256
https://github.com/Shopify/shopify-app-js/issues/505
The only change we need to do, is in the webhooks.jsx, Need to add the break in Switch-Case. Adding the code below.. No need to change anything in shopify.server.js.
import { authenticate } from "../shopify.server";
import db from "../db.server";
export const action = async ({ request }) => {
const { topic, shop, session, admin } = await authenticate.webhook(request);
if (!admin) {
// The admin context isn't returned if the webhook fired after a shop was uninstalled.
throw new Response();
}
switch (topic) {
case "APP_UNINSTALLED":
if (session) {
await db.deltaXDetails.deleteMany({
where: { shop: session.shop }
});
await db.session.deleteMany({ where: { shop } });
}
break;
case "CUSTOMERS_DATA_REQUEST":
break;
case "CUSTOMERS_REDACT":
break;
case "SHOP_REDACT":
break;
default:
throw new Response("Unhandled webhook topic", { status: 404 });
}
throw new Response();
};
And while submission in partners dashboard, we need to put the mandatory webhook URLs as <Your-App-URL>/webhooks.
My Automatic test cases have passed with the above change.
Hello there,
i have fixed this using following changes
Step 1 import : npm install @remix-run/node
Step 2 do small chnages in webhooks file as per given link.
This will work for me.
can you share code?
import crypto from "node:crypto";
import type { ActionFunctionArgs } from "@remix-run/node"; // or cloudflare/deno
import { json } from "@remix-run/node"; // or cloudflare/deno
export const action = async ({
request,
}: ActionFunctionArgs) => {
if (request.method !== "POST") {
return json({ message: "Method not allowed" }, 405);
}
const payload = await request.json();
/* Validate the webhook */
const signature = request.headers.get(
"X-Hub-Signature-256"
);
const generatedSignature = `sha256=${crypto
.createHmac("sha256", process.env.GITHUB_WEBHOOK_SECRET)
.update(JSON.stringify(payload))
.digest("hex")}`;
if (signature !== generatedSignature) {
return json({ message: "Signature mismatch" }, 401);
}
/* process the webhook (e.g. enqueue a background job) */
return json({ success: true }, 200);
};
Thank you it was the 'import crypto from "node:crypto";' that I was missing.
It should be
.digest("base64")
Otherwise it wasn't a match for me, just gonna put it out there in case someone else has the same issue.
And I also needed to remove the prefix 'sha256=' for it to be a match in my remix-app -> so it's just:
const generatedHmac = crypto
.createHmac("sha256", CLIENT_SECRET)
.update(JSON.stringify(payload))
.digest("base64");
Just for clarity, this still caused some issues with calculating the hmac for some webhooks. (Some generated the correct one and some didn't, really strange). But tvthatsme's solution solved my issue!