hyprstack hyprstack - 6 months ago 131
Node.js Question

Nodejs - Expressjs - Verify shopify webhook

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

Answer

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