Can't verify Webhooks in Node.js! (serverless AWS lambda)

Highlighted
Shopify Partner
38 1 3

That exclamation point used in the title is not an accident. 

 

1. The official doc doesn't have an example for Node.js 

 

2. On this Shopify Partners' blog post, it says: 

 

exports.handler = (event, context, callback) => {
  var client_secret = event.client_secret;
  delete event.client_secret;

  //calculate the hash
  var calculated_hash = crypto.createHmac("sha256", client_secret).update(new Buffer(event.body, "base64")).digest("base64");

  //reject the message if the hash doesn't match
  if (event["X-Shopify-Hmac-SHA256"] != calculated_hash) {
    console.log("calculated_hash: (" + calculated_hash + ") != X-Shopify-Hmac-SHA256: (" + event["X-Shopify-Hmac-SHA256"] + ")");
    return;
  }
}

This post was specifically for an AWS lambda function. But when I generate the hash like this:

 

 

var calculated_hash = crypto.createHmac("sha256", client_secret).update(new Buffer(event.body, "base64")).digest("base64");

It's still different from the `X-Shopify-Hmac-Sha256` header value. 

 

3. I tried it with the following different ways: 

  • update(Buffer.from(event.body, "base64"))
  • update(Buffer.from(JSON.stringify(event.body), 'utf8', 'hex'))
  • update(event.body)

4. I replaced `client_secret` with the following values: 

  • "API secret key" from partners.shopify.com → Apps → App
  • "API key" from partners.shopify.com → Apps → App
  • "Access token" I got from the store when we installed the app

All the docs say it's either "client secret" or "shared secret". But I can't find any value with that exact name. Am I using the wrong secret? Or am I using a wrong method to generate the hash? 

 

Working remotely from Mongolia ~ code.zolbayar.com
0 Likes
Highlighted
Shopify Partner
38 1 3

I tried again with the following code snippet. Even the buffer lengths are different.

 

const message = querystring.stringify(map);
    const providedHmac = Buffer.from(hmac, 'utf-8');
    const generatedHash = Buffer.from(
      crypto
        .createHmac('sha256', shopifyApp.apiSecret)
        .update(message)
        .digest('hex'),
      'utf-8',
    );
    let hashEquals = false;
    try {
      hashEquals = crypto.timingSafeEqual(generatedHash, providedHmac);
    } catch (e) {
      console.log('Error while timingSafeEqual', e);
      hashEquals = false;
    }

 

Working remotely from Mongolia ~ code.zolbayar.com
0 Likes
Highlighted
Shopify Staff
Shopify Staff
1041 140 167

Hey @Zolbayar,

 

Here's an Express example (where secretKey is the API secret key), see if you can get this working then work backwards to remove Express.

 

 

const express = require('express')
const app = express()
const crypto = require('crypto')
const secretKey = '<your secret key>'
const bodyParser = require('body-parser')

app.use('/webhooks', bodyParser.raw({ type: 'application/json' }))
app.use(bodyParser.json())

app.post('/webhooks/orders/create', async (req, res) => {
  const hmac = req.get('X-Shopify-Hmac-Sha256')

  // create a hash using the body and our key
  const hash = crypto
    .createHmac('sha256', secretKey)
    .update(req.body, 'utf8', 'hex')
    .digest('base64')

  // Then compare hash to hmac.
  // ...
})

app.listen(3000, () => console.log('Example app listening on port 3000!'))

Let me know if you get stuck.

 

Notice; Out of office, replies will be delayed until my return. Thanks!
0 Likes
Highlighted
Shopify Partner
38 1 3

Hey @SBD_ ,

Thanks for replying :)

 

I did it like that and the hash differs from the hmac:

hash jVjYxZpx4qaDLjW7g62RLaOKSs2H87861p7/nWuz150=
hmac EKYVgsZJ4DQo5jBSMbPrUSGN6rGRWOQbYhKVnuwvpQ0=

I think the cause might be the following line: 

app.use('/webhooks', bodyParser.raw({ type: 'application/json' }))
app.use(bodyParser.json())

Since I'm using serverless.com to deploy to AWS lambda, I'm not sure how can I do this.

Working remotely from Mongolia ~ code.zolbayar.com
0 Likes
Highlighted
Tourist
5 0 3

Hi,

 

Did you ever happen to get this working?  I am trying to use AWS Lambda/API Gateway and like you, I am pulling my hair out trying to get the hmac to validate.

 

Thanks!

 

Alex

0 Likes
Highlighted
app.use(bodyParser.json({
verify: (req, res, buf) => {
req.rawbody = buf
}
})
);

Then you can use req.rawbody to generate hash instead of req.body

 

https://github.com/expressjs/body-parser/pull/24
https://github.com/expressjs/body-parser#verify

Co-Founder / Developer at https://merchbees.com
Merchbees Low Stock Alert - Keep track of your low stock items by email and slack
Merchbees Inventory Value - Know your inventory value in real-time
1 Like
Highlighted
Shopify Staff
Shopify Staff
1041 140 167

Hey @Zolbayar + @AlexH316,

Finally got to the bottom of this. The body contains backslashes, so some\/string becomes some/string.

 

I escaped the backslashes themselves (some\\/string) and had success with the following code:

const hash = crypto
.createHmac('sha256', secret)
.update(body, 'utf8', 'hex')
.digest('base64')

So you can manually escape the backslashes, or try to get the raw body before it's turned into a string.

 

Let me know how you go.

Notice; Out of office, replies will be delayed until my return. Thanks!
0 Likes
Highlighted
New Member
1 0 0

There is a blog post about how to make it work with node 12 lambda on aws here:

https://medium.com/@damianpieszczynski/validating-shopify-webhooks-with-aws-lambda-and-node-58a8da00...

0 Likes
Highlighted
Shopify Partner
5 0 0

When I add the bodyParser like this:

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

I get an error when I try to use the rawbody like this:

app.post('/uninstall', async (req, res) => {
    req.rawbody
};

Is there something I'm doing obviously wrong?

0 Likes
Shopify Partner
38 1 3

Hi @krumholz ,

I'm confused. Seems like you're trying to get the raw body by using the json body parser. I think you should use

bodyParser.raw([options])

instead :)

Working remotely from Mongolia ~ code.zolbayar.com
0 Likes