Verify webhook on remix

blueskydev
Shopify Partner
1 0 0

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();
};

 

blueskydev_0-1710724740974.png

 

Replies 11 (11)

abhishek1108
Shopify Partner
6 0 0

Hey, is this solved ? Currently stuck on the same issue.

 

HarshaKDeltaX
Shopify Partner
2 0 0

Hi @abhishek1108 , Were you able to solve this? We are stuck at the same step. It'll be great if you can help. 

abhishek1108
Shopify Partner
6 0 0
No I am still looking for a solution to this issue.
appaza
Shopify Partner
13 1 2

Were you able to solve this? We are stuck at the same step. It'll be great if you can help for me.

Collaboration creates new things
tvthatsme
Shopify Partner
2 0 2
HarshaKDeltaX
Shopify Partner
2 0 0

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.

 

 

yash_soni
Shopify Partner
4 0 0

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.

appaza
Shopify Partner
13 1 2

can you share code?

Collaboration creates new things
appaza
Shopify Partner
13 1 2
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);
};
Collaboration creates new things
Alicia_P
Excursionist
12 3 3

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");

 

Alicia_P
Excursionist
12 3 3

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!