How do people handle custom webhooks subscription in the Remix template?

tititi
Shopify Partner
1 0 0

I'm able to successfully create a Google PubSub subscription in the after_auth block in shopify.server.js.

 

However, the issue is that the subscription is lost if I restart my server for any reason.

 

I would like to be able to register subscriptions when the server starts and not have to refresh a page or re-install the app.

 

 

Is there a way to register code somewhere that runs only once when the server starts or does anyone have any recommendations for dealing with this type of thing?

 

 

Thanks.

Reply 1 (1)

jeff121
Shopify Partner
1 0 0

Your domain url changes on every server restart and your webhook subscription is only triggered upon installation. This problem only occurs on local development. At the moment, my solution that I could come up with was to have a command script to delete the previous webhook and re-subscribe all webhooks again whenever I restart my server.

 

import { json } from "@remix-run/node";
import { cors } from "remix-utils/cors";
import { authenticate, unauthenticated } from "../shopify.server";
import dbConnect from "@/db";
import { Session } from "@/modules/session/session.model";

export const loader = async ({ request, response }) => {
    await authenticate.admin(request);    

    return cors(request, response);
}

const webhookTopics = [
  'app/uninstalled',
  'orders/updated',
  // 'orders/create',
  'products/update',
  'products/delete',
  'inventory_levels/update',
  'inventory_items/create',
];

export const action = async ({ request }) => {
  await dbConnect()
    try {
      const storeList = await Session.find()
      for (const store of storeList) {
        const { admin } = await unauthenticated.admin(store.shop);
       
        const subscribedWebhook = await admin.rest.get({
          path: `webhooks.json`
        });
        const subscribedWebhookResponse = await subscribedWebhook.json();
        let deleteWebhook = false

          // Delete Existing Webhook, Temporary comment incase we need to resubscribe
          deleteWebhook = true
          for (const currentWebhook of subscribedWebhookResponse.webhooks) {
            const deletedWebhook = await admin.rest.delete({
              path: `webhooks/${currentWebhook.id}.json`
            });
            const deletedWebhookResponse = await deletedWebhook.json();
            console.log(`Deleted webhook ${currentWebhook.topic}`)
          }

          for (const topic of webhookTopics) {
            let exist = false
            subscribedWebhookResponse.webhooks.map(existingWebhook => {
              if (existingWebhook.topic === topic && deleteWebhook == false) {
                exist = true
              }
            })
            if (exist) {
              console.log(`Skip existing webhook ${topic}`)
              continue
            }

            const webhookEndpoint = `${process.env.URL}/webhooks`;
            const webhookData = {
              webhook: {
                topic: topic,
                address: webhookEndpoint,
                format: 'json',
              },
            };

           
            const response = await admin.rest.post({
              path: `webhooks.json`,
              data: webhookData
            });
            const responseData = await response.json();

            if (response.ok) {
              console.log(`Webhook for ${topic} successfully created with ID: ${responseData.webhook.id}`);
            } else {
              console.error(`Failed to create webhook for ${topic}. Error: ${JSON.stringify(responseData)}`);
            }
          }
      }

      return cors(request, json({ status: 'success', data: "" }));
    } catch (err) {
        console.error(err);
        return cors(request, json({
          status: 'error',
          message: 'error'
        }, {
          status: 500
        }));
    }
};