Issues with Subscribing to Webhooks in Shopify Embedded App

I’m trying to subscribe to the orders/updated webhook from a Shopify embedded app, but I’m encountering an issue where all webhook requests are failing according to the Partner Dashboard insights. Here’s what I have done so far:

  1. Configured the shopify.app.toml file:

    [[webhooks.subscriptions]]
    topics = [ "orders/updated" ]
    uri = "/webhooks"
    
  2. Set up the POST endpoint to handle the webhook:

    app.post('/webhooks', express.text({ type: '*/*' }), async (req, res) => {
      const { valid, topic, domain } = await shopify.api.webhooks.validate({
        rawBody: req.body,
        rawRequestBody: req,
        rawResponse: res,
      });
    
      if (!valid) {
        return res.status(400);
      }
    
      if (topic === 'orders/create' || topic === 'orders/updated') {
        // await this._orderRoute.syncOrdersHandler(req, res);
        console.log('Order updated');
      }
    });
    

Despite setting this up, the Partner Dashboard shows that all webhook requests fail.

Can someone help me identify the problem and guide me on how to fix this issue?

You probably need to provide the full URL not just /webhooks in the uri

1 Like

But Shopify documentation says this:
Shopify recommends using the relative URL "/webhooks" if you are using your app URL as your HTTPS delivery endpoint. This is because your app URL will update every time you run shopify app deploy.
https://shopify.dev/docs/apps/build/webhooks/subscribe/get-started?framework=remix&deliveryMethod=https

@Nirmal_Sankalana, have you requested access to protected customer data?
Both orders/create and orders/updated webhooks require access to it.
Here is the doc

1 Like

yes. from Shopify side They send post requests. but 100% fails

1 Like

Can you track down why it fails? Look at dashboard details, logs, maybe debug info.
I would still follow @Luke 's suggestion and use a complete URL as in the example. i.e. /webhooks/app/orders-updated

1 Like

Could be worth checking.

This did happen with me.
Shopify automatically adds a trailing slash (/) as the end of webhook URL.

Can you check once if the webhook calls are hitting /webhooks/ instead of /webhooks and how does your app handle trailing slash?

3 Likes

Hi Felix and all,
Thanks for your support in figuring out my issues. One problem is that the webhook base URL I subscribed to (via Toml)(fig1) and my current base URL are different because the Cloudfare tunnel URL changes every time I rerun the app. Is there any way to address this problem?

@Nirmal_Sankalana I would recommend using relative URI’s in your app configuration (shopify.app.toml). We detail how to do that here.

The Cloudflare tunnel URL’s will change every time you run shopify app dev and so having absolute URL’s won’t work super well at least while you are testing.

1 Like

@Nirmal_Sankalana If you need a non-changing URL for your webhook localhost URLs, you can look at localtunnel solutions that allow permanent URLs, even when the process has stopped and started.

The ones I’ve used are:

  • Hookdeck CLI - free and no account required (by the company I work for, Hookdeck). Built for asynchronous (e.g. webhooks) and not for serving websites.
  • localtunnel - free with no account required. Use the --subdomain flag to request a subdomain.
  • ngrok with a paid plan (personal plan is currently 8USD)

There’s a comprehensive list in the awesome tunning GitHub repo.

2 Likes

This is so insightful. Thank you so mutch @leggetter

1 Like

@Nirmal_Sankalana, I also highly recommend using Cloudflare Tunnel for local development to get static URLs.
It is worth to check @Kirill_Platonov’s blog:

1 Like

Now, I’ve configured everything, but webhook delivery fails with a 404 error according to the Partner Dashboard. The Cloudflare tunnel is correctly set up, and the console logs show no errors.

Here’s my setup:
Webhook Endpoint in index.js

app.post('/webhooks', express.text({ type: '*/*' }), async (req, res) => {
  const { valid, topic, domain } = await shopify.api.webhooks.validate({
    rawBody: req.body,
    rawRequestBody: req,
    rawResponse: res,
  });

  if (!valid) {
    console.error('Invalid webhook signature');
    return res.status(400).send('Invalid webhook signature');
  }

  if (topic === 'orders/create' || topic === 'orders/updated') {
    console.log('Order updated');
  }

  res.status(200).send('Webhook received');
});

Shopify App Initialization

const shopify = shopifyApp({
  api: {
    apiVersion: LATEST_API_VERSION,
    restResources,
    billing: billingConfig,
  },
  auth: {
    path: '/api/auth',
    callbackPath: '/api/auth/callback',
  },
  webhooks: {
    path: '/webhooks',
    webhooks: [
      {
        topic: 'orders/create',
        deliveryMethod: DeliveryMethod.Http,
        callbackUrl: '/webhooks',
      },
      {
        topic: 'orders/updated',
        deliveryMethod: DeliveryMethod.Http,
        callbackUrl: '/webhooks',
      },
    ],
  },
  hooks: {
    afterAuth: async ({ session }) => {
      await shopify.registerWebhooks({ session });
    },
  },
  sessionStorage: new SQLiteSessionStorage(DB_PATH),
});

