Close

Sign up to Gadget

Handling webhook echoes in Gadget

-

About

Use Gadget’s built-in Record API to handle dirty tracking and avoid infinite looping in your webhook effects that update Shopify resources.

Problem

Solution

Result

Handling webhook echoes in Gadget

Harry Brundage
September 1, 2022

If you’re building an 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 <inline-code>product/create<inline-code> and <inline-code>product/update<inline-code> 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:


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 <inline-code>product/update<inline-code> webhook as a result of the changes you make.

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 <inline-code>product/updated<inline-code> webhook, you’ll always get another webhook from that update, retriggering the cycle over and over.

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 <inline-code>record<inline-code> 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 <inline-code>record.changed<inline-code> API to ask if the description has changed, and only run our API update when it has:


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 <inline-code>record.changed<inline-code> 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.

If you have questions or comments, please join us on Discord to discuss!

Interested in learning more about Gadget?

Join leading agencies making the switch to Gadget and experience the difference a fullstack platform can make.

Keep reading

No items found.
Keep reading to learn about how it's built

Under the hood

We're on Discord, drop in and say hi!
Join Discord
Bouncing Arrow