I am trying to verify the hmac
code sent from a shopify
webhook
on a dev environment. However shopify
will not send a post request for a webhook
to a non live endpoint, so I am using requestbin to capture the request and then use postman
to send it to my local webserver.
From shopify documentation, I seem to be doing everything right and have also tried applying the method used in node-shopify-auth verifyWebhookHMAC function. But none of this has worked so far. The codes are never a match. What am I doing wrong here?
My code to verify the webhook:
function verifyWebHook(req, res, next) {
var message = JSON.stringify(req.body);
//Shopify seems to be escaping forward slashes when the build the HMAC
// so we need to do the same otherwise it will fail validation
// Shopify also seems to replace '&' with \u0026 ...
//message = message.replace('/', '\\/');
message = message.split('/').join('\\/');
message = message.split('&').join('\\u0026');
var signature = crypto.createHmac('sha256', shopifyConfig.secret).update(message).digest('base64');
var reqHeaderHmac = req.headers['x-shopify-hmac-sha256'];
var truthCondition = signature === reqHeaderHmac;
winston.info('sha256 signature: ' + signature);
winston.info('x-shopify-hmac-sha256 from header: ' + reqHeaderHmac);
winston.info(req.body);
if (truthCondition) {
winston.info('webhook verified');
req.body = JSON.parse(req.body.toString());
res.sendStatus(200);
res.end();
next();
} else {
winston.info('Failed to verify web-hook');
res.writeHead(401);
res.end('Unverified webhook');
}
}
My route which receives the request:
router.post('/update-product', useBodyParserJson, verifyWebHook, function (req, res) {
var shopName = req.headers['x-shopify-shop-domain'].slice(0, -14);
var itemId = req.headers['x-shopify-product-id'];
winston.info('Shopname from webhook is: ' + shopName + ' For item: ' + itemId);
});
If you log into your Partner account, choose an app, and go into the 'App Setup' tab, in the 'Webhooks' section on that page will be where you set up URLs that you want GDPR webhooks to be sent to. Then when a GDPR request is triggered on any shop that has your app installed, it'll be sent to you.
Creating a webhook subscription using the Shopify admin interface. Now that you have your webhook URL, the next step is to create a webhook subscription on Shopify. On your Shopify admin dashboard, navigate to Settings → Notifications → (scroll down to) Webhooks .
I do it a little differently -- Not sure where I saw the recommendation but I do the verify in the body parser. IIRC one reason being that I get access to the raw body before any other handlers are likely to have touched it:
app.use( bodyParser.json({verify: function(req, res, buf, encoding) {
var shopHMAC = req.get('x-shopify-hmac-sha256');
if(!shopHMAC) return;
if(req.get('x-kotn-webhook-verified')) throw "Unexpected webhook verified header";
var sharedSecret = process.env.API_SECRET;
var digest = crypto.createHmac('SHA256', sharedSecret).update(buf).digest('base64');
if(digest == req.get('x-shopify-hmac-sha256')){
req.headers['x-kotn-webhook-verified']= '200';
}
}}));
and then any web hooks just deal with the verified header:
if('200' != req.get('x-kotn-webhook-verified')){
console.log('invalid signature for uninstall');
res.status(204).send();
return;
}
var shop = req.get('x-shopify-shop-domain');
if(!shop){
console.log('missing shop header for uninstall');
res.status(400).send('missing shop');
return;
}
The body parser in express does not handle BigInt well, and things like order number which are passed as integer get corrupted. Apart from that certain values are edited such as URLs are originally sent as "https://...", which OP also found out from the other code.
To solve this, do not parse the data using body parser and instead get it as raw string, later on you can parse it with json-bigint to ensure none of it has been corrupted.
Although the answer by @bknights works perfectly fine, it's important to find out why this was happening in the first place.
For a webhook I made on the "order_created" event from Shopify I found out that the id of the request being passed to the body was different than what I was sending from my test data, this turned out to be an issue with body-parser in express which did not play nice with big integers.
Ultimately I was deploying something to Google cloud functions and the req already had raw body which I could use, but in my test environment in Node I implemented the following as a separate body parser as using the same body parser twice overwrote the raw body with JSON
var rawBodySaver = function (req, res, buf, encoding) {
if (buf && buf.length) {
req.rawBody = buf.toString(encoding || 'utf8');
}
}
app.use(bodyParser.json({verify: rawBodySaver, extended: true}));
Based on this answer
I later on parse the rawBody using json-bigint for use in code elsewhere as otherwise some of the numbers were corrupted.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With