skip to Main Content

There are several SO posts, but years old, and there is no documentation that I can find on how to use nodejs and express to validate a webhook from Shopify for nodejs in 2023. My issue, I know, is in getting the request body in the same format that Shopify used to create their hmac.

How do you correctly get the req body to create the local hash to compare against the hmac from Shopify?

import express from 'express'
import cors from 'cors'
import * as crypto from 'crypto'
import bodyParser from 'body-parser'

const app = express()
app.use(cors({ origin: true }))

app.post('/order/create', bodyParser.text({ type: 'application/json' }), async (req, res) => {
  try {
    const hmac = req.header('X-Shopify-Hmac-Sha256')
    const topic = req.header('X-Shopify-Topic')
    const shop = req.header('X-Shopify-Shop-Domain')
    const secret = await shopifySharedSecret()

    if (!secret) {
      throw Error('Check logs.')
    }

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

    if (hash !== hmac) {
      throw Error('hmac validation failed')
    }

    res.send({
      processed: true,
    })
  } catch (error) {
    res.send({})
  }
})

2

Answers


  1. Chosen as BEST ANSWER

    Needed to add an augment to access req.rawBody.

    import express from 'express'
    import cors from 'cors'
    import * as crypto from 'crypto'
    
    declare module 'http' {
      interface IncomingMessage {
        rawBody: unknown
      }
    }
    
    app.post('/ingest', async (req, res) => {
      try {
        const hmac = req.header('X-Shopify-Hmac-Sha256')
        const topic = req.header('X-Shopify-Topic')
        const shop = req.header('X-Shopify-Shop-Domain')
    
        if (!hmac || !topic || !shop) {
          res.send({})
          return
        }
    
        const secret = await shopifySharedSecret()
    
        if (!secret) {
          throw Error('Check logs.')
        }
    
        const hash = crypto.createHmac('sha256', secret).update(String(req.rawBody)).digest('base64')
    
        if (hash !== hmac) {
          logger.info('hmac validation failed')
          res.send({})
          return
        }
    
        // Now can safely use webhook's req.body, which is json.
    
        res.send({})
      } catch (error) {
        res.status(403).send({})
      }
    })
    

  2. I believe the issue is that you use digest('hex') instead of digest('base64').

    The Shopify docs says :

    Each webhook request includes a base64-encoded X-Shopify-Hmac-SHA256
    header

    So you need to use the same encoding.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search