Webhook Validation with Node / Express.js

Highlighted
Shopify Partner
4 0 0

Hello there, how did you get it to work? 

 

I am using the below atm. But couldn't get it to work. 


 
let jsonString = JSON.stringify(req.body);

const generated_hash = crypto
.createHmac('sha256', secret)
.update(jsonString)
.digest('base64');
 
0 Likes
Highlighted
New Member
2 0 0

No luck yet with this issue. Tried this suggested solution but didn't work either.

 

 

0 Likes
Highlighted
Tourist
4 0 1

I found a solution that works, given you can get the raw body buffer from the request.

In express & bodyparser you can do that with this piece of code:

app.use(
  bodyParser.json({
    verify: (req, res, buf) => {
      req.rawBody = buf;
    },
  })
);

Then generate the hmac signature and compare it by converting the raw bodybuffer to a string, signing it, and finaly comparing the output to the header.

const verify = async (hmacHeader, rawBody) => {
  if (!hmacHeader || !rawBody) {
    return false;
  }

  const data = rawBody.toString('utf8');
  const hmac = await crypto
    .createHmac('sha256', VERIFICATION_KEY)
    .update(data)
    .digest('base64');

  // safe comparator.
  let valid = true;
  hmacHeader.split('').forEach((element, index) => {
    if (element !== hmac[index]) {
      valid = false;
    }
  });
  return valid;
};
1 Like
Highlighted
Tourist
9 0 1

My problem is that I'm using the wrong key.

Using webhook secret key instead of using Shopify API secret key lol

0 Likes
Highlighted
Shopify Partner
5 0 0

@ken_smas or @itsgookit, have either of you posted your solutions on Github or would you mind sharing them here? I'm having trouble with getting the hmac's to be the same. I'm partially wondering if one of my node packages is causing an issue.

0 Likes
Highlighted
Tourist
4 0 1

@krumholz   Make certain that you are using the correct key first and foremost. you get the correct key by using the key in the "shared secret" field in a private app.

secondly, the hmac signature has to be done with the raw request body, and not the parsed json data. any json parsing will replace the body, making validation imposible.

The code i use is the same shown in my last post.

here is a list of npm packages invoked on the request before the point of verification:

express,
bodyParser,
cors,
crypto

verification functions:

const verify = async (hmacHeader, rawBody) => {
  if (!hmacHeader || !rawBody) {
    return false;
  }

  const data = rawBody.toString('utf8');
  const hmac = await crypto
    .createHmac('sha256', VERIFICATION_KEY)
    .update(data)
    .digest('base64');

// safe comparator.
let valid = true;
hmacHeader.split('').forEach((element, index) => {
  if (element !== hmac[index]) {
    valid = false;
    }
  });
  return valid;
};
 
And here is the invocation of the function:
 
const verified = await verify(
  req.headers['x-shopify-hmac-sha256'],
  req.rawBody    // takes the raw body extracted before bodyparsing
);
 
Saving the raw body, must be done before any other bodyparser type function. so put this right after cors, but before everything else.
app.use(
  bodyParser.json({
    verify: (req, res, buf) => {
    req.rawBody = buf; 
    },
  })
);
 
hope it helps.
0 Likes
Highlighted
Shopify Partner
1172 34 218

Thanks to @ken_smas . I finally made it work.  Is async/await is a best practice here?

const express = require("express")
const bodyParser = require("body-parser")
const crypto = require("crypto")
const secretKey  = "xxxxx"
const app = express()
const PORT = 3000

 
 app.use(
  bodyParser.json({
    verify: (req, res, buf) => {
      req.rawBody = buf;
    },
  })
);
 
app.post("/webhook", (req, res) => {
 
  const data = req.rawBody
  const hmacHeader  = req.get('X-Shopify-Hmac-Sha256')

  const hmac = crypto
    .createHmac('sha256', secretKey)
    .update(data, 'utf8', 'hex')
    .digest('base64');

    if (hmacHeader === hmac) {
      console.log('Phew, it came from Shopify!')
      res.sendStatus(200)
      console.log(req.body)

      }else{
      console.log('Danger! Not from Shopify!')
      res.sendStatus(403)
    }

})

app.listen(PORT, () => console.log(` Server running on port ${PORT}`))

 

Available for hiring. Reach me at lixon@ecommercestudio.in
0 Likes
Highlighted
Shopify Partner
1172 34 218

updated the code based on https://github.com/Shopify/shopify-express/blob/master/middleware/webhooks.js

app.post("/webhook", async(req, res) => {
 
  const data = await req.rawBody
  const hmacHeader  = await req.get('X-Shopify-Hmac-Sha256')

  try{
    const hmac = crypto
    .createHmac('sha256', secretKey)
    .update(data, 'utf8', 'hex')
    .digest('base64');

      if (hmacHeader === hmac) {
        console.log('Phew, it came from Shopify!')
        res.sendStatus(200)
        console.log(req.body)

      }else{
        console.log('Danger! Not from Shopify!')
        res.sendStatus(403)
      }
  }catch(error){
    console.log(error)
  }
   
})

 

Available for hiring. Reach me at lixon@ecommercestudio.in
0 Likes