Topics covering webhook creation & management, event handling, Pub/Sub, and Eventbridge, in Shopify apps.
Having a hard time validating the Webhook with node js (using express.js)
My middleware before handling the request with a typical express router.
app.use('/webhook',
bodyParser.json({
limit: '50mb',
verify: function(req, res, buf) {
req.rawbody = buf;
}
}),
routes.webhook
);
The payload seems to be received properly with valid headers and payload, But something might be wrong in the validation code.
const message = JSON.stringify(req.rawbody);
const digest = crypto.createHmac('SHA256', config.getShopifySecretKey())
.update(message)
.digest('base64');
return callback(digest === req.headers['X-Shopify-Hmac-Sha256']);
Any help would be greatly appreciated!
Anyone?
I'm also having this issue, my latest search is this gist
https://gist.github.com/andjosh/5c4f0244914adfd312e4
If I'm reading it right is says to use bodyParser to read the body as text rather than application/json -I guess that is what you are doing with the rawbody read???
You can find a working implementation here, in shopify-express.
To learn more visit the Shopify Help Center or the Community Blog.
I'm a bit lost with this - I'vetried getting the hmac using the req body in a node session using the commands as outlined but can't figure out how the req body gets converted to rawdata?
I also tried using https://www.npmjs.com/package/express-shopify-webhooks but it just keeps giving me 403 errors
Is there a document showing how shopify generates the hmac? why is is done differently from the OAuth hmac ?
I got it working for my app. Here is a summary of the actions I took:
In my main app.js I add the raw body to the req with bodyparser.json for any route that starts with '/webhooks'
app.use(bodyParser.json({
type:'*/*',
limit: '50mb',
verify: function(req, res, buf) {
if (req.url.startsWith('/webhooks')){
req.rawbody = buf;
}
}
})
);
then in my routes file I'm adding a validation function to the webhooks/app/uninstalled route:
router.post('/webhooks/app/uninstalled',validateWebhook, appcontroller.uninstalled);
This is the function that performs the validation, if the calculated hmac matches what is sent in the header then the next() function is called, otherwise send a forbidden status 403
function validateWebhook (req,res,next){
generated_hash = crypto
.createHmac('sha256', config.SHOPIFY_SHARED_SECRET)
.update(Buffer.from(req.rawbody))
.digest('base64');
if (generated_hash == req.headers['x-shopify-hmac-sha256']) {
next()
} else {
res.sendStatus(403)
}
}
Do you have this working with HapiJs ? I'm stucked for the last two days with it. Here's what I have now:
newOrder: { payload: { output: 'data', parse: false }, pre: [ { method: (request, reply) => { const hmac = request.headers['x-shopify-hmac-sha256']; let generatedHash = crypto.createHmac('sha256', utils.SHOPIFY_API_SECRET) .update(request.payload.toString()) .digest('base64'); if (generatedHash == request.headers['x-shopify-hmac-sha256']) { console.log("VALIDATED") } else { console.log("ALWAYS ENTERS HERE") } } } ], handler: function(request, reply) { reply().code(200); }, auth: false, notes: 'Shopifys new order webhook', tags: ['api'], id: 'newOrder' }
HapiJs version is 14.0.0
doesn't work !
const rawBody = await getRawBody(request); return { BadRequestError: request aborted...
what is shopify requirement with request.body ??? Can you share a sample request
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');
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; };
My problem is that I'm using the wrong key.
Using webhook secret key instead of using Shopify API secret key lol
@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.
@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; };
const verified = await verify( req.headers['x-shopify-hmac-sha256'], req.rawBody // takes the raw body extracted before bodyparsing );
app.use( bodyParser.json({ verify: (req, res, buf) => { req.rawBody = buf; }, }) );
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}`))
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)
}
})