Migrate from webhooks
Events uses the same delivery infrastructure as classic webhooks, but gives you more control over what fires and what data you receive.
Migrating means replacing your [[webhooks.subscriptions]] blocks with [[events.subscription]] blocks and mapping your webhook topics to Events equivalents.
The most significant change is the payload shape: Events payloads are GraphQL-shaped rather than REST-shaped, so field names and nested structures follow GraphQL Admin API conventions instead of REST conventions. See Events and webhooks for a full comparison.
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
Events require Shopify CLI version 3.92 or higher.
Run shopify version to check your version, and follow the upgrade instructions if needed.
Anchor to Step 1: Map your webhook topics to EventsStep 1: Map your webhook topics to Events
For each classic webhook subscription block, migration results in an equivalent Events subscription block. Use the sections below to find the matching Events topic and action for each webhook topic string.
As you create Events equivalents for your classic webhooks, remember:
api_versionis set at the[events]level and applies to all Events subscriptions.- Each subscription requires a unique
handle. - Each topic section below shows the Events equivalent configuration, any required
triggersorquery_filter, and field differences between the webhook and Events payloads. - Not all webhook topics are currently supported by Events. You should continue to use classic webhooks in production. You can test Events migration using supported topics, and continue to use Events and classic webhook subscriptions side by side until Events supports more topics.
You can recreate webhook topics that represent business operations (for example, orders/fulfilled, orders/cancelled, customers/redact) with a combination of triggers and query_filter using the update action.
Events and classic webhooks can coexist in the same shopify.app.toml.
This example shows a webhook subscription for an unsupported topic running alongside an Events subscription for a supported one.
You can migrate topic by topic without removing existing webhook coverage.
Anchor to [object Object]Collection
CollectionThe Collection Events topic can be used with actions and triggers to replicate collection-related classic webhook subscriptions. Collection Events require the read_products access scope. Each accordion entry shows the equivalent Events subscription and the field-level differences between the classic webhook payload and the GraphQL response.
collections/create
Maps directly to a Collection create subscription. No triggers are needed.
All REST snake_case field names use camelCase in Events. Additional field differences:
body_htmlbecomesdescriptionHtml.idis a GID string instead of an integer.admin_graphql_api_idis the same value.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
Maps to a Collection update subscription. No triggers are set, so any change to the collection fires a delivery, matching the behavior of the classic webhook. To narrow delivery volume, add triggers such as collection.title, collection.handle, collection.products, collection.sortOrder, or collection.templateSuffix.
Use collection.products when you want deliveries for product membership changes in a collection.
collections/delete
Maps directly to a Collection delete subscription. No triggers or query are needed.
data.collectionisnull. The collection is deleted before the query runs.idin the webhook payload is available asquery_variables.collectionId.
Anchor to [object Object]Customer
CustomerThe Customer Events topic can be used with actions and triggers to replicate customer-related classic webhook subscriptions. Each accordion entry shows the equivalent Events subscription and the field-level differences between the classic webhook payload and the GraphQL response.
customers/create
Maps directly to a Customer create subscription. No triggers are needed.
All REST snake_case field names use camelCase in Events. Additional field differences:
addressesis a connection (addresses.nodes).currencyhas no Events equivalent. The webhook derived it from store defaults.idis a GID string instead of an integer.admin_graphql_api_idis the same value.last_order_idandlast_order_namemerge intolastOrder { id name }.stateuses uppercase enum values.tagsreturns an array instead of a comma-separated string.total_spentbecomesamountSpent { amount currencyCode }.
customers/update
Maps to a Customer update subscription. Without triggers, this fires on any change to the customer record.
All REST snake_case field names use camelCase in Events. Additional field differences:
addressesis a connection (addresses.nodes).currencyhas no Events equivalent. The webhook derived it from store defaults.idis a GID string instead of an integer.admin_graphql_api_idis the same value.last_order_idandlast_order_namemerge intolastOrder { id name }.stateuses uppercase enum values.tagsreturns an array instead of a comma-separated string.total_spentbecomesamountSpent { amount currencyCode }.
customers/disable
Fires when a customer account is disabled. Maps to a Customer update subscription with triggers = ["customer.state"] and query_filter = "customer.state:'DISABLED'". Without the filter, the subscription also fires when an account is enabled.
All REST snake_case field names use camelCase in Events. Additional field differences:
addressesis a connection (addresses.nodes).currencyhas no Events equivalent. The webhook derived it from store defaults.idis a GID string instead of an integer.admin_graphql_api_idis the same value.last_order_idandlast_order_namemerge intolastOrder { id name }.stateis always"DISABLED".tagsreturns an array instead of a comma-separated string.total_spentbecomesamountSpent { amount currencyCode }.
customers/enable
Fires when a customer account is enabled. Maps to a Customer update subscription with triggers = ["customer.state"] and query_filter = "customer.state:'ENABLED'". Without the filter, the subscription also fires when an account is disabled.
All REST snake_case field names use camelCase in Events. Additional field differences:
addressesis a connection (addresses.nodes).currencyhas no Events equivalent. The webhook derived it from store defaults.idis a GID string instead of an integer.admin_graphql_api_idis the same value.last_order_idandlast_order_namemerge intolastOrder { id name }.stateis always"ENABLED".tagsreturns an array instead of a comma-separated string.total_spentbecomesamountSpent { amount currencyCode }.
customers/purchasing_summary
Fires when purchase totals update. Maps to a Customer update subscription with triggers covering the purchasing fields your handler needs.
The webhook payload is already GraphQL-flavored. 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
Maps directly to a Customer delete subscription. No triggers or query are needed.
admin_graphql_api_idandidare available asquery_variables.customerId.data.customerisnull. The customer is deleted before the query runs.tax_exemptionshas no Events equivalent.
customers/data_request
Coming soon
No direct Events equivalent. This GDPR topic will be available in a future release.
customers/marketing_consent/update
Coming soon
No direct Events equivalent. This GDPR topic will be available in a future release.
customers/redact
Coming soon
No direct Events equivalent. This GDPR topic will be available in a future release.
customers/merge
Coming soon
No direct Events equivalent. This topic will be available in a future release.
customer.joined_segment
Coming soon
No direct Events equivalent. This topic will be available in a future release.
customer.left_segment
Coming soon
No direct Events equivalent. This topic will be available in a future release.
Anchor to [object Object]InventoryItem
InventoryItemThe InventoryItem Events topic can be used with actions and triggers to replicate inventory item-related classic webhook subscriptions. InventoryItem Events require the read_inventory access scope.
inventory_items/create
Maps directly to an InventoryItem create subscription. No triggers are needed.
All REST snake_case field names use camelCase in Events. Additional field differences:
costbecomesunitCost { amount currencyCode }.country_code_of_originbecomescountryCodeOfOrigin.harmonized_system_codebecomesharmonizedSystemCode.idis a GID string instead of an integer.admin_graphql_api_idis the same value.province_code_of_originbecomesprovinceCodeOfOrigin.tracked_editablebecomestrackedEditable.
inventory_items/update
Maps to an InventoryItem update subscription. No triggers are set, so any change to the inventory item fires a delivery, matching the behavior of the classic webhook. To narrow delivery volume, add triggers such as inventoryItem.sku, inventoryItem.tracked, inventoryItem.requiresShipping, inventoryItem.unitCost, or inventoryItem.measurement.weight.
inventory_items/delete
Maps directly to an InventoryItem delete subscription. No triggers or query are needed.
data.inventoryItemisnull. The inventory item is deleted before the query runs.idin the webhook payload is available asquery_variables.inventoryItemId.
Anchor to [object Object]InventoryShipment
InventoryShipmentThe InventoryShipment Events topic can be used with actions, triggers, and query_filter to replicate shipment lifecycle and tracking-related classic webhook subscriptions. InventoryShipment Events require the read_inventory_shipments access scope.
inventory_shipments/create
Maps directly to an InventoryShipment create subscription. No triggers are needed.
All REST snake_case field names use camelCase in Events. Additional field differences:
idis a GID string instead of an integer.admin_graphql_api_idis the same value.tracking_company,tracking_number, andtracking_urlare nested undertrackingascompany,trackingNumber, andtrackingUrl.- Shipment status values use uppercase enum values, such as
DRAFT,IN_TRANSIT, andRECEIVED.
inventory_shipments/mark_in_transit
Maps to an InventoryShipment update subscription with inventoryShipment.status as the trigger. Use a query_filter to receive only shipments that move to IN_TRANSIT.
inventory_shipments/update_tracking
Maps to an InventoryShipment update subscription with tracking triggers. Use the parent inventoryShipment.tracking trigger when you want any tracking change, or choose narrower triggers such as inventoryShipment.tracking.trackingNumber and inventoryShipment.tracking.trackingUrl.
inventory_shipments/delete
Maps directly to an InventoryShipment delete subscription. No triggers or query are needed.
data.inventoryShipmentisnull. The shipment is deleted before the query runs.idin the webhook payload is available asquery_variables.inventoryShipmentId.
inventory_shipments/add_items
Coming soon
No direct Events equivalent. This item-level shipment topic will be available in a future release.
inventory_shipments/remove_items
Coming soon
No direct Events equivalent. This item-level shipment topic will be available in a future release.
inventory_shipments/update_item_quantities
Coming soon
No direct Events equivalent. This item-level shipment topic will be available in a future release.
inventory_shipments/receive_items
Coming soon
No direct Events equivalent. This item-level shipment topic will be available in a future release.
Anchor to [object Object]Location
LocationThe Location Events topic can be used with actions, triggers, and query_filter to replicate location-related classic webhook subscriptions. Location Events require the read_locations access scope. Each accordion entry shows the equivalent Events subscription and the field-level differences between the classic webhook payload and the GraphQL response.
locations/create
Maps directly to a Location create subscription. No triggers are needed.
All REST snake_case field names use camelCase in Events. Additional field differences:
activebecomesisActive.address1,address2,city,country_code,province_code, andzipare nested underaddress.idis a GID string instead of an integer.admin_graphql_api_idis the same value.localized_country_nameandlocalized_province_namedon't map to scalarLocationfields. Useaddress.countryCodeandaddress.provinceCodewhen you need location geography.
locations/update
Maps to a Location update subscription. No triggers are set, so any change to the location fires a delivery, matching the behavior of the classic webhook. To narrow delivery volume, add triggers such as location.address, location.fulfillsOnlineOrders, location.isActive, or location.shipsInventory.
locations/activate
Maps to a Location update subscription with location.isActive as the trigger. Use a query_filter to receive only activations.
locations/deactivate
Maps to a Location update subscription with location.isActive and location.deactivatedAt as triggers. Use a query_filter to receive only deactivations.
locations/delete
Maps directly to a Location delete subscription. No triggers or query are needed.
data.locationisnull. The location is deleted before the query runs.idin the webhook payload is available asquery_variables.locationId.
The Order Events topic can be used with actions, triggers, and query_filter to replicate order-related classic webhook subscriptions. Order Events require one of the read_orders, read_marketplace_orders, read_buyer_membership_orders, or read_quick_sale access scopes. Each accordion entry shows the equivalent Events subscription and the field-level differences between the classic webhook payload and the GraphQL response.
orders/create
Maps directly to an Order create subscription. No triggers are needed.
All REST snake_case field names use camelCase in Events. Additional field differences:
idis a GID string instead of an integer.admin_graphql_api_idis the same value.financial_statusbecomesdisplayFinancialStatus.fulfillment_statusbecomesdisplayFulfillmentStatus.- Price fields are returned as money sets, such as
totalPriceSet { shopMoney { amount currencyCode } }. - Line item data is available through GraphQL Admin API connections, such as
lineItems.
orders/updated
Maps to an Order update subscription. No triggers are set, so any change to the order fires a delivery, matching the behavior of the classic webhook. To narrow delivery volume, add triggers such as order.email, order.note, order.tags, order.shippingLines, or order.totalPriceSet.
orders/cancelled
Maps to an Order update subscription with order.cancellation as the trigger. Include cancelReason, cancelledAt, and cancellation in the query when you need cancellation details.
orders/fulfilled
Maps to an Order update subscription with order.displayFulfillmentStatus as the trigger. Use a query_filter to receive only fulfilled orders.
orders/partially_fulfilled
Maps to an Order update subscription with order.displayFulfillmentStatus as the trigger. Use a query_filter to receive only partially fulfilled orders.
orders/paid
Maps to an Order update subscription with order.totalReceivedSet as the trigger. Use a query_filter to receive only paid orders.
orders/delete
Maps directly to an Order delete subscription. No triggers or query are needed.
data.orderisnull. The order is deleted before the query runs.idin the webhook payload is available asquery_variables.orderId.
orders/edited
No direct field-level Events equivalent. Use the orders/updated migration pattern and choose the specific Order triggers that correspond to the fields your app needs to replicate.
Anchor to [object Object]Product
ProductThe Product Events topic can be used with actions and triggers to replicate product-related classic webhook subscriptions. Each accordion entry shows the equivalent Events subscription and the field-level differences between the classic webhook payload and the GraphQL response.
products/create
Maps directly to a Product create subscription. No triggers are needed.
All REST snake_case field names use camelCase in Events. The following fields also rename, restructure, or have no equivalent:
body_htmlbecomesdescriptionHtml.categorybecomescategory { id name }.idis a GID string instead of an integer.admin_graphql_api_idis the same value.image(featured image) becomesfeaturedImage.image_idon variants becomesimage { id url altText }.imagesandvariantsare connections (images.nodes,variants.nodes) instead of flat arrays. Usevariants.nodes[*].idin place ofvariant_gids.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.has_variants_that_requires_components,old_inventory_quantityon variants, andpublished_scopehave no Events equivalent.
products/update
Maps to a Product update subscription. No triggers are set, so any change to the product fires a delivery, matching the behavior of the classic webhook. To narrow delivery volume after migration, see Delivery filtering.
products/delete
Maps directly to a Product delete subscription. No triggers or query are needed.
data.productisnull. The product is deleted before the query runs.idin the webhook payload is available asquery_variables.productId.
Anchor to Step 2: Match your delivery structureStep 2: Match your delivery structure
If your webhook used include_fields, modify your Events query to match those fields and reproduce the same payload.
Field names change from REST snake_case to GraphQL camelCase, and nested fields become proper GraphQL selections.
Anchor to Step 3: Match how you filter deliveriesStep 3: Match how you filter deliveries
If your webhook used filter, express the same condition as query_filter on your Events subscription.
Field names become camelCase with the topic name as a prefix, and enum values become uppercase to match GraphQL Admin API conventions.
Some things to consider as you translate your filter:
id:*drops.product_typebecomesproduct.productType: camelCase, prefixed with the topic name.status:activebecomesproduct.status:'ACTIVE': enum values are uppercase in the GraphQL Admin API.variants.pricebecomesproduct.variants.price: camelCase, prefixed with the topic name.
Anchor to Step 4: Update your handlersStep 4: Update your handlers
Events payloads are GraphQL-shaped, so your handlers need to read from a different structure than classic webhook payloads.
- The payload is wrapped in a
dataobject keyed by the topic name (for example,data.customerordata.product). - All field names are camelCase instead of snake_case.
- Some fields are restructured or renamed. Check the field differences listed in each topic section above.
fields_changedlists which fields triggered the delivery. Use it to avoid re-processing unchanged data.- For
deletesubscriptions,datais absent. Usequery_variablesto identify what was deleted.
See the Events reference for the full payload structure and available fields.
Anchor to Step 5: Test your new subscriptionsStep 5: Test your new subscriptions
With both subscription blocks active, make changes on your test store and verify that your new endpoint receives deliveries with the expected shape.
Confirm that fields_changed, query_variables, and data all look correct before removing the old subscription.
Anchor to Step 6: Remove the migrated webhooksStep 6: Remove the migrated webhooks
After your Events subscriptions are working and your handlers are updated, remove each migrated [[webhooks.subscriptions]] block from your TOML and redeploy.
Your old endpoints stop receiving deliveries for those topics.
Anchor to Next stepsNext steps
- Narrow delivery volume: Add
triggersto yourupdatesubscriptions so deliveries fire only when specific fields change. See Delivery filtering. - Expand your query: Now that you control the payload shape, request only the fields your handler actually uses, or add fields the webhook payload never included. See Delivery structure.
- Consolidate subscriptions: You can collapse multiple webhook topic strings for the same resource into one Events subscription with multiple
actionsand a single handler. - Events reference: Full topic and trigger path reference.