Webhook handler not firing

pizza123
Visitor
2 0 1

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!

Replies 13 (13)

rac146
Visitor
1 0 2

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());

 

leeshkay
Visitor
2 0 0

wow, great find!

SBD_
Shopify Staff
1829 269 405

Hey @pizza123,

Did @rac146's response help?

I just tested with a fresh app from the CLI and the handler ran. The tutorial has evolved over time, so our code might be different - are you able to post a link to an example app with the issue?

Scott | Developer Advocate @ Shopify 

pizza123
Visitor
2 0 1

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.

dan-zip
Visitor
1 0 0

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.

Sebastian_H
Shopify Partner
17 3 4

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?

SBD_
Shopify Staff
1829 269 405

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 

Sebastian_H
Shopify Partner
17 3 4

@SBD_I used the CLI. As mentioned @rac146 's answer solved my problem, but I do not understand the reason why it is relevant where to put the bodyparser declaration. And it is not documented.

SBD_
Shopify Staff
1829 269 405

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 

Sebastian_H
Shopify Partner
17 3 4

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.

HD_WORK
New Member
20 0 0

@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 ?

Nicholas_Babu
Shopify Partner
6 0 0

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

leeshkay
Visitor
2 0 0

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.