In my first approach of setting this up for a client, I've come across this nasty pitfall: To generate the correct HMAC digest out of the webhook request body and the secret key, we need the requests raw body. Next.js gives you the option to deactivate the default body parser. This requires that we handle the http.IncomingMessage
Stream ourselves and extract the requests Buffer
from within. We will use a library called raw-body
to do exactly this job for us.
import getRawBody from 'raw-body'
export default async function (req, res) {
// We need to await the Stream to receive the complete body Buffer
const body = await getRawBody(req)
// ...
}
// We turn off the default bodyParser provided by Next.js
export const config = {
api: {
bodyParser: false,
},
}
Now that we got the raw body Buffer
stored in a variable, we digest the raw body into a hmac hash using our secret key and compare it with the X-Shopify-Hmac-SHA256
header string in the end. Since the HMAC header is base64 encoded, we need to encode our digest as well. If the digest and the header are equal, the webhook can be deemed valid.
To create a hmac digest, Node provides us with the crypto
module and its crypto.createHmac()
method. We import the module into our API route and feed in the data we prepared in the previous steps. For more information on the crypto module, have a look at the official documentation.
import getRawBody from 'raw-body'
import crypto from "crypto"
export default async function (req, res) {
// We need to await the Stream to receive the complete body Buffer
const body = await getRawBody(req)
// Get the header from the request
const hmacHeader = req.headers['x-shopify-hmac-sha256']
// Digest the data into a hmac hash
const digest = crypto
.createHmac('sha256', process.env.SHOPIFY_SECRET)
.update(body)
.digest('base64')
// Compare the result with the header
if (digest === hmacHeader) {
// VALID - continue with your tasks
res.status(200).end()
} else {
// INVALID - Respond with 401 Unauthorized
res.status(401).end()
}
}
// We turn off the default bodyParser provided by Next.js
export const config = {
api: {
bodyParser: false,
},
}
Keeping the data transfers concerning pricing and inventory safe is certainly a critical feature to headless e-commerce in general. With just a few lines of code you're able to leverage the validation built into Shopify webhook system in your serverless functions.