Webhooks Not Firing as Expected in Shopify App

Topic summary

A developer encountered issues with Shopify webhooks not firing from the admin panel, despite successful manual triggers via Shopify CLI. The APP_UNINSTALLED webhook worked correctly, but others like CUSTOMERS_DATA_REQUEST failed to fire when triggered through normal Shopify admin actions.

Troubleshooting Steps Taken:

  • Registered webhooks in shopify.server.ts with proper configuration
  • Reset the app without resolution
  • Verified manual CLI triggers work as expected

Solution Provided:
A community member suggested verifying webhook registration using Shopify’s GraphQL API (webhookSubscriptions query) or a provided CURL command to check webhook configuration accuracy.

Current Status - Resolved:
Webhooks are now firing successfully with 200 responses visible in the Partner Dashboard.

New Issue Identified:
During development, running shopify app dev creates a new application URL, causing webhooks to fire for both the new development URL and the previously deployed production URL. This results in duplicate webhook deliveries—one succeeding (200) and one failing (503) for the old URL.

Final Guidance:
This dual-firing behavior is normal for development environments where domains change frequently and can be safely ignored. The issue will resolve once deployed to production with a stable domain.

Summarized with AI on November 3. AI used: claude-sonnet-4-5-20250929.

HELLO,

I am new to the Shopify community and need assistance in troubleshooting why my webhooks are not firing as expected.

I have registered the webhooks and even reset the app, but I see a message stating:
“Sending APP_UNINSTALLED webhook to app server”
This webhook gets delivered successfully. However, the other webhooks are not firing when triggered from the Shopify admin.

Interestingly, if I manually trigger them using the Shopify CLI’s shopify app webhook trigger command, they fire and function as expected.

Below is the relevant code from my shopify.server.ts file:

import "@shopify/shopify-app-remix/adapters/node";
import {
  ApiVersion,
  AppDistribution,
  DeliveryMethod,
  shopifyApp,
} from "@shopify/shopify-app-remix/server";
import { PrismaSessionStorage } from "@shopify/shopify-app-session-storage-prisma";
import { restResources } from "@shopify/shopify-api/rest/admin/2024-07";
import prisma from "./db.server";

const shopify = shopifyApp({
  apiKey: process.env.SHOPIFY_API_KEY,
  apiSecretKey: process.env.SHOPIFY_API_SECRET || "",
  apiVersion: ApiVersion.October24,
  scopes: process.env.SCOPES?.split(","),
  appUrl: process.env.SHOPIFY_APP_URL || "",
  authPathPrefix: "/auth",
  sessionStorage: new PrismaSessionStorage(prisma),
  distribution: AppDistribution.AppStore,
  restResources,
  webhooks: {
    APP_UNINSTALLED: {
      deliveryMethod: DeliveryMethod.Http,
      callbackUrl: `/webhooks`,
    },
    SHOP_REDACT: {
      deliveryMethod: DeliveryMethod.Http,
      callbackUrl: `/webhooks`,
    },
    CUSTOMERS_DATA_REQUEST: {
      deliveryMethod: DeliveryMethod.Http,
      callbackUrl: `/webhooks`,
    },
    CUSTOMERS_REDACT: {
      deliveryMethod: DeliveryMethod.Http,
      callbackUrl: `/webhooks`,
    },
  },
  hooks: {
    afterAuth({ session }) {
      console.log("After Auth");
      shopify.registerWebhooks({ session });
    },
  },

  future: {
    unstable_newEmbeddedAuthStrategy: true,
  },
  ...(process.env.SHOP_CUSTOM_DOMAIN
    ? { customShopDomains: [process.env.SHOP_CUSTOM_DOMAIN] }
    : {}),
});

export default shopify;
export const apiVersion = ApiVersion.October24;
export const addDocumentResponseHeaders = shopify.addDocumentResponseHeaders;
export const authenticate = shopify.authenticate;
export const unauthenticated = shopify.unauthenticated;
export const login = shopify.login;
export const registerWebhooks = shopify.registerWebhooks;
export const sessionStorage = shopify.sessionStorage;

Below is the relevant code from my webhooks.tsx file:

