Migrate from webhooks
Events uses the same delivery infrastructure as classic webhooks, with field-level triggers and data selected by a GraphQL query. Use this guide to translate the REST-shaped fields your webhook handler reads into GraphQL Admin API selections, then design an Events subscription around the changes your app needs.
The starter queries below map classic webhook fields to their GraphQL equivalents where available. Each topic explains renamed, restructured, and unavailable fields. These queries are a starting point for field mapping. They don't configure a subscription or reproduce the conditions that caused the classic webhook to fire.
Events is in developer preview on the unstable API version, available today for a subset of topics. Use it 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. As Events expands topic coverage, it will become the primary subscription mechanism.
Events is in developer preview on the unstable API version, available today for a subset of topics. Use it 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. As Events expands topic coverage, it will become the primary subscription mechanism.
Anchor to RequirementsRequirements
- Use Shopify CLI version 3.92 or higher. Run
shopify versionto check your version, and follow the upgrade instructions if needed. - Identify the classic webhook topics your app uses, the changes that cause them to fire, and the fields your handler reads.
- Check the Events reference for supported topics, triggers, query variables, and required access scopes.
Anchor to Unsupported topicsUnsupported topics
Keep classic webhooks for these unsupported topics:
customer.joined_segmentcustomer.left_segmentcustomers/data_requestcustomers/marketing_consent/updatecustomers/mergecustomers/redactinventory_shipments/add_itemsinventory_shipments/receive_itemsinventory_shipments/remove_itemsinventory_shipments/update_item_quantities
Anchor to Step 1: Translate your webhook fieldsStep 1: Translate your webhook fields
Choose your classic webhook topic below, and use its starter query to map the fields your handler needs.
REST field names generally change from snake_case to camelCase, but some fields have different structures or no GraphQL equivalent.
Complexity points estimate the work required by a query based on its selected fields and connection sizes. By default, scalar and enum fields cost zero points, object fields cost one point, and connection costs depend on first and last. Some fields have custom costs, so use the GraphQL Admin API cost calculation rules to estimate complexity and validate your subscription query to confirm it meets the limit.
Events subscription queries have a complexity limit of 100 points. The broad starter queries can exceed that limit, especially when they select large connections or nested objects.
Optimize your query before using it in a subscription. Connection arguments such as first: 250 limit the number of records returned. A page size isn't a complexity budget or a guarantee of a complete result.
For delete actions, there's no starter query: the resource no longer exists. Use the resource ID in query_variables to identify what Shopify deleted. See Events delivery structure.
Continue using classic webhooks in production during the Events developer preview. Test supported topics alongside your existing webhooks, and keep classic webhooks for topics Events doesn't support.
Anchor to [object Object]Collection
CollectionUse the Collection reference to choose supported triggers and query variables for your app. Collection Events require the read_products access scope.
collections/create
Use this query to translate the fields from a collection creation webhook.
Field differences:
body_htmlbecomesdescriptionHtml.- The webhook's
admin_graphql_api_idbecomesid. Numeric REST IDs don't have an Events equivalent. published_atandpublished_scopedon't map to scalarCollectionfields. Use publication-related GraphQL Admin API fields when you need publication state.sort_orderbecomessortOrder.template_suffixbecomestemplateSuffix.
collections/update
Use the same collection field mapping for updates. Identify whether your handler needs collection details, product membership changes, or both, then choose supported triggers in the Collection reference.
collections/delete
No query is needed for a deleted Collection. Use query_variables.collectionId to identify it.
Starter query: collections/create
Anchor to [object Object]Customer
CustomerUse the Customer reference to choose supported triggers and query variables for your app.
customers/create
Use this query to translate the fields from a customer creation webhook.
Field differences:
currencyhas no Events equivalent. The webhook derives it from store defaults.- The webhook's
admin_graphql_api_idbecomesid. Numeric REST IDs don't have an Events equivalent. - Address
customer_id,country_name, anddefaultfields have no Events equivalents. - Address
idvalues are GID strings instead of integers. stateuses uppercase enum values.
customers/update
Use this query to translate customer fields, then choose triggers for the changes your handler needs.
Field differences:
currencyhas no Events equivalent. The webhook derives it from store defaults.- The webhook's
admin_graphql_api_idbecomesid. Numeric REST IDs don't have an Events equivalent. - Address
customer_id,country_name, anddefaultfields have no Events equivalents. - Address
idvalues are GID strings instead of integers. stateuses uppercase enum values.
customers/disable
This webhook represents a customer account becoming disabled. The query returns the current customer state; selecting state doesn't restrict delivery to that transition. Review state triggers and filtering when you design your subscription.
Field differences:
currencyhas no Events equivalent. The webhook derives it from store defaults.- The webhook's
admin_graphql_api_idbecomesid. Numeric REST IDs don't have an Events equivalent. - Address
customer_id,country_name, anddefaultfields have no Events equivalents. - Address
idvalues are GID strings instead of integers. stateuses uppercase enum values. The query can return any current state.
customers/enable
This webhook represents a customer account becoming enabled. The query returns the current customer state; selecting state doesn't restrict delivery to that transition. Review state triggers and filtering when you design your subscription.
Field differences:
currencyhas no Events equivalent. The webhook derives it from store defaults.- The webhook's
admin_graphql_api_idbecomesid. Numeric REST IDs don't have an Events equivalent. - Address
customer_id,country_name, anddefaultfields have no Events equivalents. - Address
idvalues are GID strings instead of integers. stateuses uppercase enum values. The query can return any current state.
customers/purchasing_summary
Use this query to translate purchasing summary fields. Check the Customer reference for triggers that cover the purchasing changes your handler needs.
Field differences:
lastOrderIdbecomeslastOrder { id }.numberOfOrdersis a string (UnsignedInt64), not an integer.occurredAtapproximatesCustomer.updatedAt. They match for purchasing events, butupdatedAtadvances on any customer change.
customers/delete
No query is needed for a deleted Customer. Use query_variables.customerId to identify it.
Starter query: customers/create
Anchor to [object Object]InventoryItem
InventoryItemUse the InventoryItem reference to choose supported triggers and query variables for your app. InventoryItem Events require the read_inventory access scope.
inventory_items/create
Use this query to translate the fields from an inventory item creation webhook.
Field differences:
costbecomesunitCost { amount currencyCode }.country_code_of_originbecomescountryCodeOfOrigin.country_harmonized_system_codesbecomescountryHarmonizedSystemCodes.nodes.harmonized_system_codebecomesharmonizedSystemCode.- The webhook's
admin_graphql_api_idbecomesid. Numeric REST IDs don't have an Events equivalent. province_code_of_originbecomesprovinceCodeOfOrigin.weight_valueandweight_unitare nested undermeasurement.weightasvalueandunit.
inventory_items/update
Use the same inventory item field mapping for updates. Choose triggers for the item fields your handler needs, and remove unused fields and connections from the query.
inventory_items/delete
No query is needed for a deleted InventoryItem. Use query_variables.inventoryItemId to identify it.
Starter query: inventory_items/create
Anchor to [object Object]InventoryShipment
InventoryShipmentUse the InventoryShipment reference to choose supported triggers and query variables for your app. InventoryShipment Events require the read_inventory_shipments access scope.
inventory_shipments/create
Use this query to translate shipment fields. The line item connection is a starting point; reconcile additional pages separately if your app needs every item.
Field differences:
happened_atandinventory_transfer_idhave no Events equivalents.line_itemsbecomes thelineItems.nodesconnection.trackingfields use camelCase, includingarrivesAt,trackingNumber, andtrackingUrl.
inventory_shipments/mark_in_transit
This webhook represents a shipment moving to IN_TRANSIT. The query returns the current status. Review status triggers and filtering to decide which transitions your app needs to process.
inventory_shipments/update_tracking
Use this query to translate tracking fields. Choose supported tracking triggers based on the changes your handler needs.
inventory_shipments/delete
No query is needed for a deleted InventoryShipment. Use query_variables.inventoryShipmentId to identify it.
Starter query: inventory_shipments/create
Anchor to [object Object]Location
LocationUse the Location reference to choose supported triggers and query variables for your app. Location Events require the read_locations access scope.
locations/create
Use this query to translate the fields from a location creation webhook.
Field differences:
activebecomesisActive.- Address fields are nested under
address. countryandprovincebecomeaddress.countryandaddress.province. Their code fields becomeaddress.countryCodeandaddress.provinceCode.country_nameis also represented byaddress.country.- The webhook's
admin_graphql_api_idbecomesid. Numeric REST IDs don't have an Events equivalent. legacyhas no Events equivalent.
locations/update
Use the same location field mapping for updates. Choose supported triggers for the location details your handler needs.
locations/activate
This webhook represents a location becoming active. The query returns isActive; choose triggers and filtering based on the state transitions your app needs.
locations/deactivate
This webhook represents a location becoming inactive. The query returns isActive; choose triggers and filtering based on the state transitions your app needs.
locations/delete
No query is needed for a deleted Location. Use query_variables.locationId to identify it.
Starter query: locations/create
Use the Order reference to choose supported triggers and query variables for your app. Order Events require one of the read_orders, read_marketplace_orders, read_buyer_membership_orders, or read_quick_sale access scopes.
orders/create
Use this query to translate order fields. Its broad field selection and nested connections need optimization before you use it in an Events subscription.
Field differences:
- The webhook's
admin_graphql_api_idbecomesid. Numeric REST IDs don't have an Events equivalent. financial_statusbecomesdisplayFinancialStatus.fulfillment_statusbecomesdisplayFulfillmentStatus.- Price fields are returned as money sets, such as
totalPriceSet { shopMoney { amount currencyCode } }. browser_ipbecomesclientIp,buyer_accepts_marketingbecomescustomerAcceptsMarketing, andnote_attributesbecomescustomAttributes.order_status_urlbecomesstatusPageUrl,referring_sitebecomesreferrerUrl, andsource_urlbecomesregisteredSourceUrl.- Line items, discount applications, shipping lines, and returns are connections with
nodesinstead of flat arrays. client_details,device_id,landing_site_ref,reference, andtokenhave no direct Events equivalents.
orders/updated
Use the same order field mapping for updates. Choose supported triggers for the order changes your handler needs, then reduce the query to the relevant fields.
orders/cancelled
This webhook represents an order cancellation. Use cancelReason and cancelledAt for cancellation details, and check the Order reference for the corresponding change triggers.
orders/fulfilled
This webhook represents an order becoming fulfilled. The query returns displayFulfillmentStatus; selecting it alone doesn't restrict deliveries to fulfilled orders. Review fulfillment triggers and filtering.
orders/partially_fulfilled
This webhook represents an order becoming partially fulfilled. The query returns displayFulfillmentStatus; selecting it alone doesn't restrict deliveries to partially fulfilled orders. Review fulfillment triggers and filtering.
orders/paid
This webhook represents an order becoming paid. The query returns displayFinancialStatus; review the Order reference and your payment workflow to choose triggers and filtering.
orders/delete
No query is needed for a deleted Order. Use query_variables.orderId to identify it.
orders/edited
There isn't a direct field-level Events equivalent. Start with the orders/updated query and identify the supported triggers that cover the order edits your app needs.
Starter query: orders/create
Anchor to [object Object]Product
ProductUse the Product reference to choose supported triggers and query variables for your app.
products/create
Use this query to translate product fields, including the variants, images, and media embedded in classic webhooks. The connections are a starting point for field mapping and need optimization before use in Events.
Field differences:
body_htmlbecomesbodyHtml.categorybecomescategory { id name fullName }.has_variants_that_requires_componentsbecomeshasVariantsThatRequiresComponents.- The webhook's
admin_graphql_api_idbecomesid. Numeric REST IDs don't have an Events equivalent. image(featured image) becomesfeaturedImage.image_idon variants becomesimage { id url altText }.- The classic webhook embeds
images,media, andvariantsas flat arrays. The query selects them through connections withnodes. Variantidvalues correspond tovariant_gids; connection pagination can omit variants. inventory_item_idon variants becomesinventoryItem { id }.inventory_policyon variants andstatususe uppercase enum values (DENY/CONTINUEandACTIVE/DRAFT/ARCHIVED).option1/option2/option3on variants becomeselectedOptions { name value }.tagsreturns an array instead of a comma-separated string.old_inventory_quantityon variants andpublished_scopehave no Events equivalents.
products/update
Use the same product field mapping for updates. Identify whether your handler needs product, variant, or media changes. When a supported trigger provides a child ID, query the changed child directly instead of reloading the connection. See Optimizing your subscriptions.
products/delete
No query is needed for a deleted Product. Use query_variables.productId to identify it.
Starter query: products/create
Anchor to Step 2: Identify what should trigger your subscriptionStep 2: Identify what should trigger your subscription
A query selects data. It doesn't determine when an Events subscription fires. For each classic webhook you want to migrate, identify the operation or field change your app reacts to and find the corresponding Events topic, action, and supported triggers.
- For
create, query the new resource using its ID. - For
update, choose triggers for the specific changes your app needs. Every subscription whose actions includeupdaterequires triggers. - For
delete, identify the deleted resource fromquery_variablesinstead of querying it.
Check the variables available for every action and trigger you select. A query can require a child ID only when every trigger in that subscription provides it. Split changes into separate subscriptions when they need different IDs or query roots.
For business operations such as an order becoming paid or a customer account becoming enabled, identify both the change and the resulting state that matter to your app.
Use triggers to detect changes and query_filter to check the current query result. A query filter can't detect a transition by itself.
If your app tracks a set of resources, then include the changes that remove resources from that set as well as those that add them.
See Delivery filtering for trigger and query filter behavior.
Anchor to Step 3: Optimize your queryStep 3: Optimize your query
Use Optimizing your subscriptions for the detailed workflow and worked examples. Apply it to each starter query before configuring your replacement subscription:
- Keep only the fields your handler and
query_filterneed. If your classic webhook usesinclude_fields, then use that list as your starting point. - Follow the optimization guide to choose targeted queries, split subscriptions, and reconcile any data that deliveries don't include.
- Validate the optimized subscription to confirm that its query meets the 100-point complexity limit. Shopify checks complexity when you deploy an app version, without executing the query or waiting for an event. Use
shopify app deploy --no-releaseto validate without releasing the version to users.
Anchor to Next stepsNext steps
- Subscribe to Events: Configure a subscription after you've chosen its actions, triggers, and optimized query.
- Events delivery structure: Update your handler for GraphQL field names, query variables, and delivery metadata.
- Troubleshoot Events: Inspect test deliveries and diagnose failures before changing your existing webhook coverage.