Fly.io: Failed to process webhook: Error: Could not validate request for topic orders/paid

Hey,

I am working a shopify app on api-version 2023-04, based almost identically on a shopify-example node app from ~9 months ago, though now appears to have vanished from the internet.

All I want to do is trigger the orders/paid webhook on my app when it’s deployed on fly.io.

My app appears to work when triggered locally, but am I listening to the webhook wrong? This version should still be supported currently right?

So this works perfectly when I trigger the webhook when running the app locally (npm run dev):

npm run shopify webhook trigger -- --topic ORDERS_PAID --api-version 2023-04 --delivery-method http --client-secret=x --address http://localhost:61989/api/webhooks

However, when deployed to fly.io and I click the Order payment’s “Send test nofitication” button to trigger the webhook and fly.io noticed the webhook is called but seemingly my setup is wrong here as none of my “ORDERS_PAID” webhook JS is running:

2023-10-15T16:54:24.579 app[x] lhr [info] webhook-handlers.js
2023-10-15T16:54:25.556 app[x] lhr [info] [shopify-api/INFO] Processing webhook request | {apiVersion: 2023-04, domain: redacted, topic: orders/paid, webhookId: 123
2023-10-15T16:54:25.568 app[x] lhr [info] [shopify-app/ERROR] Failed to process webhook: Error: Could not validate request for topic orders/paid

My webhooks file:

import shopify from "./shopify.js";
import { DeliveryMethod } from "@shopify/shopify-api";

console.log('webhook-handlers.js');

export default {
  CUSTOMERS_DATA_REQUEST: {
    deliveryMethod: DeliveryMethod.Http,
    callbackUrl: shopify.config.webhooks.path,
    callback: async (topic, shop, body, webhookId) => {
    },
  },

  CUSTOMERS_REDACT: {
    deliveryMethod: DeliveryMethod.Http,
    callbackUrl: shopify.config.webhooks.path,
    callback: async (topic, shop, body, webhookId) => {
    },
  },

  SHOP_REDACT: {
    deliveryMethod: DeliveryMethod.Http,
    callbackUrl: shopify.config.webhooks.path,
    callback: async (topic, shop, body, webhookId) => {
    },
  },

  ORDERS_PAID: {
    console.log('do something');
},
};

index.js of the app:

// @ts-check
import { join } from "path";
import { readFileSync } from "fs";
import express from "express";
import serveStatic from "serve-static";

import shopify from "./shopify.js";
import webhookHandlers from "./webhook-handlers.js";

const PORT = parseInt(process.env.BACKEND_PORT || process.env.PORT, 10);

const STATIC_PATH =
  process.env.NODE_ENV === "production"
    ? `${process.cwd()}/frontend/dist`
    : `${process.cwd()}/frontend/`;

const app = express();

// Set up Shopify authentication and webhook handling
app.get(shopify.config.auth.path, shopify.auth.begin());
app.get(
  shopify.config.auth.callbackPath,
  shopify.auth.callback(),
  shopify.redirectToShopifyOrAppRoot()
);
app.post(
  shopify.config.webhooks.path,
  shopify.processWebhooks({ 
    webhookHandlers,
  }),
);
// ...

Any suggestions on why fly.io behaves this way? And how I can ensure my JS is run on this webhook?

There are some suggestions in the docs that some domains will be banned, one post here referencing an app a user built on heroku being a banned domain. But helpfully shopify doesn’t list all banned domains, so no idea if I’m technically using a domain which might be shadow banned (*.fly.dev). Also a mild suggestion of using cloudflare tunnel, which I believe was in the example applicaiton, though can’t see any reference on how to easily set this up which I’m sure was referenced much more thoroughly back then.

Just realised I was a bit heavy handed redacting my example webhook as has sensitive info in it. Just pointing out ORDERS_PAID does actually look like this:

...  
  ORDERS_PAID: {
    deliveryMethod: DeliveryMethod.Http,
    callbackUrl: shopify.config.webhooks.path,
    callback: async (topic, shop, body, webhookId) => {
      console.log('ORDERS_PAID');
      ...

So I gave up, started a new app with remix and used a later version of the API, and, I no longer get the issue of the topic being and issue on fly.io, instead the incredibly helpful 400 response with NO elaboration on what the problem is. grr.