Solved

Cannot verify webhook HMAC in Koa

Paul91
Tourist
8 1 3

I am trying to verify my api secret for webhook requests in my Shopify app, but simply cannot get the two HMAC on the request and my token digest to match. Here is my code:

 

const verifyShopifyHook = ctx => {
  const { query } = ctx;
  const { hmac: _hmac, signature: _signature, ...map } = query;

  const orderedMap = Object.keys(map)
    .sort((value1, value2) => value1.localeCompare(value2))
    .reduce((accum, key) => {
      accum[key] = map[key];
      return accum;
    }, {});
  const message = querystring.stringify(orderedMap);

  let digest = crypto

    .createHmac("sha256", process.env.SHOPIFY_API_SECRET_KEY)
    .update(message, "utf8")
    .digest("base64");

  return digest === ctx.headers["x-shopify-hmac-sha256"];
};

I am definitely using the right tokens. Can anyone see what I am doing wrong?

Accepted Solution (1)
Paul91
Tourist
8 1 3

This is an accepted solution.

Yep, the addition of rawBody ended up being the ticket. Here is my final implementation if anyone else runs across this:
 
const verifyShopifyHook = ctx => {
  const { headers, request } = ctx;
  const { "x-shopify-hmac-sha256": hmac } = headers;
  const { rawBody } = request;

  const digest = crypto
    .createHmac("SHA256", process.env.SHOPIFY_API_SECRET_KEY)
    .update(new Buffer(rawBody, "utf8"))
    .digest("base64");

  return safeCompare(digest, hmac);
};

View solution in original post

Replies 10 (10)

SBD_
Shopify Staff
1829 269 409

Hey @Paul91,

 

I had an issue with this recently - the trick was to use the raw body (before it's turned into a string) or escape the backslashes.

 

More info: https://community.shopify.com/c/Shopify-APIs-SDKs/Can-t-verify-Webhooks-in-Node-js-serverless-AWS-la...

 

Let me know how you go.

Scott | Developer Advocate @ Shopify 

Paul91
Tourist
8 1 3

This is an accepted solution.

Yep, the addition of rawBody ended up being the ticket. Here is my final implementation if anyone else runs across this:
 
const verifyShopifyHook = ctx => {
  const { headers, request } = ctx;
  const { "x-shopify-hmac-sha256": hmac } = headers;
  const { rawBody } = request;

  const digest = crypto
    .createHmac("SHA256", process.env.SHOPIFY_API_SECRET_KEY)
    .update(new Buffer(rawBody, "utf8"))
    .digest("base64");

  return safeCompare(digest, hmac);
};
Not applicable

How I can use your function within the api?

theschoolofux
Shopify Partner
10 0 3

Had the same issue. Using request.rawBody instead of request.body helped. Also note that the secret key you should use for verification is not your Shopify API secret key, but the key under Webhooks section in your admin panel (<yourstore>.myshopify.com/admin/settings/notifications) where it says "All your webhooks will be signed with [SHOPIFY_WEBHOOKS_KEY] so you can verify their integrity".

 

import Router from "koa-router";
import koaBodyParser from "koa-bodyparser";
import crypto from "crypto";

...

koaServer.use(koaBodyParser()); 

...

koaRouter.post(
    "/webhooks/<yourwebhook>",
    verifyShopifyWebhooks,
    async (ctx) => {
      try {
        ctx.res.statusCode = 200;
      } catch (error) {
        console.log(`Failed to process webhook: ${error}`);
      }
    }
);

...

async function verifyShopifyWebhooks(ctx, next) {
  const generateHash = crypto
    .createHmac("sha256", process.env.SHOPIFY_WEBHOOKS_KEY)
    .update(ctx.request.rawBody, "utf-8")
    .digest("base64");

  if (generateHash !== shopifyHmac) {
    ctx.throw(401, "Couldn't verify Shopify webhook HMAC");
  } else {
    console.log("Successfully verified Shopify webhook HMAC");
  }
  await next();
}
Sergei Golubev | Devigner @ The School of UX | schoolofux.com
PTP
Shopify Partner
14 0 2

@theschoolofux Seeing a `shopifyHmac is not defined` error. Where are you defining the shopifyHmac?

theschoolofux
Shopify Partner
10 0 3

@PTP These are request headers:

 

const shopifyTopic = ctx.request.headers["x-shopify-topic"];
const shopifyDomain = ctx.request.headers["x-shopify-shop-domain"];
const shopifyHmac = ctx.request.headers["x-shopify-hmac-sha256"];

 

 

 

Sergei Golubev | Devigner @ The School of UX | schoolofux.com
Andy96
Visitor
2 0 0

 

router.post("/webhooks", verifyShopifyWebhooks, async (ctx) => {
  try {
    ctx.res.statusCode = 200;
    await Shopify.Webhooks.Registry.process(ctx.req, ctx.res);
    console.log(`Webhook processed, returned status code 200`);
  }catch (error) {
    console.log(`Failed to process webhook: ${error}`);
  }

});

 


Help me please! 

even when hmac is valid `proccess` never fires. What can be wrong?

 

re. Seems like it's related to Koabodyparser. But I don't get how to avoid that.

banned
dipeshbeckham
Shopify Partner
13 0 1

@Andy96 

were you able to fix this?

manishbsn
New Member
5 0 0
const shopifyTopic = ctx.request.headers["x-shopify-topic"];
const shopifyDomain = ctx.request.headers["x-shopify-shop-domain"];
const shopifyHmac = ctx.request.headers["x-shopify-hmac-sha256"];
 
where do we have to put these lines of code
manishbsn
New Member
5 0 0
Click to expand...
 

@theschoolofux 

where do we need to set these headers

const shopifyTopic = ctx.request.headers["x-shopify-topic"];
const shopifyDomain = ctx.request.headers["x-shopify-shop-domain"];
const shopifyHmac = ctx.request.headers["x-shopify-hmac-sha256"];