Topics covering webhook creation & management, event handling, Pub/Sub, and Eventbridge, in Shopify apps.
I'm following this tutorial to set up my app uninstall webhook: https://shopify.dev/tutorials/build-a-shopify-app-with-node-and-react/listen-for-store-events-with-w...
When I install the app, I get the message that the webhook was successfully registered.
When I uninstall the app, I get a POST request to /webhooks, as expected. For some reason, though, the function I registered in the webhookHandler property initially never seems to run. Here's the relevant code:
server.use(
createShopifyAuth({
accessMode: 'offline',
async afterAuth(ctx) {
console.log('after auth being called')
const { shop, accessToken, scope } = ctx.state.shopify;
//store accessToken in db
await save_app_info(shop, accessToken, scope);
// Your app should handle the APP_UNINSTALLED webhook to make sure merchants go through OAuth if they reinstall it
const registration = await Shopify.Webhooks.Registry.register({
shop,
accessToken,
path: "/webhooks",
topic: "APP_UNINSTALLED",
apiVersion: ApiVersion.January21,
webhookHandler: async (_topic, shop, _body) => {
console.log('webhook handler running')
console.log(_topic)
},
});
if (registration.success) {
console.log('Successfully registered webhook!');
} else {
console.log('Failed to register webhook', registration.result);
}
ctx.redirect(`/?shop=${shop}`);
},
}),
);
router.post('/webhooks', async (ctx) => {
// We'll compare the hmac to our own hash
const hmac = ctx.get('X-Shopify-Hmac-Sha256')
// Create a hash using the body and our key
const hash = crypto
.createHmac('sha256', process.env.SHOPIFY_API_SECRET)
.update(ctx.request.rawBody, 'utf8', 'hex')
.digest('base64')
// Compare our hash to Shopify's hash
if (hash === hmac) {
// It's a match! All good
console.log('Phew, it came from Shopify!')
await Shopify.Webhooks.Registry.process(ctx.req, ctx.res);
console.log(`Webhook processed with status code 200`);
res.sendStatus(200)
} else {
// No match! This request didn't originate from Shopify
console.log('Danger! Not from Shopify!')
res.sendStatus(403)
}
});
Any idea why my webhookHandler function wouldn't be firing? I expect it to be called as part of Shopify.Webhooks.Registry.process(). This code is more or less exactly what it was in the various tutorials Shopify provides so I'm not sure where I could be going wrong.
Thank you for any help you can provide!
Mr. Pizza - I actually have the exact same problem as you using the new Shopify Node API.
After digging around, it seems the koa-bodyparser is the root cause of the webhook not firing. As a workaround, I created a separate router that initializes before koa-bodyparser. If someone wants to chime in on WHY this is necessary, that would be great. Hope this helps!
// Webhook router BEFORE bodyparser
const { webhookRouter } = require('./app/webhookRoutes.js');
server.use(webhookRouter.routes());
server.use(webhookRouter.allowedMethods());
// Use module 'koa-bodyparser'
server.use(bodyParser());
wow, great find!
Hi SBD_,
Thank you for your response. I sent you a PM with my app's details.
And thank you to @rac146 for sharing your solution. I have a similar setup so I'm not sure why mine wouldn't be working.
I'm having the same problem. Is it possible to make the plugin work side by side with bodyparser? It's not ideal having to have inconsistent koa request pipelines.
Have confirmed it doesn't work with either koa-bodyparser or koa-body.
Hi @SBD_ ,
@rac146's answer helped me. But the question is still open why the order regarding bodyparser matters. I needed 6 hours to identify the root course and to get to this thread. The behaviour is so unclear.
Can you check that? That would be great. Furthermore, do you have alternatives to bodyparser that work with the cli app?
Hi @Sebastian_H
Are you following the tutorial or generating from CLI?
Can you please provide steps to replicate or a sample broken app?
Scott | Developer Advocate @ Shopify
Thanks. I just ran some tests and was able to replicate. The tutorial seems to be retired, so I created a fresh app with the CLI which includes Shopify Node API.
Shopify Node API has a `Shopify.Webhooks.Registry.process` method which reads the body and verifies the webhook for you, you don't need to import koa-body or do any hmac comparisons. Here's how to register a webhook:
const response = await Shopify.Webhooks.Registry.register({
shop,
accessToken,
path: "/webhooks",
topic: "PRODUCTS_UPDATE",
webhookHandler: async (topic, shop, body) =>
console.log('Product updated!', shop, body)
});
And here's how to process them:
router.post("/webhooks", async (ctx) => {
try {
await Shopify.Webhooks.Registry.process(ctx.req, ctx.res);
console.log(`Webhook processed, returned status code 200`);
} catch (error) {
console.log(`Failed to process webhook: ${error}`);
}
});
Note the webhookHandler in the first snippet will fire with access to the parsed body.
By using koa-body on the same route, the Shopify Node API webhook registry is unable to parse the body of the request. If you need to use koa-body in other parts of your app, parse the specific route (instead of every route):
router.post('/some-other-route', koaBody(), (ctx) => {
console.log(ctx.request.body);
});
Let me know if I've missed anything or you have any questions!
Scott | Developer Advocate @ Shopify
Hi @SBD_ ,
Thanks for taking the time again. Everything sounds understandable. Your suggestion with explicit parsing by koaBody() I find very good and I will test. This also makes the code more readable without "magic" functions.
@SBD_
Hi there,
I want to know that if want to do some functionality with the webhook body data then where should i do that
because if i use koa-bodyparser then i am unable to pass await Shopify.Webhooks.Registry.process(ctx.req, ctx.res);
and i dont use body parser then iam unable to get ctx.request.body
can you please help me with that ?
I think one needs to verify the webhook, from within the app and process that, or something like that. some disconnects in the documentation @https://shopify.dev/apps/webhooks/configuration/https#step-5-verify-the-webhook
Hey @Sebastian_H ,
Interesting question.
From what I understand, Koa's Server.use*() function is similar to Express's app.use() function.
There are 3 main objects passed to these NodeJS Middleware Libraries: Req, Res, Next. It's called middleware because these libraries/functions catch & mutate these core objects "in the middle"
When you see a library do something like: Server.use(bodyParser()), the library is usually transforming these core functions (Req,Res,Next) - perhaps they're complementing the behavior of these objects, or "supercharging them".
In this case, it seems the BodyParser library mutates the Req.body object or req.request.body object. Perhaps the Webhook Middleware function is trying to process / parse an object which has *(_unknowignly to it) already been mutated by the bodyParser middleware.