Weaverse LogoWeaverse
All Articles
Paul Phan
8 mins read

Shopify Next Generation Events: September Payload and Trigger Changes

Migrate Shopify Events to added, updated and removed change paths, explicit parent wildcards and new header rules. Updated examples for Hydrogen teams.
#shopify#hydrogen#webhooks#events#app-development
Shopify Next Generation Events: September Payload and Trigger Changes
Table of Contents

Update, September 17, 2026: Shopify changed the Events preview contract on September 16. fields_changed is now an object with added, updated and removed arrays; parent triggers need an explicit .*; subscriptions with the update action require a trigger; and two delivery headers have been removed. Classic Webhook subscriptions are unaffected.[1]

The recommendation remains: test Events in development, but keep production webhooks in place while Events is in developer preview. Shopify's current guidance still identifies Events as an unstable API with a subset of supported topics.[2]

If you copied the original examples in this article, update your payload parser first. Check parent-trigger syntax before your next app-configuration deploy. These are different migration boundaries: an existing subscription continuing to work does not mean its delivered payload keeps the old shape.[1]

What changed on September 16

1. fields_changed describes how a path changed

The old flat array identified a path, but could not distinguish an addition from a removal. The new object separates those changes:[1]

  • added: A resource or relationship was added.
  • updated: A value changed on an existing resource.
  • removed: A resource or relationship was removed.

Here is Shopify's variant-addition example, shown as payload fragments rather than complete deliveries.[1]

Before September 16, historical format only:

{
"topic": "Product",
"action": "update",
"fields_changed": [
"product[id: 'gid://shopify/Product/123'].variants[id: 'gid://shopify/ProductVariant/456']"
]
}

Current format for the same variant addition:

{
"topic": "Product",
"action": "update",
"fields_changed": {
"added": [
"product[id: 'gid://shopify/Product/123'].variants[id: 'gid://shopify/ProductVariant/456']"
],
"updated": [],
"removed": []
}
}

Notice that adding a variant is still an update on the Product, not a Product create. The action describes the root entity's lifecycle; the buckets describe what happened inside it. A price change goes in updated, while removing a variant from a surviving product puts its path in removed.[1][8]

Keep those distinctions in your processing code. Flattening all three arrays into one list discards the information this change adds.

2. Parent triggers need a terminal wildcard

A parent path that subscribes to all supported descendant fields must now end in .*. These are configuration fragments, not complete subscriptions.[1]

Old parent-trigger syntax, replace before redeploying:

triggers = [
"product.variants",
"product.options.optionValues.swatch"
]

Current parent-trigger syntax:

triggers = [
"product.variants.*",
"product.options.optionValues.swatch.*"
]

Leaf triggers do not change. product.variants.price still means variant price changes; do not append .* to it. The wildcard migration preserves matching behavior and delivery volume for equivalent subscriptions. It makes an existing subscription's breadth explicit, rather than making that subscription narrower.[1]

Shopify says existing subscriptions continue to work, but parent triggers must use the new syntax the next time you deploy shopify.app.toml.[1]

3. An update subscription must include a trigger

Subscriptions whose actions include update now require at least one trigger. Do not use an omitted or empty triggers list as your new configuration for receiving all updates. Shopify says existing subscriptions continue to work.[1]

Some reference pages and older examples still describe triggers as optional or show the flat-array payload. For these changed fields, the September 16 changelog supersedes that older guidance. The examples below use the new contract.[1]

4. Stop requiring the two removed headers

Events deliveries no longer include shopify-event-id or shopify-resource-id. Shopify directs developers to update their Shopify API packages to versions that handle the removal. Custom code must also stop reading or requiring those headers for validation.[1]

Do not remove authentication or duplicate-delivery handling. For HTTPS Events, verify Shopify-Hmac-Sha256 against the raw body before trusting it. Shopify-Webhook-Id remains the delivery identifier used for deduplication; header names are case-insensitive.[9]

Use the delivery's query_variables for the entity IDs available to that subscription, rather than assuming the removed resource-ID header is present.[5]

A current price-sync subscription

Events moves three decisions into the subscription: which field change qualifies, what data Shopify queries, and whether the query result satisfies a delivery filter. Classic webhooks already support payload trimming and state-based filters; Events adds explicit change triggers and a custom GraphQL payload.[2]

The following example keeps the original article's price-sync use case. Its leaf triggers were already valid and do not need wildcards. It combines the trigger, query and filter in one shopify.app.toml example so their relationship is clear.[1][3]

Merge these settings into your existing app configuration. Preserve other access scopes and subscriptions, and implement the receiver at the example uri. This is subscription configuration, not a complete app:

[access_scopes]
scopes = "read_products"
[events]
api_version = "unstable"
[[events.subscription]]
handle = "price_sync"
topic = "Product"
actions = ["update"]
triggers = [
"product.variants.price",
"product.variants.compareAtPrice"
]
uri = "/events/app/products"
query = """
query priceSync($productId: ID!, $variantsId: ID!) {
productVariant(id: $variantsId) {
id
price
compareAtPrice
}
product(id: $productId) {
id
title
status
}
}
"""
query_filter = "product.status:'ACTIVE'"