shopify.app.toml

[access_scopes]
# Learn more at https://shopify.dev/docs/apps/tools/cli/configuration#access_scopes
scopes = "read_customer_events,read_customers,read_orders,write_orders,write_pixels,write_products"

[auth]
redirect_urls = [
  "https://volunteers-corner-monsters-skins.trycloudflare.com/auth/callback",
  "https://volunteers-corner-monsters-skins.trycloudflare.com/auth/shopify/callback",
  "https://volunteers-corner-monsters-skins.trycloudflare.com/api/auth/callback"
]

[webhooks]
api_version = "2024-10"

  [[webhooks.subscriptions]]
  topics = [ "orders/updated" ]
  uri = "/webhooks"

Despite this setup, the webhook requests fail with a 404 error. I’ve verified that the webhook URL is the same as the Cloudflare tunnel URL and matches the application_url in shopify.app.toml. The app runs without errors, but webhook requests never reach the /webhooks endpoint.

Here’s a screenshot of the Partner Dashboard showing the webhook failures:

Steps I’ve Taken

  1. Verified the Cloudflare tunnel URL is correct.
  2. Confirmed the /webhooks endpoint exists.
  3. Checked the Partner Dashboard, which indicate a 404 error(in the previous setup I got 503).
  4. Logged incoming requests to the /webhooks endpoint, but nothing is received.

I’ve been stuck on this issue for over two weeks. Could someone help me debug or spot what I might be missing? Any advice would be greatly appreciated!

PS: I sent a post request to my other app endpoint (/api/orders/sync) via tunnel URL, and it hit the endpoint. But when I sent it using webhook route /webhooks it didn’t even hit the endpoint
Then I changed my webhook URL endpoint to /api/webhooks Then I received the the post request with this error:

10:43:50 │               web-backend │ [shopify-api/INFO] Receiving webhook request
10:43:50 │               web-backend │ [shopify-app/ERROR] Failed to process webhook: Error: No HTTP webhooks registered for topic ORDERS_UPDATED

Just reviewed everything here. You are defining your webhooks twice once inside the shopify.app.toml and once inside the shopifyApp() function.
The docs here reference that if you are setting up your webhooks in the toml you don’t need to define them in your shopifyApp shopify-app-js/packages/apps/shopify-api/docs/guides/webhooks.md at main · Shopify/shopify-app-js · GitHub

That will be making it hard to tell which set is failing. I would first start by removing those.

Secondly the relative path in the toml file, looks fine. Have you set the application url as well?
If your cloudflare tunnel url changes, you will need to deploy your shopify.app.toml as otherwise it will be pointing at the wrong url.

1 Like

yeah…
this happen to me for another use case, the app install - auth flow process, the trailing / was the issue

2 Likes

Running into the first issue here as well. I’m defining webhooks via a relative path /webhooks in the shopify.app.toml file. Since I’m using cloudflare tunnels during local development, the application URLs update every time I run the app. So the URL for webhook subscriptions in the Active Version of the app in the Partner Dashboard become out of sync with the updated application URL in my shopify.app.toml file. Shopify then delivers webhooks to the wrong endpoint, which is why you see the failed webhooks that show an out of sync endpoint in the first screenshot.

My work around for this is to run my application via the shopify app dev command, which updates the application URLs in my app configuration. Then, in a separate terminal, I deploy the updated app configuration via shopify app deploy. This ensures my application URLs in my local app configuration match the webhook endpoints in the Active Version of my app in the Partner Dashboard. Now when Shopify delivers webhooks, they are successfully delivered to the correct development endpoints.

Generally speaking, this seems to work for me during local development since 1) my app is also not public yet, and 2) I’m only doing this for a development environment (I plan to host permanent urls with their own copies of the Shopify app for other environments).

I do hate having to run two commands when I run the app though, so I’ll probably look into the static cloudflare tunnel URLs solution posted by @remy727.

If anyone has any feedback on doing this, definitely let me know. I believe it shouldn’t be a problem to release many versions of a development app like this…? I am also curious if this is expected behavior, or, if the shopify-cli, when running shopify app dev, should or could update where Shopify sends webhooks to during local development based on the updated application urls defined in the toml file?? @eytan-shopify

Thanks!

Yeah, that is a good work around that I use as well in my workflow.

We are working on a better experience such that when you run shopify app dev it won’t be destructive as it relates to URL’s. Stay tuned!

2 Likes