--- title: Events description: Subscribe to resource-level changes (for example, Product) with granular triggers and custom GraphQL payloads. Configure subscriptions in your app configuration file and receive deliveries only when the fields you care about change. api_version: unstable api_name: events source_url: html: https://shopify.dev/docs/api/events/latest md: https://shopify.dev/docs/api/events/latest.md --- # Events Subscribe to topics with field-level triggers, custom GraphQL Admin API queries, and query filters. Define subscriptions in your `shopify.app.toml` and receive deliveries only when the data you care about changes. **Developer preview:** Events is in developer preview, available today for a subset of topics you can find in this section in the sidebar. Use Events for early testing ahead of a stable release and broader topic coverage. For topics not yet supported, use webhooks alongside Events in the same [`shopify.app.toml`](https://shopify.dev/docs/apps/build/cli-for-apps/app-configuration#app-configuration-file-example). As Events expands topic coverage, it will become the primary subscription mechanism. ## Getting started Use Events to subscribe to changes in merchant data and receive deliveries when they occur. With Events, you choose which GraphQL Admin field changes trigger a delivery, what data each payload contains, and whether to send the delivery at all. Triggers and queries are independent from one another. You can query anything available in the GraphQL Admin API from within a `query` in response to any trigger. Events require Shopify CLI version 3.92 or higher. Run `shopify version` to check, and see [upgrade instructions](https://shopify.dev/docs/api/shopify-cli#upgrade-shopify-cli) if needed. [Guide - Create an Events subscription](https://shopify.dev/docs/apps/build/events/get-started) ## shopify.app.toml ```toml [events] api_version = "unstable" [[events.subscription]] handle = "my_product_events" topic = "Product" actions = ["update"] triggers = ["product.variants.price"] uri = "https://your-app.com/events" query = """ query product_details($productId: ID!) { product(id: $productId) { id status } } """ query_filter = "product.status:'ACTIVE'" ``` *** ## How it works When a [qualifying change](https://shopify.dev/docs/apps/build/events#how-it-works) happens in a store, Shopify sends a delivery (a signed JSON notification) to your app’s endpoint. You declare a subscription in `shopify.app.toml` to tell Events which resource to watch, which changes should trigger a delivery, and what data each delivery includes. The four core concepts are: * **[Configuration](#configuration):** Declare which resource and operations to watch, and where to send deliveries. * **[Delivery filtering](#delivery-filtering):** Use `triggers` to narrow which field changes qualify, and `query_filter` to suppress deliveries based on current data values. * **[Delivery structure](#delivery-structure):** Shape the delivered payload with a GraphQL `query` and understand what each delivery contains. * **[Verify deliveries](#verify-deliveries):** Verify HMAC signatures and ignore duplicate deliveries using `Shopify-Webhook-Id`. If you’re migrating from webhooks, see [how concepts map](https://shopify.dev/docs/apps/build/events/migrate-from-webhooks). *** ## Configuration Events are configured directly in your `shopify.app.toml`. Subscriptions are defined under the `[events]` table, with each subscription in an `[[events.subscription]]` entry. Subscriptions defined in your app configuration file apply to all shops where your app is installed. We recommend that the `api_version` reflects the latest supported API version. See [Manage Events subscriptions](https://shopify.dev/docs/apps/build/events/subscribe) for a setup walkthrough. ### Top-level properties The top-level table for all Events configuration contains the following properties: ##### `api_version` required The API version used for Events subscriptions. ### Per-subscription properties Each entry defines a single subscription containing the following properties: ##### `handle` required A unique name for this subscription. The handle is included in deliveries so you can identify which subscription triggered the delivery. Alphanumeric characters, `_`, and `-` only. Maximum 50 characters. ##### `topic` required The resource to subscribe to (for example, `Product`). Topics correspond to GraphQL Admin objects. Some child objects don't have a separate topic. For example, `ProductVariant` changes surface under the `Product` topic. See the sidebar for the full list of supported topics. ##### `actions` required An array of operations to listen for. Valid values are `create`, `update`, and `delete`. `create` and `delete` apply to the topic entity itself. `update` covers changes on the topic entity and, where the model routes them there, on owned children (for example, `ProductVariant` fields under `Product`). ##### `uri` required The endpoint URL where deliveries are sent. ##### `triggers` optional An array of field paths that specify which `update` changes should trigger a delivery. `triggers` applies only to `update` actions. It has no effect on `create` or `delete`. Multiple triggers use implicit OR logic: a delivery is sent when any listed field changes. Omit to receive deliveries on any `update` change to the resource. For example, `"product.variants.price"` triggers a delivery only when a variant's price changes. Only source-of-truth fields can be used as triggers. Calculated or derived fields (like `product.description`, which is derived from `descriptionHtml`) and auto-updated timestamps (like `product.updatedAt`) are not valid triggers. Deprecated fields can still be valid trigger paths. Deprecation signals eventual removal from the API but doesn't remove the underlying change signal. Refer to each topic's trigger list for available fields. See [Filter Events deliveries](https://shopify.dev/docs/apps/build/events/delivery-filtering#triggers) for field path syntax and constraints. ##### `query` optional A GraphQL query that defines the shape of the data returned in the delivery payload's `data` field. Your query is not limited to the topic's root resource. You can use any query root available in the [GraphQL Admin API](https://shopify.dev/docs/api/admin-graphql), and you can include multiple root nodes in a single query. IDs flow upward from the changed entity through its parent hierarchy and become available as GraphQL variables. Variable names follow the `$entityId` pattern in camelCase (for example, `$productId`, `$variantsId`). A variant-level change provides both `$variantsId` and `$productId`; a product-level change provides only `$productId`. Variables used in your query must be available given your selected `triggers`, and Shopify validates this at subscription time. If you omit `query`, the payload won't include `data`. **Note:** Shopify runs your `query` and includes the result in the payload. Because the query runs after the qualifying change occurs, results reflect the state of the data at query execution time, not necessarily at the time of the change. See [Events delivery structure](https://shopify.dev/docs/apps/build/events/delivery-structure#custom-queries) for query examples and variable reference. ##### `query_filter` optional A filter expression that gates delivery based on the results of your `query`. Deliveries are only sent when the filter matches. Supports `AND` and `OR` operators for combining conditions (for example, `"product.variants.price:>100 AND product.status:'ACTIVE'"`). The fields referenced in the filter must be included in your `query`, and filter paths must start from the same root field as your `query` (for example, if your query starts at `productVariant`, use `productVariant.price` in the filter, not `product.variants.price`). Filters evaluate against current values, not change deltas. If `query_filter` is set, `query` must also be configured. **Note:** Don't include a space between `:` and the value in a filter expression. `product.status:'ACTIVE'` is valid. `product.status: 'ACTIVE'` (with a space after the colon) fails. See [Filter Events deliveries](https://shopify.dev/docs/apps/build/events/delivery-filtering#query-filters) for filter syntax and examples. ## shopify.app.toml ```toml [events] api_version = "unstable" [[events.subscription]] handle = "my_product_events" topic = "Product" actions = ["create", "update", "delete"] uri = "https://your-app.com/events" triggers = [ "product.variants.price", "product.variants.compareAtPrice" ] query = """ query my_product_events($productId: ID!) { product(id: $productId) { id title status } } """ query_filter = "product.status:'ACTIVE'" ``` *** ## Delivery filtering Two mechanisms control when deliveries fire and what they contain. * `triggers` narrows which `update` changes qualify before any query runs. Without it, a delivery fires for any field change on the subscribed topic. Adding `triggers` restricts deliveries to changes on those specific field paths. Multiple paths use implicit OR logic. `triggers` has no effect on `create` or `delete`. * `query_filter` evaluates after the `query` result is ready and suppresses the delivery if the expression doesn't match. Filters evaluate against current values, not change deltas. `query_filter` requires `query` to also be configured, and filter paths must reference fields your query returns. See [Filter Events deliveries](https://shopify.dev/docs/apps/build/events/delivery-filtering) for field path syntax, filter expressions, and constraints. ## shopify.app.toml ```toml [[events.subscription]] handle = "price-sync" topic = "Product" actions = ["update"] triggers = ["product.variants.price"] uri = "https://your-app.example.com/events" query = """ query price_change($productId: ID!, $variantsId: ID!) { product(id: $productId) { status } productVariant(id: $variantsId) { price } } """ query_filter = "product.status:'ACTIVE' AND productVariant.price:>100" ``` *** ## Delivery structure Shopify delivers a JSON payload to your `uri`. HTTPS endpoints receive an HTTP POST with request headers. AWS EventBridge and Google Pub/Sub deliver the payload through their respective transport formats. The body always includes metadata about the change. Add a `query` to include custom data from the GraphQL Admin API. ### Payload Every Events delivery payload contains the following fields, depending on whether a custom `query` is defined: ##### Always present * `topic`: The resource name for this delivery (for example, `Product`). * `action`: The operation that occurred: `create`, `update`, or `delete`. * `handle`: The subscription handle from your TOML configuration, useful for routing when you have multiple subscriptions on the same topic. * `fields_changed`: An array of dot-notation paths with embedded GIDs showing exactly which fields changed and on which entities. * `query_variables`: The entity IDs that were resolved for this delivery in a flat key-value format, matching the variables available to your `query`. ##### Present only when a `query` is configured * `data`: The result of your GraphQL `query`. * `errors`: A GraphQL errors array returned when your `query` fails to execute (for example, referencing a removed field or a deleted resource). Each delivery captures a single change, but `fields_changed` can include multiple paths when several fields changed together. Child entities like variants trigger their parent topic's delivery, so a product with many variants can generate high volume. Use `triggers` to narrow which field changes fire deliveries, and `query_filter` to further gate what Shopify sends. When a payload exceeds the channel size limit, Shopify delivers a download URL instead. See [Large payloads](https://shopify.dev/docs/apps/build/events/delivery-structure#large-payloads) for payload limits and handling guidance. ## Payload ```json { "topic": "Product", "action": "update", "handle": "my_product_events", "data": { "product": { "id": "gid://shopify/Product/123", "status": "ACTIVE" }, "productVariant": { "id": "gid://shopify/ProductVariant/456", "price": "24.99", "compareAtPrice": "29.99", "inventoryQuantity": 100 } }, "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" } } ``` ### Headers HTTPS deliveries include the following request headers (using the `Shopify-` prefix). Treat header names as case-insensitive in your implementation. * `Shopify-Topic`: The topic name (for example, `Product`). * `Shopify-Action`: The action (`create`, `update`, or `delete`). * `Shopify-Handle`: The subscription handle. * `Shopify-Api-Version`: The API version for this subscription. * `Shopify-Resource-Id`: The GID of the root resource. * `Shopify-Event-Id`: A unique ID shared across all deliveries produced by the same merchant action. * `Shopify-Webhook-Id`: A unique composite key per delivery, used to [detect and ignore duplicate deliveries](https://shopify.dev/docs/apps/build/events/verify-deliveries#ignoring-duplicates). * `Shopify-Triggered-At`: Timestamp of when Shopify triggered the delivery. * `Shopify-Shop-Domain`: The `myshopify.com` domain of the store that triggered the change. * `Shopify-Hmac-Sha256`: Base64-encoded HMAC-SHA256 signature (HTTPS deliveries only). The `User-Agent` header is set to `Shopify-Webhooks` on all deliveries. See [Verify deliveries](https://shopify.dev/docs/apps/build/events/verify-deliveries) for HMAC verification steps and deduplication guidance. ## Example request headers Shopify-Topic: Product Shopify-Action: update Shopify-Handle: my\_product\_events Shopify-Api-Version: unstable Shopify-Resource-Id: gid://shopify/Product/123 Shopify-Event-Id: 01ARZ3NDEKTSV4RRFFQ69G5FAV Shopify-Webhook-Id: abc123-def456 Shopify-Triggered-At: 2026-01-15T12:34:56Z Shopify-Shop-Domain: my-store.myshopify.com Shopify-Hmac-Sha256: \ ### Custom queries Add a `query` to your subscription to define exactly what data each delivery contains. When set, Shopify runs the query after a qualifying change and includes the result in the payload's `data` field. Your query isn't limited to the topic's root resource. You can use any query root available in the [GraphQL Admin API](https://shopify.dev/docs/api/admin-graphql), including child entities like `productVariant` directly. When a variant triggers the delivery, both `$productId` and `$variantsId` are available as variables, so you can query either or both. The subscription uses the `Product` topic, but the query fetches variant data directly using `productVariant` as the root. If you omit `query`, the payload won't include `data`. See [Custom queries](https://shopify.dev/docs/apps/build/events/delivery-structure#custom-queries) for examples with multiple query roots and a full payload walkthrough. ## shopify.app.toml ```toml [[events.subscription]] handle = "price-watcher" topic = "Product" actions = ["update"] triggers = ["product.variants.price", "product.variants.compareAtPrice"] uri = "https://your-app.example.com/events" query = """ query price_watcher($variantsId: ID!) { productVariant(id: $variantsId) { id price compareAtPrice } } """ ``` *** ## Verify deliveries Every HTTPS delivery includes a `Shopify-Hmac-Sha256` header containing a base64-encoded HMAC-SHA256 signature generated from the raw request body and your app's client secret. Verify this signature before processing any delivery. To detect and ignore duplicate deliveries, use `Shopify-Webhook-Id`. This header is a unique composite key per delivery. Store it and check for it on each incoming request so your handler can skip reprocessing when a delivery is retried. See [Verify Events deliveries](https://shopify.dev/docs/apps/build/events/verify-deliveries) for verification steps and code examples. *** ## Limitations Unsupported `topic` values fail validation when you deploy. See the sidebar for the current list of supported topics. Events can be configured only in `shopify.app.toml`. *** ## Tutorials and resources Deepen your understanding of Events with these tutorials and community resources. ### Tutorials and guides [Guide - About Events](https://shopify.dev/docs/apps/build/events) [Guide - Create an Events subscription](https://shopify.dev/docs/apps/build/events/get-started) [Guide - Troubleshoot Events](https://shopify.dev/docs/apps/build/events/troubleshoot) [Tutorial - Migrate from webhooks](https://shopify.dev/docs/apps/build/events/migrate-from-webhooks) ### Core concepts [Guide - Manage Events subscriptions](https://shopify.dev/docs/apps/build/events/subscribe) [Guide - Filter Events deliveries](https://shopify.dev/docs/apps/build/events/delivery-filtering) [Guide - Events delivery structure](https://shopify.dev/docs/apps/build/events/delivery-structure) [Guide - Verify Event deliveries](https://shopify.dev/docs/apps/build/events/verify-deliveries) ### Community resources [Reference - Developer changelog](https://shopify.dev/changelog?filter=events) [Community - Community forum](https://community.shopify.dev/c/webhooks-and-events) ***