Handling Shopify webhook echoes in Gadget
Tags: shopify, webdev, programming
Published: 2022-09-19 20:43:15

If you’re building a Shopify app that needs to know about updates to data within a Shopify store, the fastest and most reliable way is to listen to Shopify’s stream of events via webhooks. Webhooks allow you to find out about changes to any Shopify store quickly and without consuming your (remarkably low) Shopify API rate limit.

Listening for webhooks generally works great, but there’s a subtle issue many developers face with webhooks once they’re up and running: echoes. If your application both listens to changes from Shopify and makes changes to Shopify, you will get notified of your own changes just the same as any others! This can cause all sorts of surprises.

Imagine an example application that automatically applies tags to a product in Shopify when the product’s description changes. To keep it simple, let’s say this application listens to the `product/create` and `product/update` webhook from Shopify, and when either arrives, it extracts 3 string tags from the product’s description. It then makes an API call back to Shopify to update the product’s tags:


```
module.exports = async ({ record, connections }) => {
    let newTags = extractTags(record.body).slice(0, 3);
    const shopifyAPI =  connections.shopify.current;
    await shopifyAPI.product.update(record.id, { tags: finalTags.join(",") });
  }
};
```

This will run a second product update to set the new tags in response to the first update. Shopify will treat this update just like any other and send webhooks out to all listeners, including your application. So, you’ll get a second `product/update` webhook as a result of the changes you make.


![state flow diagram for Shopify webhooks](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/714p6ferhh9etoismnhs.png)

This second webhook is useful if you want to record updated product information in your own database, but, it is easy to let it accidentally re-trigger the tagging business logic. This is a bug that creates an infinite loop: if you always update a product in the `product/updated` webhook, you’ll always get another webhook from that update, retriggering the cycle over and over.


![state flow diagram for Shopify webhooks](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wdbax1om8d78gopcucbh.png)

## The solution to webhook echoes

To avoid the infinite loops created by webhook echos, you must add some logic in your application to only react to the specific changes you care about in the upstream records, so that you only make updates once or twice, instead of over and over. The easiest way to do this is to use your framework’s change reporting functionality (sometimes also called dirty tracking). Gadget’s `record` object has change tracking built right into it, so you can ask the record what fields have changed as a result of an incoming webhook, and only run your important logic when the fields you care about have changed.

In the case of the product tagger, we only need to update the tags of a product when the description changes. We explicitly don’t need to update tags when the tags change. So, we can use the `record.changed` API to ask if the description has changed, and only run our API update when it has:

```
module.exports = async ({ record, connections }) => {
  // check to see if the product’s description has changed, and only run the update if it has
   if (record.changed(“body”)) { 
      let newTags = extractTags(record.body).slice(0, 3);
      const shopifyAPI =  connections.shopify.current;
      await shopifyAPI.product.update(record.id, { tags: finalTags.join(",") });
   } 
   // otherwise, do nothing
  }
};

```
This change detection breaks the cycle, and ensures that no subsequent update is made when the second webhooks arrives.  Systems other than Gadget support change detection too: Rails calls it dirty tracking and has it built right in, there’s a django egg for it, and most other ORMs have hooks that allow you to implement it yourself if you need to.

With the `record.changed` API built right into Gadget, Shopify app developers can quickly sidestep the challenges posed by webhook echoes, and focus their time on the business logic of their application.

---

Made it all the way? Hope it was helpful! Feel free to reach to us with thoughts or questions, or try out Gadget for free at [Gadget.dev](https://www.gadget.dev/?utm_source=devto&utm_medium=social&utm_campaign=webhook-echo-handling)