import type { ActionFunctionArgs } from "@remix-run/node";
import { authenticate } from "../shopify.server";
import db from "../db.server";

export const action = async ({ request }: ActionFunctionArgs) => {
  try {
    // Authenticate webhook and validate HMAC
    const { topic, shop, session, admin, payload } =
      await authenticate.webhook(request);
    console.log("Webhook received", topic);

    // console.log("Admin:", admin);

    // Check if the request is from an admin, unless it's for "SHOP_REDACT"
    // if (!admin && topic !== "SHOP_REDACT") {
    //   console.log("Unauthorized webhook request");
    //   return new Response("Unauthorized", { status: 401 });
    // }

    switch (topic) {
      case "APP_UNINSTALLED":
        if (session) {
          await db.session.deleteMany({ where: { shop } });
          console.log(`Sessions for shop ${shop} have been deleted.`);
        }
        return new Response("App uninstalled data cleared", { status: 200 });

      case "SHOP_REDACT":
        console.log(`Shop data for shop ${shop} has been erased.`);
        return new Response("Shop redaction processed", { status: 200 });

      case "CUSTOMERS_DATA_REQUEST":
        console.log("Customer data request received");
        return new Response("Data request received", { status: 200 });

      case "CUSTOMERS_REDACT":
        console.log("Customer data redaction received");
        return new Response("Customer redaction processed", { status: 200 });

      default:
        return new Response("Unhandled webhook topic", { status: 404 });
    }
  } catch (error) {
    console.error("Webhook error:", error);
    return new Response("Unauthorized", { status: 401 });
  }
};

I tested the CUSTOMERS_DATA_REQUEST webhook from the Shopify admin by requesting customer data through the “More Actions” option in the Customers section.

Please solve the issue.
Thank You.

hi @Siva ,

To verify whether the webhook information is correct, you can call Shopify’s API to check.

Refer the link: https://shopify.dev/docs/api/admin-graphql/2024-01/queries/webhookSubscriptions

Or you can refer CURL as below:

curl --location 'https://{yourStoreName}.myshopify.com/admin/api/2024-10/graphql.json' \
--header 'X-Shopify-Access-Token: {yourToken}' \
--header 'Content-Type: application/json' \
--data '{"query":"\nquery \n{\n  webhookSubscriptions(\n    first: 10\n    after: null\n    reverse: false\n    sortKey: CREATED_AT\n    format: JSON\n  ) {\n    edges {\n      cursor\n      node {\n        apiVersion {\n            displayName\n            handle\n            supported \n        }\n        callbackUrl\n        createdAt\n        endpoint\n        format\n        id\n        includeFields\n        legacyResourceId\n        metafieldNamespaces\n        privateMetafieldNamespaces\n        topic\n        updatedAt\n        endpoint {\n            ... on WebhookPubSubEndpoint {\n            pubSubProject\n            pubSubTopic\n            }\n        }\n        }\n    }\n    pageInfo {\n      endCursor\n      hasNextPage\n      hasPreviousPage\n      startCursor\n    }\n  }\n}\n\n","variables":{}}'

Pls note that, you need to change your store name and token in CURL.

1 Like

Thank you very much for your response, @smartive .

Currently, webhooks are being delivered successfully. However, I am facing an issue: whenever I add a webhook and deploy that version using shopify app deploy, a new version with the updated webhooks is created using the application_url available at the time of deployment. When I trigger any event, the webhook fires correctly, and I can see a 200 response in the Partner Dashboard.

The problem arises when I run shopify app dev again. Since the application_url changes in the development environment, triggering an event causes the webhook to fire for both URLs: the new one and the one from the latest deployment.

Is this behavior problematic, or can I safely ignore it?

Thank you again for your help.

You can see two consecutive 200 responses with the deployed application_url. However, once I run shopify app dev again, I get a 200 response for the new application_url and a 503 response for the previously deployed one.

Hi @Siva_1 ,

For development apps running locally, the domain changes every time. You can safely ignore this issue, as it does not impact your application’s functionality. Once the app is deployed to production with a live domain, this issue will no longer occur.

1 Like