node.jsshopifyhmacwebhooks

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

Solution

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