403 Forbidden response when trying to process webhook

403 Forbidden response when trying to process webhook

evan4747
Visitor
1 0 1

Issue summary

I'm sure this is an error on my end, but I am having trouble getting my Shopify app to process webhooks for the orders/paid event after the app has not been opened through my Shopify dashboard for a day. I used Shopify's example product-reviews-sample-app as the skeleton for my server.js. The app works exactly as intended and processes my graphql requests as long as the app has recently been opened.

Actual behavior

I am getting a 403 response "Failed to process webhook: Error: No webhook is registered for topic orders/paid." I have registered the webhook using 'accessMode: "offline"' and have setup a custom storage method to retrieve the offline access token from a postgresql database so I am not quite sure what the issue is.

 

 

import "@babel/polyfill";
import dotenv from "dotenv";
import "isomorphic-fetch";
import createShopifyAuth, { verifyRequest } from "@shopify/koa-shopify-auth";
import Shopify, { ApiVersion } from "@shopify/shopify-api";
import Koa from "koa";
import next from "next";
import Router from "koa-router";
import { enqueueInvLineItemUpdateJob } from "./jobs/inventory-update-by-line-item";

import { SqlSessionStorage } from "./app-session";

dotenv.config();
const port = parseInt(process.env.PORT, 10) || 8081;
const dev = process.env.NODE_ENV !== "production";
const app = next({
  dev,
});
const handle = app.getRequestHandler();

Shopify.Context.initialize({
  API_KEY: process.env.SHOPIFY_API_KEY,
  API_SECRET_KEY: process.env.SHOPIFY_API_SECRET,
  SCOPES: process.env.SCOPES
    ? process.env.SCOPES.split(",")
    : "read_orders",
  HOST_NAME: process.env.HOST.replace(/https:\/\//, ""),
  API_VERSION: ApiVersion.October20,
  IS_EMBEDDED_APP: true,
  // This should be replaced with your preferred storage strategy
  SESSION_STORAGE: SqlSessionStorage,
});

// Storing the currently active shops in memory will force them to re-login when your server restarts. You should
// persist this object in your app.
const ACTIVE_SHOPIFY_SHOPS = {};

app.prepare().then(async () => {
  const server = new Koa();
  const router = new Router();
  server.keys = [Shopify.Context.API_SECRET_KEY];

  server.use(
    createShopifyAuth({
      accessMode: "online",
      prefix: "/online",
      async afterAuth(ctx) {
        // Online access mode access token and shop available in ctx.state.shopify
        const { shop } = ctx.state.shopify;

        // Redirect to app with shop parameter upon auth
        // ctx.redirect(`/?shop=${shop}&host=${host}`);
        ctx.redirect(
          `https://${shop}/admin/apps/${process.env.SHOPIFY_API_KEY}`
        );
      },
    })
  );

  server.use(
    createShopifyAuth({
      accessMode: "offline",
      prefix: "/offline",
      async afterAuth(ctx) {
        // Access token and shop available in ctx.state.shopify
        const { shop, accessToken, scope } = ctx.state.shopify;

        ACTIVE_SHOPIFY_SHOPS[shop] = shop;

        let response = await Shopify.Webhooks.Registry.register({
          shop,
          accessToken,
          path: "/webhooks",
          topic: "APP_UNINSTALLED",
          webhookHandler: async (topic, shop, body) =>
            delete ACTIVE_SHOPIFY_SHOPS[shop],
        });

        if (!response.success) {
          console.log(
            `Failed to register APP_UNINSTALLED webhook: ${response.result}`
          );
        }

        response = await Shopify.Webhooks.Registry.register({
          shop,
          accessToken,
          path: "/webhooks",
          topic: "ORDERS_PAID",
          webhookHandler: async (topic, shop, body) => {
            enqueueInvLineItemUpdateJob(shop, JSON.parse(body));
          }
        });

        if (!response.success) {
          console.log(
            `Failed to register ORDERS_PAID webhook: ${response.result}`
          );
        }

        // Redirect to app with shop parameter upon auth
        ctx.redirect(`/online/auth/?shop=${shop}`);
      },
    })
  );

  const handleRequest = async (ctx) => {
    await handle(ctx.req, ctx.res);
    ctx.respond = false;
    ctx.res.statusCode = 200;
  };

  const verifyIfActiveShopifyShop = (ctx, next) => {
    const { shop } = ctx.query;

    // This shop hasn't been seen yet, go through OAuth to create a session
    if (ACTIVE_SHOPIFY_SHOPS[shop] === undefined) {
      ctx.redirect(`/offline/auth?shop=${shop}`);
      return;
    }

    return next();
  };

  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}`);
    }
  });

  router.post(
    "/graphql",
    verifyRequest({ returnHeader: true }),
    async (ctx) => {
      await Shopify.Utils.graphqlProxy(ctx.req, ctx.res);
    }
  );

  router.get("(/_next/static/.*)", handleRequest); // Static content is clear
  router.get("/_next/webpack-hmr", handleRequest); // Webpack content is clear
  // Embedded app Next.js entry point
  router.get("(.*)", verifyIfActiveShopifyShop, handleRequest);

  server.use(router.allowedMethods());
  server.use(router.routes());
  server.listen(port, () => {
    console.log(`> Ready on http://localhost:${port}`);
  });
});

 

 

Any help is greatly appreciated, and I can provide additional information if needed.

Replies 3 (3)

dacosta51
Visitor
1 0 2

I have the same problem and I can't find a solution. With the Shopify sample webhook application only works the UNINSTALL webhook, if you change it by any other webhook it doesn't work. I

samuelkaizen
Shopify Partner
12 0 4

Getting same issue too - my webhooks were working fine before this 

ssagli
Shopify Partner
18 3 7

My Uninstall webhook works as expected, but I am unable to get 

APP_SUBSCRIPTIONS_UPDATE (app_subscriptions/update) working. It was working fine with API version 2021-10. I am getting 403 Forbidden when I checked the Shopify partner dashboard for the webhook log info. Any help would be appreciated!