Hello all,
I’m having an issue where my app’s uninstall webhook. I’m not sure if it’s because my app isn’t currently public, I copied the implementation of my other public app that I’ve tested and it works on that app. The behavior I’m expecting is a log from my server saying “webhook delivery response 200” to fire when I uninstall my app on my development store. Currently, nothing happens when I uninstall the app. No logs, no updates to my database. It’s as though the webhook is just not firing.
NOTE: If uninstall webhooks don’t fire on development stores or development/non-public apps then that answers my question. If not-
I have a webhook subscription in my TOML file:
[[webhooks.subscriptions]] uri = "https://APP_NAME.herokuapp.com/webhooks" topic = "APP_UNINSTALLED"
I also have this webhook section in my shopify.server.js file:
webhooks: {
APP_UNINSTALLED: {
deliveryMethod: DeliveryMethod.Http,
callbackUrl: "/webhooks",
},
CUSTOMERS_DATA_REQUEST: {
deliveryMethod: DeliveryMethod.Http,
callbackUrl: "/webhooks",
},
CUSTOMERS_REDACT: {
deliveryMethod: DeliveryMethod.Http,
callbackUrl: "/webhooks",
},
SHOP_REDACT: {
deliveryMethod: DeliveryMethod.Http,
callbackUrl: "/webhooks",
},
},
hooks: {
afterAuth: async ({ session }) => {
await shopify.registerWebhooks({ session });
},
},
future: {
unstable_newEmbeddedAuthStrategy: true,
removeRest: true,
v3_webhookAdminContext: true,
},
...(process.env.SHOP_CUSTOM_DOMAIN
? { customShopDomains: [process.env.SHOP_CUSTOM_DOMAIN] }
: {}),
});
...
export const registerWebhooks = shopify.registerWebhooks;
I tried manually firing an uninstalled webhook and it successfully delivered (so endpoint is correct).
Here’s my webhook file in case it’s that (unlikely, it’s delivering fine)
import { authenticate } from "../shopify.server";
import db from "../db.server";
import { Prisma } from "@prisma/client";
export const action = async ({ request }) => {
const { topic, shop, session, admin, payload } = await authenticate.webhook(
request
);
console.log(`Received webhook: ${topic} for shop: ${shop}`);
console.log('Request headers:', Object.fromEntries(request.headers.entries()));
if (!admin && topic !== "APP_UNINSTALLED") {
console.log("No admin context available for non-uninstall webhook");
throw new Response();
}
switch (topic) {
case "APP_UNINSTALLED":
console.log("Processing APP_UNINSTALLED webhook");
try {
if (session) {
console.log("Session found, looking up user");
const user = await db.user.findUnique({
where: { shopDomain: shop }
});
if (user?.pixelId) {
console.log(`Deleting web pixel with ID: ${user.pixelId}`);
await admin.graphql(
`mutation {
webPixelDelete(id: "${user.pixelId}") {
userErrors {
code
field
message
}
}
}`
);
}
console.log("Updating user and deleting sessions");
await Promise.all([
db.session.deleteMany({ where: { shop } }),
db.user.update({
where: { shopDomain: shop },
data: {
deletedAt: new Date(),
pixelId: null
}
})
]);
console.log("Uninstall cleanup completed successfully");
}
return new Response(null, { status: 200 });
} catch (error) {
if (error instanceof Prisma.PrismaClientKnownRequestError && error.code === 'P2025') {
console.log("No records found to delete - this is okay");
return new Response(null, { status: 200 });
} else {
console.error("Error during APP_UNINSTALLED webhook:", error);
return new Response("Internal server error", { status: 500 });
}
}
case "CUSTOMERS_DATA_REQUEST":
console.log("Received CUSTOMERS_DATA_REQUEST webhook");
return new Response("We don't collect customer data.", { status: 200 });
case "CUSTOMERS_REDACT":
console.log("Received CUSTOMERS_REDACT webhook");
return new Response(null, { status: 200 });
case "SHOP_REDACT":
console.log("Processing SHOP_REDACT webhook");
await db.session.deleteMany({ where: { shop } });
return new Response(null, { status: 200 });
default:
console.log(`Received unhandled webhook topic: ${topic}`);
return new Response("Unhandled webhook topic", { status: 404 });
}
};
It’s just when I uninstall the app on one of my development stores, it doesn’t fire the uninstalled webhook. I really don’t know what I did to make this not work. Help appreciated!!