For this subscription:

  • A qualifying variant price or compare-at-price change can trigger delivery. An unrelated title edit alone does not qualify.[3]
  • Shopify runs the GraphQL query and places its response in data. The variant-level triggers make both $variantsId and $productId available.[5]
  • query_filter checks the query result and suppresses delivery unless product.status is ACTIVE. Fields used by the filter must be returned by the query.[3][5]

The query runs after the qualifying change. Its result is not an immutable snapshot of the instant that change occurred. Handle GraphQL errors and missing resources, and keep reconciliation for your external state; custom payloads do not make every consistency problem disappear.[5]

The same applies to filtering. A price-only subscription filtered to active products is not a complete catalog-lifecycle sync: it is not your path for removing a product from an external index when its status changes to inactive. Design separate subscriptions or reconciliation for the lifecycle changes your application needs.

Update the parser without hiding malformed deliveries

The following small helper illustrates only the new fields_changed shape. Call it after authenticating the delivery and parsing its JSON; it does not replace signature verification, full-envelope validation or delivery deduplication.[9]

export function readFieldsChanged(payload) {
const changes = payload?.fields_changed;
if (!changes || typeof changes !== "object" || Array.isArray(changes)) {
throw new TypeError("Expected object-shaped fields_changed");
}
for (const kind of ["added", "updated", "removed"]) {
if (
!Array.isArray(changes[kind]) ||
!changes[kind].every((path) => typeof path === "string")
) {
throw new TypeError(`Expected fields_changed.${kind} to be a string array`);
}
}
return changes;
}

Route each returned bucket to its corresponding application logic. Do not default malformed or missing buckets to empty arrays and then acknowledge the delivery as successfully processed. Surface the validation failure through your receiver's error-handling path.

If you replay archived deliveries, keep their legacy format explicit. An old flat array does not tell you whether a path was added, updated or removed, so blindly assigning every old path to updated invents information. The helper above intentionally rejects that old format.

What this means for Hydrogen and Weaverse storefronts

These are app-side subscriptions in shopify.app.toml, not settings you add to a Hydrogen browser client. A backend supporting a Hydrogen storefront can use Events to drive targeted work, such as refreshing an external search record or invalidating an application-owned product cache.[2]

The migration task is at that receiver and its downstream processing:

  • Cache invalidation: Preserve the affected path and entity IDs, including additions and removals. Changing the payload contract does not automatically invalidate an Oxygen or Hydrogen cache.
  • Search synchronization: Distinguish a variant being removed from a variant price changing. Test the separate product-deletion path too.
  • Query consumers: Account for errors, null resources and state changing between the event and query execution, rather than assuming every delivery contains a complete resource snapshot.[5]

For a Weaverse storefront, visual content editing and Shopify event processing remain separate concerns. Keep the event receiver's authentication, idempotency and failure recovery in the backend design. Do not replace that design with an unauthenticated storefront endpoint or an assumption that copying a subscription handles the rest.

Migration checklist

  1. Inventory Events consumers, not every webhook indiscriminately. Find parsers, validation schemas, fixtures, queue workers and replay tools that use fields_changed, plus code that requires either removed header. Classic Webhook subscriptions are outside this change.[1]
  2. Update payload handling now. Read added, updated and removed, and test each separately. Updating Shopify API packages for the header change does not rewrite your custom payload parser.[1]
  3. Review every update subscription. Provide at least one valid trigger. Add .* to parent paths before the next configuration deploy; leave leaf triggers unchanged.[1]
  4. Review deletion assumptions. Shopify's linked migration post describes removing child-cascade update Events after a root resource is deleted. Test root deletion separately from removing one variant from a surviving Product; do not rely on follow-up child updates as your only cleanup mechanism.[8]
  5. Exercise the receiver in a development store. Test addition, value change and removal; a non-matching title edit; a matching price change; duplicate delivery; invalid signature; and a GraphQL error or missing resource. Keep a test for a delivery that lacks the two removed headers but has the required authentication and delivery-ID information.[1][3][9]
  6. Keep preview and production decisions separate. Shopify still recommends Events for early testing and webhooks for production. The community post points to 2026-10 on October 1, 2026; that is a future version milestone as of this update, not evidence that Events is stable today. Recheck release status before changing versions.[2][8]

The bottom line

The useful part of Next Generation Events remains the same: select meaningful changes, receive data shaped for the job, and avoid unnecessary follow-up queries where the delivered data is sufficient.

The September update makes the contract more explicit. Preserve the new change categories, migrate parent triggers without altering leaf triggers, and remove obsolete header dependencies without weakening verification. Measure delivery reduction in your own workload instead of assuming a universal savings figure.

If you're planning the event layer behind a Hydrogen storefront, talk to us about the integration. Start with the state your storefront must keep correct, then choose subscriptions and recovery behavior around it.

Sources

[1] https://shopify.dev/changelog/updates-to-events-payloads-and-subscription-configuration [2] https://shopify.dev/docs/apps/build/events-webhooks [3] https://shopify.dev/docs/apps/build/events/get-started [5] https://shopify.dev/docs/apps/build/events/delivery-structure [8] https://community.shopify.dev/t/upcoming-changes-to-events/37537 [9] https://shopify.dev/docs/apps/build/events/verify-deliveries

Reactions

Like
Love
Celebrate
Insightful
Cool!
Thinking

Join the Discussion

Never miss an update

Subscribe to get the latest insights, tutorials, and best practices for building high-performance headless stores delivered to your inbox.

Join the community of developers building with Weaverse.