Topics covering webhook creation & management, event handling, Pub/Sub, and Eventbridge, in Shopify apps.
We're moving the community! Starting July 7, the current community will be read-only for approx. 2 weeks. You can browse content, but posting will be temporarily unavailable. Learn more
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?
Solved! Go to the solution
This is an accepted solution.
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); };
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.
Let me know how you go.
Scott | Developer Advocate @ Shopify
This is an accepted solution.
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); };
How I can use your function within the api?
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();
}
@theschoolofux Seeing a `shopifyHmac is not defined` error. Where are you defining the shopifyHmac?
@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"];
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.
where do we need to set these headers