App development
Shopify Events vs webhooks: field-level triggers and custom GraphQL payloads
Shopify's next-generation Events replace fixed-payload webhook topics with subscriptions that declare which fields must change and what GraphQL shape to return. Here's how they differ, and when to migrate.
Bas Lefeber
Founder, learnshopify.dev · July 30, 2026 · 6 min read
Every Shopify app that reacts to store data eventually writes the same unhappy function. You subscribe to products/update, you receive the entire product on every change, and because the payload does not tell you what changed, you diff it against a copy you stored last time to work out whether the one field you actually care about moved. On a catalog of any size, the overwhelming majority of those deliveries are noise you paid to receive, parse, store, and discard.
Shopify's next-generation Events are built to delete that function. On July 21, 2026, at DotDev, they gained metafield triggers and a batch of new topics, which is the point at which they start covering real production use cases. This post is about what actually changes in your app, and how to decide when to move.
Status check before you plan a migration
Events is in developer preview on the unstable API version and covers a subset of topics. Webhooks remain the correct choice for production today and are not deprecated. Use Events to prototype and to shape your roadmap, not to carry a customer's order pipeline this quarter.
The core difference in one sentence
A webhook subscription says "tell me when a product changes" and gets a fixed payload. An Event subscription says "tell me when this specific field on a product changes, and when you do, run this GraphQL query and send me the result".
That moves three responsibilities from your handler to the platform: deciding whether a delivery is relevant, deciding what data to include, and telling you which field moved.
| Aspect | Webhooks | Events |
|---|---|---|
| Configured in | shopify.app.toml or the GraphQL Admin API | shopify.app.toml only |
| Topic format | topics = ["products/update"] | topic = "Product" + actions = ["update"] |
| Field filtering | include_fields trims the payload after the fact | triggers decide whether you get a delivery at all |
| Payload shape | Fixed | A GraphQL query you write |
| What changed? | Work it out yourself | fields_changed array, with paths |
| Coverage | All Shopify resources | Developer preview subset |
The row that matters most is the third one. include_fields on a webhook makes the payload smaller; it does not stop the delivery. triggers on an Event is pre-qualification at the source: if the field did not change, nothing is sent, and your endpoint never wakes up.
What a subscription looks like
Events are declared in shopify.app.toml, and only there. A price-tracking subscription reads roughly like this:
[events]api_version = "unstable" [[events.subscription]]handle = "price_sync"topic = "Product"actions = ["update"]triggers = ["product.variants.price", "product.variants.compareAtPrice"]uri = "/api/events/price_tracker" query = """query priceSync($productId: ID!, $variantsId: ID!) { productVariant(id: $variantsId) { id price compareAtPrice sku } product(id: $productId) { id title status }}"""query_filter = "product.status:'ACTIVE'"Each field is doing a specific job:
handleis a unique identifier scoped to your app, which means you can have several subscriptions on the same topic for different purposes instead of one overloaded endpoint that switches on payload contents.topicmaps to a GraphQL type from the Admin API (Product,Customer), not to a slash-separated string.actionsare standardised tocreate,update, anddelete.triggersis the field-level pre-qualification, written in dot notation. Omit it and you get every change to the topic, which is webhook behaviour.queryis standard GraphQL defining the payload you want back, subject to complexity limits. This is where you stop receiving 40KB of product you did not ask for.query_filterruns after the query and suppresses the delivery conditionally. Fields referenced here have to appear in the query, which is whyproduct { status }is selected above even though the app mostly cares about price.
Read the two filter stages as a pipeline
There are two chances to drop a delivery: triggers (before anything is queried, based on which fields moved) and query_filter (after the query, based on the resulting values). Push work into triggers wherever you can, since it is the cheaper of the two and it never touches your endpoint.
The payload tells you what changed
This is the part that removes stored state from your app. The delivery carries your query result, the variables that were resolved, and an explicit list of the fields that triggered it:
{ "data": { "productVariant": { "id": "gid://shopify/ProductVariant/456", "price": "24.99" }, "product": { "status": "ACTIVE" } }, "fields_changed": [ "product[id: 'gid://shopify/Product/123'].variants[id: 'gid://shopify/ProductVariant/456'].price" ], "query_variables": { "productId": "gid://shopify/Product/123", "variantsId": "gid://shopify/ProductVariant/456" }}Look at fields_changed. It is not a field name, it is a path with IDs resolved along it, so on a nested change you know which variant of which product moved without walking the payload to find out. The shadow copy of every product you were keeping purely to compute diffs stops earning its keep.
What July 21 actually added
Two things, and the second is the reason to pay attention now.
More topics. Apps can now subscribe to Order, Collection, InventoryItem, InventoryShipment, and Location, alongside the existing Product, Customer, and ProductVariant coverage. Inventory and location coverage in particular is what makes Events viable for the fulfilment and ERP-sync category of app.
Metafield change triggers. You can now trigger on changes to specific metafields, by namespace and key, across Product, Order, Customer, Collection, and Location. ProductVariant metafields come through the Product topic. Both $app metafields and regular metafields work, and normal access control applies: your app has to have access to a metafield to subscribe to it or receive it in a query.
Why metafield triggers are the interesting one
Metafields are where apps put their own data. Before this, an app that needed to react to its own custom field had to subscribe to every update of the parent resource and diff, which is the single most common source of wasted webhook volume in the ecosystem. Subscribing to two targeted metafields instead of all product updates is not an optimisation, it is a different order of magnitude.
How to decide what to do
Events is preview software, so the honest recommendation is not "migrate." It is "find out what you would migrate, and stop building things that Events will make obsolete." Three concrete moves:
- Audit your subscriptions for diff-only topics. For every webhook topic your app subscribes to, ask what fraction of deliveries lead to an actual action. Any topic where the answer is under a few percent is a subscription that exists only to detect a change, and it is a direct Events candidate.
- Stop building payload-diffing infrastructure. If you are about to add a stored-snapshot table so you can compute what changed, that table has a defined shelf life now. Weigh a simpler interim approach against it.
- Prototype one subscription on the unstable version. Pick the noisiest topic you have, write the equivalent Event subscription, and measure the delivery volume difference on a development store. That number is your migration business case, and you will want it written down before you argue for the engineering time.
Keep both mechanisms in mind for the long run, too. Webhooks still cover every Shopify resource and support flexible delivery destinations; Events cover a preview subset with far more precision. They coexist in the same shopify.app.toml, and Shopify's own docs frame them as complementary rather than as a straight replacement. The realistic end state for most apps is Events for the high-volume topics where precision pays, and webhooks for the long tail.
Learn this properly · free lesson
App vs theme vs Function: which tool for which job
Choosing where logic lives, an app reacting to events, a theme, or a Function, is the decision that shapes everything after it. Work through the real tradeoffs on a concrete build. Free lesson, no signup.
Try this lesson — freeThe through-line worth taking from this release is not the TOML syntax, which will change. It is that Shopify is moving app integration from "receive everything, sort it out downstream" to "declare precisely what you need." Declarative subscriptions are cheaper to run, easier to reason about, and considerably easier for an AI agent to generate correctly, because the intent is in the configuration instead of buried in a handler. That pattern is going to keep showing up.
Frequently asked questions
What is the difference between Shopify Events and webhooks?
A webhook subscribes to a topic string like products/update and delivers a fixed payload on every change, leaving your app to work out what changed. An Event subscription declares a GraphQL topic and action, field-level triggers that decide whether a delivery happens at all, and a custom GraphQL query defining the payload shape. The delivery also includes a fields_changed array, so you do not need to diff payloads to detect what moved.
Do Shopify Events replace webhooks?
Not yet, and not entirely. Events is in developer preview on the unstable API version and covers a subset of topics, while webhooks cover every Shopify resource and support flexible delivery destinations. Both are configured in the same shopify.app.toml and can coexist. Webhooks remain the right choice for production today.
Can Shopify Events trigger on metafield changes?
Yes, as of July 21, 2026. You can add targeted metafield triggers for a specific namespace and key on Product, Order, Customer, Collection, and Location topics, with ProductVariant metafields delivered through the Product topic. Both $app metafields and regular metafields are supported, and your app must have access to a metafield to subscribe to it or query it.
Which topics do Shopify Events support?
The July 2026 update added Order, Collection, InventoryItem, InventoryShipment, and Location to the existing Product, ProductVariant, and Customer coverage. It remains a developer-preview subset rather than full parity with webhook topics, so check the current documentation before planning around a specific resource.
How do you configure a Shopify Event subscription?
In shopify.app.toml only, unlike webhooks which can also be created through the GraphQL Admin API. An [[events.subscription]] block sets a unique handle, a topic matching an Admin API GraphQL type, actions from create/update/delete, optional dot-notation triggers for field-level filtering, a delivery uri, an optional GraphQL query defining the payload, and an optional query_filter that suppresses deliveries based on the query results.
Start free
Ready to become a Shopify developer?
You just read how it works. Now write it yourself: real tickets from a live store, in an editor where the storefront updates as you type. Module 1 is free, no card.
Free · No credit card · Your first win in minutes
About the author
Bas Lefeber, Founder, learnshopify.dev
Bas builds learnshopify.dev, where developers learn production-grade Shopify theme development against a live storefront. He writes about Liquid, theme architecture, and the parts of the job that still matter now that AI writes the code.
Keep going in the curriculum
