shopify-app-template-node icon indicating copy to clipboard operation
shopify-app-template-node copied to clipboard

Could not validate request HMAC

Open yanglee2421 opened this issue 2 years ago • 3 comments

Issue summary

I am building a merchant-facing app to onboard merchants to my marketplace. After setting up shopifyApp from @shopify/shopify-app-express, the app it keeps giving the following error:

Webhook request is invalid, returning 401: Could not validate request HMAC
Failed to process webhook: Error: Could not validate request HMAC

Complete setup (removed key and secret to paste here):

// Shopify.ts
export const shopify = shopifyApp({
  api: {
    apiVersion: LATEST_API_VERSION,
    restResources,
    ...env(),
  },
  auth: {
    path: "/api/auth",
    callbackPath: "/api/auth/callback",
  },
  webhooks: {
    path: "/api/webhooks",
  },
  sessionStorage: new SQLiteSessionStorage(DB_PATH),
});

// GDPR.ts
import { DeliveryMethod } from "@shopify/shopify-api";
import { WebhookHandlersParam } from "@shopify/shopify-app-express";

export const webhookHandlers: WebhookHandlersParam = {
  CUSTOMERS_DATA_REQUEST: {
    deliveryMethod: DeliveryMethod.Http,
    callbackUrl: "/api/webhooks",
    async callback(topic: any, shop: any, body: any, webhookId: any) {
      const payload = JSON.parse(body);
      console.log("CUSTOMERS_DATA_REQUEST", webhookId);
    },
  },

  CUSTOMERS_REDACT: {
    deliveryMethod: DeliveryMethod.Http,
    callbackUrl: "/api/webhooks",
    async callback(topic: any, shop: any, body: any, webhookId: any) {
      const payload = JSON.parse(body);
      console.log("CUSTOMERS_REDACT", webhookId);
    },
  },

  SHOP_REDACT: {
    deliveryMethod: DeliveryMethod.Http,
    callbackUrl: "/api/webhooks",
    async callback(topic: any, shop: any, body: any, webhookId: any) {
      const payload = JSON.parse(body);
      console.log("SHOP_REDACT", webhookId);
    },
  },

  APP_UNINSTALLED: {
    deliveryMethod: DeliveryMethod.Http,
    callbackUrl: "/api/webhooks",
    async callback(topic: any, shop: any, body: any, webhookId: any) {
      const payload = JSON.parse(body);
      console.log("APP_UNINSTALLED", webhookId);
    },
  },

  PRODUCTS_CREATE: {
    deliveryMethod: DeliveryMethod.Http,
    callbackUrl: "/api/webhooks",
    async callback(topic: any, shop: any, body: any, webhookId: any) {
      const payload = JSON.parse(body);
      console.log("PRODUCTS_CREATE", webhookId);
    },
  },

  PRODUCTS_DELETE: {
    deliveryMethod: DeliveryMethod.Http,
    callbackUrl: "/api/webhooks",
    async callback(topic: any, shop: any, body: any, webhookId: any) {
      const payload = JSON.parse(body);
      console.log("PRODUCTS_DELETE", webhookId);
    },
  },
};
  • @shopify/shopify-app-express version:^2.2.2
  • Node version:v18.16.0
  • Operating system:Windows

Expected behavior

  • shopifyApp should setup correctly and link it provides should work

Actual behavior

Giving error:

Webhook request is invalid, returning 401: Could not validate request HMAC
Failed to process webhook: Error: Could not validate request HMAC

Steps to reproduce the problem

  1. git clone https://github.com/yanglee2421/shopify-app-demo.git
  2. yarn && yarn dev
  3. yarn shopify webhook trigger

yanglee2421 avatar Sep 01 '23 02:09 yanglee2421

Hi, any update on this, I have the same issue and I tried different solutions but nothing works

ernesto-ck avatar Oct 10 '23 05:10 ernesto-ck

Unfortunately, there have been no new discoveries

从 Windows 版邮件https://go.microsoft.com/fwlink/?LinkId=550986发送

发件人: Ernesto La @.> 发送时间: 2023年10月10日 13:47 收件人: @.> 抄送: @.>; @.> 主题: Re: [Shopify/shopify-app-template-node] Could not validate request HMAC (Issue #1296)

Hi, any update on this, I have the same issue and I tried different solutions but nothing works

― Reply to this email directly, view it on GitHubhttps://github.com/Shopify/shopify-app-template-node/issues/1296#issuecomment-1754431599, or unsubscribehttps://github.com/notifications/unsubscribe-auth/A5GNBTG24LSNTJJFZKX4BMDX6TOOTAVCNFSM6AAAAAA4G52KW2VHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMYTONJUGQZTCNJZHE. You are receiving this because you authored the thread.Message ID: @.***>

yanglee2421 avatar Oct 10 '23 15:10 yanglee2421

To ensure that a webhook was sent from Shopify, you need to verify its authenticity by calculating a digital signature. Shopify includes a header x-shopify-hmac-sha256 with the request, which contains a base64-encoded HMAC-SHA256 hash of the request body. This hash is generated using the secret key you set in your webhook settings.

You can find more details here.

Here's an improved example in JavaScript:

function verifyWebhook(data, hmacHeader, CLIENT_SECRET) {
  const encoder = new TextEncoder();
  const keyData = encoder.encode(CLIENT_SECRET);

  return crypto.subtle.importKey('raw', keyData, { name: 'HMAC', hash: 'SHA-256' }, false, ['sign'])
    .then(key => crypto.subtle.sign('HMAC', key, data))
    .then(signature => {
      const calculatedHmac = btoa(String.fromCharCode(...new Uint8Array(signature)));
      return calculatedHmac === hmacHeader;
    });
}



const clonedRequest = request.clone()
const hmac = clonedRequest.headers.get('x-shopify-hmac-sha256')

if (!hmac)
  return new Response('not shopify request', { status: 401 })

const CLIENT_SECRET = "YOUR_SHOPIFY_SECRET"
const body = await clonedRequest.arrayBuffer()
const isValid = await verifyWebhook(body, hmac, CLIENT_SECRET)

if (!isValid)
  return new Response('Invalid Signature/HMAC', { status: 401 })

// Continue with your logic

// return shopify 200 response
return new Response('OK', { status: 200 })

This code performs the following steps:

  1. Imports the client secret key for HMAC signing.
  2. Signs the request body with the imported key.
  3. Converts the signature to a base64 string.
  4. Compares the calculated HMAC with the HMAC received in the request header.
  5. Returns a 401 response if the HMAC verification fails or a 200 response if it succeeds.

I hope this helps! Let me know if you have further questions.

rameardo avatar Jul 21 '24 20:07 rameardo