---
title: Migrate from webhooks
description: >-
  Translate classic webhook fields into starter GraphQL queries, identify the
  changes your app needs, and optimize your queries for Events.
source_url:
  html: 'https://shopify.dev/docs/apps/build/events/migrate-from-webhooks'
  md: 'https://shopify.dev/docs/apps/build/events/migrate-from-webhooks.md'
api_name: events
---

# 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.

Use the topic mapping below to find the Events resource for your classic webhook. The starter queries illustrate GraphQL field selections; the topic references provide supported triggers, query variables, and subscription examples. 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.

***

## Requirements

* Use Shopify CLI version 3.92 or higher. Run `shopify version` to check your version, and follow the [upgrade instructions](https://shopify.dev/docs/api/shopify-cli#upgrade-shopify-cli) 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](https://shopify.dev/docs/api/events) for supported topics, triggers, query variables, and required access scopes.

***

## Step 1: Translate your webhook fields

Find your Events topic below, and use its starter query or linked subscription example to select 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](https://shopify.dev/docs/apps/build/apis/graphql-admin/rate-limits#cost-calculation) to estimate complexity and [validate your subscription query](https://shopify.dev/docs/apps/build/events/optimizing-your-subscriptions#step-8-validate-the-optimized-subscriptions) 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](#step-3-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 identifiers in `query_variables` to identify what Shopify deleted. Definition topics use a type or a namespace, key, and owner type instead of a queryable resource ID. See [Events delivery structure](https://shopify.dev/docs/apps/build/events/delivery-structure).

### `Collection`

Use the [`Collection`](https://shopify.dev/docs/api/events/latest/collection) reference to choose supported triggers and query variables for your app. Requires `read_products` [access scope](https://shopify.dev/docs/api/usage/access-scopes#read_products).

##### `collections/create`

Use this query to translate the fields from a collection creation webhook.

Field differences:

* `body_html` becomes `descriptionHtml`.
* The webhook's `admin_graphql_api_id` becomes `id`. Numeric REST IDs don't have an Events equivalent.
* `published_at` and `published_scope` don't map to scalar `Collection` fields. Use publication-related GraphQL Admin API fields when you need publication state.
* `sort_order` becomes `sortOrder`.
* `template_suffix` becomes `templateSuffix`.

##### `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

```graphql
query collection_create_starter($collectionId: ID!) {
  collection(id: $collectionId) {
    id
    handle
    title
    updatedAt
    descriptionHtml
    sortOrder
    templateSuffix
  }
}
```

## Starter query: collections/update

```graphql
query collection_update_starter($collectionId: ID!) {
  collection(id: $collectionId) {
    id
    handle
    title
    updatedAt
    descriptionHtml
    sortOrder
    templateSuffix
  }
}
```

### `Company`

Use the [`Company`](https://shopify.dev/docs/api/events/latest/company) reference to choose supported triggers and query variables for your app. Query the company with `companyId`. Contact, contact role, and location changes are updates to the company.

##### `companies/create`

Use this query to translate the fields from a company creation webhook. `external_id` becomes `externalId`. Add the contact or location selections your handler needs using the reference examples.

##### `companies/update`

Use the same company field mapping for updates. Choose supported triggers for the company fields your handler needs.

##### `companies/delete`

No query is needed for a deleted `Company`. Use `query_variables.companyId` to identify it.

##### `company_contacts/create`

Contact creation is an update to the parent company. Review `company.contacts.*` and select the current contact fields your handler needs. Use child query variables only with triggers that expose them.

##### `company_contacts/update`

Choose supported `company.contacts.*` triggers for contact changes. Select the current contact fields your handler needs. Use child query variables only with triggers that expose them.

##### `company_contacts/delete`

Contact removal is an update to the parent company. Use `fields_changed` to identify the removed contact instead of querying it. Use child query variables only with triggers that expose them.

##### `company_contact_roles/assign`

Review `company.contacts.roleAssignments` and `company.locations.roleAssignments` for role assignment changes. Query the company and add the contact or location selections your handler needs. Use child query variables only with triggers that expose them.

##### `company_contact_roles/revoke`

Review `company.contacts.roleAssignments` and `company.locations.roleAssignments` for role assignment changes. Query the company and add the contact or location selections your handler needs. Use child query variables only with triggers that expose them.

##### `company_locations/create`

Location creation is an update to the parent company. Review `company.locations.*` and select the current location fields your handler needs. Use child query variables only with triggers that expose them.

##### `company_locations/update`

Choose supported `company.locations.*` triggers for location changes. Select the current location fields your handler needs. Use child query variables only with triggers that expose them.

##### `company_locations/delete`

Location removal is an update to the parent company. Use `fields_changed` to identify the removed location instead of querying it. Use child query variables only with triggers that expose them.

## Starter query: companies/create

```graphql
query companies_create_starter($companyId: ID!) {
  company(id: $companyId) {
    id
    name
    externalId
    note
  }
}
```

## Starter query: companies/update

```graphql
query companies_update_starter($companyId: ID!) {
  company(id: $companyId) {
    id
    name
    externalId
    note
  }
}
```

## Starter query: company\_contacts/create

```graphql
query company_contacts_create_starter($companyId: ID!) {
  company(id: $companyId) {
    id
    name
    externalId
    note
  }
}
```

## Starter query: company\_contacts/update

```graphql
query company_contacts_update_starter($companyId: ID!) {
  company(id: $companyId) {
    id
    name
    externalId
    note
  }
}
```

## Starter query: company\_contacts/delete

```graphql
query company_contacts_delete_starter($companyId: ID!) {
  company(id: $companyId) {
    id
    name
    externalId
    note
  }
}
```

## Starter query: company\_contact\_roles/assign

```graphql
query company_contact_roles_assign_starter($companyId: ID!) {
  company(id: $companyId) {
    id
    name
    externalId
    note
  }
}
```

## Starter query: company\_contact\_roles/revoke

```graphql
query company_contact_roles_revoke_starter($companyId: ID!) {
  company(id: $companyId) {
    id
    name
    externalId
    note
  }
}
```

## Starter query: company\_locations/create

```graphql
query company_locations_create_starter($companyId: ID!) {
  company(id: $companyId) {
    id
    name
    externalId
    note
  }
}
```

## Starter query: company\_locations/update

```graphql
query company_locations_update_starter($companyId: ID!) {
  company(id: $companyId) {
    id
    name
    externalId
    note
  }
}
```

## Starter query: company\_locations/delete

```graphql
query company_locations_delete_starter($companyId: ID!) {
  company(id: $companyId) {
    id
    name
    externalId
    note
  }
}
```

### `Customer`

Use the [`Customer`](https://shopify.dev/docs/api/events/latest/customer) reference to choose supported triggers and query variables for your app.

For `customers_email_marketing_consent/update` and `customers_marketing_consent/update`, review the `customer.defaultEmailAddress.*` and `customer.defaultPhoneNumber.smsMarketingConsent.*` triggers. Select the current consent fields from `Customer` and use `fields_changed` to identify the changed fields. A consent change doesn't provide the merge operation details from `customers/merge`.

##### `customers/create`

Use this query to translate the fields from a customer creation webhook.

Field differences:

* `currency` has no Events equivalent. The webhook derives it from store defaults.
* The webhook's `admin_graphql_api_id` becomes `id`. Numeric REST IDs don't have an Events equivalent.
* Address `customer_id`, `country_name`, and `default` fields have no Events equivalents.
* Address `id` values are GID strings instead of integers.
* `state` uses uppercase enum values.

##### `customers/update`

Use this query to translate customer fields, then choose triggers for the changes your handler needs.

Field differences:

* `currency` has no Events equivalent. The webhook derives it from store defaults.
* The webhook's `admin_graphql_api_id` becomes `id`. Numeric REST IDs don't have an Events equivalent.
* Address `customer_id`, `country_name`, and `default` fields have no Events equivalents.
* Address `id` values are GID strings instead of integers.
* `state` uses 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:

* `currency` has no Events equivalent. The webhook derives it from store defaults.
* The webhook's `admin_graphql_api_id` becomes `id`. Numeric REST IDs don't have an Events equivalent.
* Address `customer_id`, `country_name`, and `default` fields have no Events equivalents.
* Address `id` values are GID strings instead of integers.
* `state` uses 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:

* `currency` has no Events equivalent. The webhook derives it from store defaults.
* The webhook's `admin_graphql_api_id` becomes `id`. Numeric REST IDs don't have an Events equivalent.
* Address `customer_id`, `country_name`, and `default` fields have no Events equivalents.
* Address `id` values are GID strings instead of integers.
* `state` uses uppercase enum values. The query can return any current state.

##### `customer.tags_added`

Use this query to retrieve the current tag set. Determine what changed by comparing it with your stored state. A query filter evaluates the current data and can't distinguish a tag addition from a removal.

Field differences:

* `occurredAt` approximates `updatedAt` on `Customer`. They match for tag events, but `updatedAt` advances on any customer change.
* `tags` in Events is the full current tag set, not just the added tags.

##### `customer.tags_removed`

Use this query to retrieve the remaining tag set. Determine what changed by comparing it with your stored state. A query filter evaluates the current data and can't distinguish a tag removal from an addition.

Field differences:

* `occurredAt` approximates `updatedAt` on `Customer`. They match for tag events, but `updatedAt` advances on any customer change.
* `tags` in Events is the remaining tag set after removal, not the removed tags.

##### `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:

* `lastOrderId` becomes `lastOrder { id }`.
* `numberOfOrders` is a string (`UnsignedInt64`), not an integer.
* `occurredAt` approximates `Customer.updatedAt`. They match for purchasing events, but `updatedAt` advances 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

```graphql
query customer_create_starter($customerId: ID!) {
  customer(id: $customerId) {
    id
    createdAt
    updatedAt
    firstName
    lastName
    state
    note
    verifiedEmail
    multipassIdentifier
    taxExempt
    email
    phone
    taxExemptions
    defaultAddress {
      id
      firstName
      lastName
      company
      address1
      address2
      city
      province
      country
      zip
      phone
      name
      provinceCode
      countryCode
    }
    addresses {
      id
      firstName
      lastName
      company
      address1
      address2
      city
      province
      country
      zip
      phone
      name
      provinceCode
      countryCode
    }
  }
}
```

## Starter query: customers/update

```graphql
query customer_update_starter($customerId: ID!) {
  customer(id: $customerId) {
    id
    createdAt
    updatedAt
    firstName
    lastName
    state
    note
    verifiedEmail
    multipassIdentifier
    taxExempt
    email
    phone
    taxExemptions
    defaultAddress {
      id
      firstName
      lastName
      company
      address1
      address2
      city
      province
      country
      zip
      phone
      name
      provinceCode
      countryCode
    }
    addresses {
      id
      firstName
      lastName
      company
      address1
      address2
      city
      province
      country
      zip
      phone
      name
      provinceCode
      countryCode
    }
  }
}
```

## Starter query: customers/disable

```graphql
query customer_state_starter($customerId: ID!) {
  customer(id: $customerId) {
    id
    createdAt
    updatedAt
    firstName
    lastName
    state
    note
    verifiedEmail
    multipassIdentifier
    taxExempt
    email
    phone
    taxExemptions
    defaultAddress {
      id
      firstName
      lastName
      company
      address1
      address2
      city
      province
      country
      zip
      phone
      name
      provinceCode
      countryCode
    }
    addresses {
      id
      firstName
      lastName
      company
      address1
      address2
      city
      province
      country
      zip
      phone
      name
      provinceCode
      countryCode
    }
  }
}
```

## Starter query: customers/enable

```graphql
query customer_state_starter($customerId: ID!) {
  customer(id: $customerId) {
    id
    createdAt
    updatedAt
    firstName
    lastName
    state
    note
    verifiedEmail
    multipassIdentifier
    taxExempt
    email
    phone
    taxExemptions
    defaultAddress {
      id
      firstName
      lastName
      company
      address1
      address2
      city
      province
      country
      zip
      phone
      name
      provinceCode
      countryCode
    }
    addresses {
      id
      firstName
      lastName
      company
      address1
      address2
      city
      province
      country
      zip
      phone
      name
      provinceCode
      countryCode
    }
  }
}
```

## Starter query: customer.tags\_added

```graphql
query customer_tags_starter($customerId: ID!) {
  customer(id: $customerId) {
    id
    updatedAt
    tags
  }
}
```

## Starter query: customer.tags\_removed

```graphql
query customer_tags_starter($customerId: ID!) {
  customer(id: $customerId) {
    id
    updatedAt
    tags
  }
}
```

## Starter query: customers/purchasing\_summary

```graphql
query customer_purchasing_summary_starter($customerId: ID!) {
  customer(id: $customerId) {
    id
    updatedAt
    numberOfOrders
    amountSpent {
      amount
      currencyCode
    }
    lastOrder {
      id
    }
  }
}
```

### `FulfillmentOrder`

Use the [`FulfillmentOrder`](https://shopify.dev/docs/api/events/latest/fulfillment-order) reference to choose supported triggers and query variables for your app. Query the parent with `fulfillmentOrderId`; a classic fulfillment ID isn't a fulfillment order ID.

##### `fulfillments/create`

Fulfillment creation is an update to the parent fulfillment order. Review `fulfillmentOrder.fulfillments.*` and add the fulfillment selections your handler needs.

##### `fulfillments/update`

Review `fulfillmentOrder.fulfillments.status` and `fulfillmentOrder.fulfillments.trackingInfo.*` for fulfillment updates. Add the status and tracking fields your handler reads.

##### `fulfillment_orders/cancelled`

Review `fulfillmentOrder.status` and evaluate the resulting state. Cancellation is an update, not deletion of the fulfillment order.

##### `fulfillment_orders/cancellation_request_submitted`

Review `fulfillmentOrder.requestStatus` and `fulfillmentOrder.merchantRequests` for cancellation requests. Query the resulting state.

##### `fulfillment_orders/cancellation_request_accepted`

Review `fulfillmentOrder.requestStatus` and `fulfillmentOrder.status` for accepted cancellation requests.

##### `fulfillment_orders/cancellation_request_rejected`

Review `fulfillmentOrder.requestStatus` for rejected cancellation requests. Query the resulting state.

##### `fulfillment_orders/fulfillment_request_submitted`

Review `fulfillmentOrder.requestStatus` and `fulfillmentOrder.merchantRequests` for fulfillment requests. Add the request fields your handler needs.

##### `fulfillment_orders/fulfillment_request_accepted`

Review `fulfillmentOrder.requestStatus` for accepted fulfillment requests and evaluate the resulting state.

##### `fulfillment_orders/fulfillment_request_rejected`

Review `fulfillmentOrder.requestStatus` for rejected fulfillment requests and evaluate the resulting state.

##### `fulfillment_orders/placed_on_hold`

Review `fulfillmentOrder.fulfillmentHolds` and `fulfillmentOrder.status`. Add current hold selections to the query and evaluate the resulting state.

##### `fulfillment_orders/hold_released`

Review `fulfillmentOrder.fulfillmentHolds` and `fulfillmentOrder.status` for hold changes. Compare the resulting state with the state your handler tracks.

##### `fulfillment_holds/added`

Review `fulfillmentOrder.fulfillmentHolds` and add the current hold selections your handler needs.

##### `fulfillment_holds/released`

Review `fulfillmentOrder.fulfillmentHolds` and query the parent fulfillment order.

##### `fulfillment_orders/rescheduled`

Review `fulfillmentOrder.fulfillAt` and `fulfillmentOrder.fulfillBy` for scheduling changes. `fulfill_at` and `fulfill_by` become `fulfillAt` and `fulfillBy`.

## Starter query: fulfillments/create

```graphql
query fulfillments_create_starter($fulfillmentOrderId: ID!) {
  fulfillmentOrder(id: $fulfillmentOrderId) {
    id
    status
    requestStatus
    fulfillAt
    fulfillBy
  }
}
```

## Starter query: fulfillments/update

```graphql
query fulfillments_update_starter($fulfillmentOrderId: ID!) {
  fulfillmentOrder(id: $fulfillmentOrderId) {
    id
    status
    requestStatus
    fulfillAt
    fulfillBy
  }
}
```

## Starter query: fulfillment\_orders/cancelled

```graphql
query fulfillment_orders_cancelled_starter($fulfillmentOrderId: ID!) {
  fulfillmentOrder(id: $fulfillmentOrderId) {
    id
    status
    requestStatus
    fulfillAt
    fulfillBy
  }
}
```

## Starter query: fulfillment\_orders/cancellation\_request\_submitted

```graphql
query fulfillment_orders_cancellation_request_submitted_starter($fulfillmentOrderId: ID!) {
  fulfillmentOrder(id: $fulfillmentOrderId) {
    id
    status
    requestStatus
    fulfillAt
    fulfillBy
  }
}
```

## Starter query: fulfillment\_orders/cancellation\_request\_accepted

```graphql
query fulfillment_orders_cancellation_request_accepted_starter($fulfillmentOrderId: ID!) {
  fulfillmentOrder(id: $fulfillmentOrderId) {
    id
    status
    requestStatus
    fulfillAt
    fulfillBy
  }
}
```

## Starter query: fulfillment\_orders/cancellation\_request\_rejected

```graphql
query fulfillment_orders_cancellation_request_rejected_starter($fulfillmentOrderId: ID!) {
  fulfillmentOrder(id: $fulfillmentOrderId) {
    id
    status
    requestStatus
    fulfillAt
    fulfillBy
  }
}
```

## Starter query: fulfillment\_orders/fulfillment\_request\_submitted

```graphql
query fulfillment_orders_fulfillment_request_submitted_starter($fulfillmentOrderId: ID!) {
  fulfillmentOrder(id: $fulfillmentOrderId) {
    id
    status
    requestStatus
    fulfillAt
    fulfillBy
  }
}
```

## Starter query: fulfillment\_orders/fulfillment\_request\_accepted

```graphql
query fulfillment_orders_fulfillment_request_accepted_starter($fulfillmentOrderId: ID!) {
  fulfillmentOrder(id: $fulfillmentOrderId) {
    id
    status
    requestStatus
    fulfillAt
    fulfillBy
  }
}
```

## Starter query: fulfillment\_orders/fulfillment\_request\_rejected

```graphql
query fulfillment_orders_fulfillment_request_rejected_starter($fulfillmentOrderId: ID!) {
  fulfillmentOrder(id: $fulfillmentOrderId) {
    id
    status
    requestStatus
    fulfillAt
    fulfillBy
  }
}
```

## Starter query: fulfillment\_orders/placed\_on\_hold

```graphql
query fulfillment_orders_placed_on_hold_starter($fulfillmentOrderId: ID!) {
  fulfillmentOrder(id: $fulfillmentOrderId) {
    id
    status
    requestStatus
    fulfillAt
    fulfillBy
  }
}
```

## Starter query: fulfillment\_orders/hold\_released

```graphql
query fulfillment_orders_hold_released_starter($fulfillmentOrderId: ID!) {
  fulfillmentOrder(id: $fulfillmentOrderId) {
    id
    status
    requestStatus
    fulfillAt
    fulfillBy
  }
}
```

## Starter query: fulfillment\_holds/added

```graphql
query fulfillment_holds_added_starter($fulfillmentOrderId: ID!) {
  fulfillmentOrder(id: $fulfillmentOrderId) {
    id
    status
    requestStatus
    fulfillAt
    fulfillBy
  }
}
```

## Starter query: fulfillment\_holds/released

```graphql
query fulfillment_holds_released_starter($fulfillmentOrderId: ID!) {
  fulfillmentOrder(id: $fulfillmentOrderId) {
    id
    status
    requestStatus
    fulfillAt
    fulfillBy
  }
}
```

## Starter query: fulfillment\_orders/rescheduled

```graphql
query fulfillment_orders_rescheduled_starter($fulfillmentOrderId: ID!) {
  fulfillmentOrder(id: $fulfillmentOrderId) {
    id
    status
    requestStatus
    fulfillAt
    fulfillBy
  }
}
```

### `InventoryItem`

Use the [`InventoryItem`](https://shopify.dev/docs/api/events/latest/inventory-item) reference to choose supported triggers and query variables for your app. Requires `read_inventory` [access scope](https://shopify.dev/docs/api/usage/access-scopes#read_inventory).

For `inventory_levels/connect`, `inventory_levels/update`, and `inventory_levels/disconnect`, review the `inventoryItem.inventoryLevel.*`, `inventoryItem.inventoryLevel.isActive`, and quantity triggers. These are updates to `InventoryItem`, not creation or deletion of the item itself. Use `inventoryLevelId` only with triggers that expose it. For disconnects, subscribe to `inventoryItem.inventoryLevel.isActive`, query the inventory level with `inventoryLevelId`, and check whether `isActive` is `false`.

##### `inventory_items/create`

Use this query to translate the fields from an inventory item creation webhook.

Field differences:

* `cost` becomes `unitCost { amount currencyCode }`.
* `country_code_of_origin` becomes `countryCodeOfOrigin`.
* `country_harmonized_system_codes` becomes `countryHarmonizedSystemCodes.nodes`.
* `harmonized_system_code` becomes `harmonizedSystemCode`.
* The webhook's `admin_graphql_api_id` becomes `id`. Numeric REST IDs don't have an Events equivalent.
* `province_code_of_origin` becomes `provinceCodeOfOrigin`.
* `weight_value` and `weight_unit` are nested under `measurement.weight` as `value` and `unit`.

##### `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

```graphql
query inventory_item_create_starter($inventoryItemId: ID!) {
  inventoryItem(id: $inventoryItemId) {
    id
    sku
    createdAt
    updatedAt
    tracked
    requiresShipping
    countryCodeOfOrigin
    provinceCodeOfOrigin
    harmonizedSystemCode
    countryHarmonizedSystemCodes(first: 250) {
      nodes {
        countryCode
        harmonizedSystemCode
      }
    }
    measurement {
      weight {
        value
        unit
      }
    }
    unitCost {
      amount
      currencyCode
    }
  }
}
```

## Starter query: inventory\_items/update

```graphql
query inventory_item_update_starter($inventoryItemId: ID!) {
  inventoryItem(id: $inventoryItemId) {
    id
    sku
    createdAt
    updatedAt
    tracked
    requiresShipping
    countryCodeOfOrigin
    provinceCodeOfOrigin
    harmonizedSystemCode
    countryHarmonizedSystemCodes(first: 250) {
      nodes {
        countryCode
        harmonizedSystemCode
      }
    }
    measurement {
      weight {
        value
        unit
      }
    }
    unitCost {
      amount
      currencyCode
    }
  }
}
```

### `InventoryShipment`

Use the [`InventoryShipment`](https://shopify.dev/docs/api/events/latest/inventory-shipment) reference to choose supported triggers and query variables for your app. Requires `read_inventory_shipments` [access scope](https://shopify.dev/docs/api/usage/access-scopes#read_inventory_shipments).

##### `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_at` and `inventory_transfer_id` have no Events equivalents.
* `line_items` becomes the `lineItems.nodes` connection.
* `tracking` fields use camelCase, including `arrivesAt`, `trackingNumber`, and `trackingUrl`.

##### `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

```graphql
query inventory_shipment_create_starter($inventoryShipmentId: ID!) {
  inventoryShipment(id: $inventoryShipmentId) {
    id
    status
    tracking {
      arrivesAt
      company
      trackingNumber
      trackingUrl
    }
    lineItems(first: 250) {
      nodes {
        id
        quantity
      }
    }
  }
}
```

## Starter query: inventory\_shipments/mark\_in\_transit

```graphql
query inventory_shipment_status_starter($inventoryShipmentId: ID!) {
  inventoryShipment(id: $inventoryShipmentId) {
    id
    status
  }
}
```

## Starter query: inventory\_shipments/update\_tracking

```graphql
query inventory_shipment_tracking_starter($inventoryShipmentId: ID!) {
  inventoryShipment(id: $inventoryShipmentId) {
    id
    tracking {
      arrivesAt
      company
      trackingNumber
      trackingUrl
    }
  }
}
```

### `InventoryTransfer`

Use the [`InventoryTransfer`](https://shopify.dev/docs/api/events/latest/inventory-transfer) reference to choose supported triggers and query variables for your app. Query the transfer with `inventoryTransferId`. Paginate the line items when your handler needs the complete transfer.

##### `inventory_transfers/updated`

Use this query to select the current transfer fields your handler reads. Review status, destination, line item quantity, and tag triggers for the changes your handler needs.

##### `inventory_transfers/add_items`

Review `inventoryTransfer.lineItems.*` for line item changes. The query returns current state. Compare it with stored state to identify added items.

##### `inventory_transfers/remove_items`

Review `inventoryTransfer.lineItems.*` for line item changes. Compare the queried current state with stored state to identify removed items. Don't rely on `fields_changed` to provide item IDs or an added/removed list.

##### `inventory_transfers/update_item_quantities`

Review `inventoryTransfer.lineItems.totalQuantity` and select current line item quantities. The query returns `totalQuantity` for each line item. Compare with stored state if your handler needs the webhook's `old_quantity` and `new_quantity` difference.

##### `inventory_transfers/cancel`

Review `inventoryTransfer.status` and evaluate the resulting state for cancellation. Cancellation is an update, not deletion of the transfer.

##### `inventory_transfers/complete`

Review `inventoryTransfer.status` and evaluate the resulting state for completion.

##### `inventory_transfers/ready_to_ship`

Review `inventoryTransfer.status` and evaluate the resulting state when the transfer becomes ready to ship.

## Starter query: inventory\_transfers/updated

```graphql
query inventory_transfers_updated_starter($inventoryTransferId: ID!) {
  inventoryTransfer(id: $inventoryTransferId) {
    id
    name
    status
    tags
    lineItems(first: 10) {
      nodes {
        id
        totalQuantity
      }
    }
  }
}
```

## Starter query: inventory\_transfers/add\_items

```graphql
query inventory_transfers_add_items_starter($inventoryTransferId: ID!) {
  inventoryTransfer(id: $inventoryTransferId) {
    id
    name
    status
    tags
    lineItems(first: 10) {
      nodes {
        id
        totalQuantity
      }
    }
  }
}
```

## Starter query: inventory\_transfers/remove\_items

```graphql
query inventory_transfers_remove_items_starter($inventoryTransferId: ID!) {
  inventoryTransfer(id: $inventoryTransferId) {
    id
    name
    status
    tags
    lineItems(first: 10) {
      nodes {
        id
        totalQuantity
      }
    }
  }
}
```

## Starter query: inventory\_transfers/update\_item\_quantities

```graphql
query inventory_transfers_update_item_quantities_starter($inventoryTransferId: ID!) {
  inventoryTransfer(id: $inventoryTransferId) {
    id
    name
    status
    tags
    lineItems(first: 10) {
      nodes {
        id
        totalQuantity
      }
    }
  }
}
```

## Starter query: inventory\_transfers/cancel

```graphql
query inventory_transfers_cancel_starter($inventoryTransferId: ID!) {
  inventoryTransfer(id: $inventoryTransferId) {
    id
    name
    status
    tags
    lineItems(first: 10) {
      nodes {
        id
        totalQuantity
      }
    }
  }
}
```

## Starter query: inventory\_transfers/complete

```graphql
query inventory_transfers_complete_starter($inventoryTransferId: ID!) {
  inventoryTransfer(id: $inventoryTransferId) {
    id
    name
    status
    tags
    lineItems(first: 10) {
      nodes {
        id
        totalQuantity
      }
    }
  }
}
```

## Starter query: inventory\_transfers/ready\_to\_ship

```graphql
query inventory_transfers_ready_to_ship_starter($inventoryTransferId: ID!) {
  inventoryTransfer(id: $inventoryTransferId) {
    id
    name
    status
    tags
    lineItems(first: 10) {
      nodes {
        id
        totalQuantity
      }
    }
  }
}
```

### `Location`

Use the [`Location`](https://shopify.dev/docs/api/events/latest/location) reference to choose supported triggers and query variables for your app. Requires `read_locations` [access scope](https://shopify.dev/docs/api/usage/access-scopes#read_locations).

##### `locations/create`

Use this query to translate the fields from a location creation webhook.

Field differences:

* `active` becomes `isActive`.
* Address fields are nested under `address`.
* `country` and `province` become `address.country` and `address.province`. Their code fields become `address.countryCode` and `address.provinceCode`.
* `country_name` is also represented by `address.country`.
* The webhook's `admin_graphql_api_id` becomes `id`. Numeric REST IDs don't have an Events equivalent.
* `legacy` has 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

```graphql
query location_create_starter($locationId: ID!) {
  location(id: $locationId) {
    id
    name
    createdAt
    updatedAt
    isActive
    address {
      address1
      address2
      city
      zip
      province
      country
      countryCode
      provinceCode
      phone
    }
  }
}
```

## Starter query: locations/update

```graphql
query location_update_starter($locationId: ID!) {
  location(id: $locationId) {
    id
    name
    createdAt
    updatedAt
    isActive
    address {
      address1
      address2
      city
      zip
      province
      country
      countryCode
      provinceCode
      phone
    }
  }
}
```

## Starter query: locations/activate

```graphql
query location_activation_starter($locationId: ID!) {
  location(id: $locationId) {
    id
    name
    createdAt
    updatedAt
    isActive
    address {
      address1
      address2
      city
      zip
      province
      country
      countryCode
      provinceCode
      phone
    }
  }
}
```

## Starter query: locations/deactivate

```graphql
query location_deactivation_starter($locationId: ID!) {
  location(id: $locationId) {
    id
    name
    createdAt
    updatedAt
    isActive
    address {
      address1
      address2
      city
      zip
      province
      country
      countryCode
      provinceCode
      phone
    }
  }
}
```

### `MetafieldDefinition`

Use the [`MetafieldDefinition`](https://shopify.dev/docs/api/events/latest/metafield-definition) reference to choose supported triggers and query variables for your app.

Look up a definition by namespace, key, and owner type. `metafieldDefinitionId` isn't available to payload queries. Events supplies `metafieldDefinitionOwnerType` as a `String`, so you can't pass it directly to the GraphQL `MetafieldOwnerType` enum.

##### `metafield_definitions/create`

Use this query to translate the fields from a metafield definition creation webhook. This example selects product definitions. Adapt the fixed `ownerType` for the definitions your app handles and keep selections specific to that owner type.

##### `metafield_definitions/update`

Use the same definition lookup for updates. Choose supported triggers for the definition fields your handler needs, such as `metafieldDefinition.name`, `metafieldDefinition.description`, or `metafieldDefinition.validations`.

##### `metafield_definitions/delete`

No query is needed for a deleted `MetafieldDefinition`. Use `query_variables.metafieldDefinitionNamespace`, `query_variables.metafieldDefinitionKey`, and `query_variables.metafieldDefinitionOwnerType` to identify it.

## Starter query: metafield\_definitions/create

```graphql
query metafield_definitions_create_starter(
  $metafieldDefinitionNamespace: String!
  $metafieldDefinitionKey: String!
) {
  metafieldDefinition(identifier: {
    namespace: $metafieldDefinitionNamespace
    key: $metafieldDefinitionKey
    ownerType: PRODUCT
  }) {
    id
    namespace
    key
    name
    description
    ownerType
    type { name }
  }
}
```

## Starter query: metafield\_definitions/update

```graphql
query metafield_definitions_update_starter(
  $metafieldDefinitionNamespace: String!
  $metafieldDefinitionKey: String!
) {
  metafieldDefinition(identifier: {
    namespace: $metafieldDefinitionNamespace
    key: $metafieldDefinitionKey
    ownerType: PRODUCT
  }) {
    id
    namespace
    key
    name
    description
    ownerType
    type { name }
  }
}
```

### `Metaobject`

Use the [`Metaobject`](https://shopify.dev/docs/api/events/latest/metaobject) reference to choose supported triggers and query variables for your app. Query an entry with `metaobjectId`.

##### `metaobjects/create`

Use this query to translate the fields from a metaobject creation webhook. `display_name` becomes `displayName`. The custom field example uses `title`; replace it with a key from your definition.

##### `metaobjects/update`

Use the same metaobject field mapping for updates. Update triggers require a metaobject type, such as `metaobject(type: 'lookbook').displayName`. For custom field changes, select a field key in both the trigger and query.

##### `metaobjects/delete`

No query is needed for a deleted `Metaobject`. Use `query_variables.metaobjectId` to identify it.

## Starter query: metaobjects/create

```graphql
query metaobjects_create_starter($metaobjectId: ID!) {
  metaobject(id: $metaobjectId) {
    id
    type
    handle
    displayName
    field(key: "title") { key value }
  }
}
```

## Starter query: metaobjects/update

```graphql
query metaobjects_update_starter($metaobjectId: ID!) {
  metaobject(id: $metaobjectId) {
    id
    type
    handle
    displayName
    field(key: "title") { key value }
  }
}
```

### `Order`

Use the [`Order`](https://shopify.dev/docs/api/events/latest/order) reference to choose supported triggers and query variables for your app. Requires one of the `read_orders`, `read_marketplace_orders`, `read_buyer_membership_orders`, or `read_quick_sale` [access scopes](https://shopify.dev/docs/api/usage/access-scopes#authenticated-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_id` becomes `id`. Numeric REST IDs don't have an Events equivalent.
* `financial_status` becomes `displayFinancialStatus`.
* `fulfillment_status` becomes `displayFulfillmentStatus`.
* Price fields are returned as money sets, such as `totalPriceSet { shopMoney { amount currencyCode } }`.
* `browser_ip` becomes `clientIp`, `buyer_accepts_marketing` becomes `customerAcceptsMarketing`, and `note_attributes` becomes `customAttributes`.
* `order_status_url` becomes `statusPageUrl`, `referring_site` becomes `referrerUrl`, and `source_url` becomes `registeredSourceUrl`.
* Line items, discount applications, shipping lines, and returns are connections with `nodes` instead of flat arrays.
* `client_details`, `device_id`, `landing_site_ref`, `reference`, and `token` have 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

```graphql
query order_create_starter($orderId: ID!) {
  order(id: $orderId) {
    id
    app { id }
    clientIp
    customerAcceptsMarketing
    cancelReason
    cancelledAt
    cartToken
    checkoutToken
    closedAt
    confirmationNumber
    confirmed
    email
    createdAt
    currencyCode
    currentShippingPriceSet { ...MoneyBagFields }
    currentSubtotalPriceSet { ...MoneyBagFields }
    currentTotalAdditionalFeesSet { ...MoneyBagFields }
    currentTotalDiscountsSet { ...MoneyBagFields }
    currentTotalDutiesSet { ...MoneyBagFields }
    currentTotalPriceSet { ...MoneyBagFields }
    currentTotalTaxSet { ...MoneyBagFields }
    customerLocale
    discountCodes
    dutiesIncluded
    estimatedTaxes
    name
    displayFinancialStatus
    displayFulfillmentStatus
    landingPageUrl
    landingPageDisplayText
    physicalLocation { id }
    retailLocation { id }
    merchantBusinessEntity { id }
    merchantOfRecordApp { id }
    note
    customAttributes { key value }
    number
    statusPageUrl
    originalTotalAdditionalFeesSet { ...MoneyBagFields }
    originalTotalDutiesSet { ...MoneyBagFields }
    paymentGatewayNames
    phone
    poNumber
    presentmentCurrencyCode
    processedAt
    referrerUrl
    sourceIdentifier
    sourceName
    registeredSourceUrl
    subtotalPriceSet { ...MoneyBagFields }
    tags
    taxExempt
    taxLines { ...TaxLineFields }
    taxesIncluded
    test
    totalCashRoundingAdjustment {
      paymentSet { ...MoneyBagFields }
      refundSet { ...MoneyBagFields }
    }
    totalDiscountsSet { ...MoneyBagFields }
    totalOutstandingSet { ...MoneyBagFields }
    totalPriceSet { ...MoneyBagFields }
    totalShippingPriceSet { ...MoneyBagFields }
    totalTaxSet { ...MoneyBagFields }
    totalTipReceived { amount currencyCode }
    totalWeight
    updatedAt
    staffMember { id }
    billingAddress { ...MailingAddressFields }
    customer { ...CustomerFields }
    discountApplications(first: 250) {
      nodes { ...DiscountApplicationFields }
    }
    fulfillments { id }
    lineItems(first: 250) {
      nodes {
        id
        currentQuantity
        fulfillableQuantity
        fulfillmentService { handle }
        fulfillmentStatus
        isGiftCard
        weight { value unit }
        name
        originalUnitPrice
        originalUnitPriceSet { ...MoneyBagFields }
        product { id }
        customAttributes { key value }
        quantity
        requiresShipping
        lineItemGroup { id }
        sku
        taxable
        title
        totalDiscount
        totalDiscountSet { ...MoneyBagFields }
        variant { id }
        variantTitle
        vendor
        staffMember { id }
        taxLines { ...TaxLineFields }
        duties {
          id
          countryCodeOfOrigin
          harmonizedSystemCode
          price { ...MoneyBagFields }
          taxLines { ...TaxLineFields }
        }
        discountAllocations {
          allocatedAmount { amount currencyCode }
          allocatedAmountSet { ...MoneyBagFields }
          discountApplication { ...DiscountApplicationFields }
        }
      }
    }
    paymentTerms { id }
    refunds { id }
    shippingAddress { ...MailingAddressFields }
    shippingLines(first: 250, includeRemovals: true) {
      nodes {
        id
        carrierIdentifier
        code
        currentDiscountedPriceSet { ...MoneyBagFields }
        discountedPrice { amount currencyCode }
        discountedPriceSet { ...MoneyBagFields }
        isRemoved
        phone
        originalPrice { amount currencyCode }
        originalPriceSet { ...MoneyBagFields }
        source
        title
        taxLines { ...TaxLineFields }
        discountAllocations {
          allocatedAmount { amount currencyCode }
          allocatedAmountSet { ...MoneyBagFields }
          discountApplication { ...DiscountApplicationFields }
        }
      }
    }
    returns(first: 250) { nodes { id } }
  }
}


fragment MoneyBagFields on MoneyBag {
  shopMoney { amount currencyCode }
  presentmentMoney { amount currencyCode }
}


fragment MailingAddressFields on MailingAddress {
  firstName
  lastName
  company
  address1
  address2
  city
  province
  country
  zip
  phone
  name
  provinceCode
  countryCode
  latitude
  longitude
}


fragment CustomerFields on Customer {
  id
  createdAt
  updatedAt
  firstName
  lastName
  state
  note
  verifiedEmail
  multipassIdentifier
  taxExempt
  email
  phone
  taxExemptions
  defaultAddress { ...MailingAddressFields }
}


fragment TaxLineFields on TaxLine {
  title
  rate
  price
  priceSet { ...MoneyBagFields }
  channelLiable
}


fragment DiscountApplicationFields on DiscountApplication {
  __typename
  index
  allocationMethod
  targetSelection
  targetType
  value {
    ... on MoneyV2 { amount currencyCode }
    ... on PricingPercentageValue { percentage }
  }
  ... on AutomaticDiscountApplication { title }
  ... on DiscountCodeApplication { code }
  ... on ManualDiscountApplication { title }
  ... on ScriptDiscountApplication { title }
}
```

## Starter query: orders/updated

```graphql
query order_update_starter($orderId: ID!) {
  order(id: $orderId) {
    id
    app { id }
    clientIp
    customerAcceptsMarketing
    cancelReason
    cancelledAt
    cartToken
    checkoutToken
    closedAt
    confirmationNumber
    confirmed
    email
    createdAt
    currencyCode
    currentShippingPriceSet { ...MoneyBagFields }
    currentSubtotalPriceSet { ...MoneyBagFields }
    currentTotalAdditionalFeesSet { ...MoneyBagFields }
    currentTotalDiscountsSet { ...MoneyBagFields }
    currentTotalDutiesSet { ...MoneyBagFields }
    currentTotalPriceSet { ...MoneyBagFields }
    currentTotalTaxSet { ...MoneyBagFields }
    customerLocale
    discountCodes
    dutiesIncluded
    estimatedTaxes
    name
    displayFinancialStatus
    displayFulfillmentStatus
    landingPageUrl
    landingPageDisplayText
    physicalLocation { id }
    retailLocation { id }
    merchantBusinessEntity { id }
    merchantOfRecordApp { id }
    note
    customAttributes { key value }
    number
    statusPageUrl
    originalTotalAdditionalFeesSet { ...MoneyBagFields }
    originalTotalDutiesSet { ...MoneyBagFields }
    paymentGatewayNames
    phone
    poNumber
    presentmentCurrencyCode
    processedAt
    referrerUrl
    sourceIdentifier
    sourceName
    registeredSourceUrl
    subtotalPriceSet { ...MoneyBagFields }
    tags
    taxExempt
    taxLines { ...TaxLineFields }
    taxesIncluded
    test
    totalCashRoundingAdjustment {
      paymentSet { ...MoneyBagFields }
      refundSet { ...MoneyBagFields }
    }
    totalDiscountsSet { ...MoneyBagFields }
    totalOutstandingSet { ...MoneyBagFields }
    totalPriceSet { ...MoneyBagFields }
    totalShippingPriceSet { ...MoneyBagFields }
    totalTaxSet { ...MoneyBagFields }
    totalTipReceived { amount currencyCode }
    totalWeight
    updatedAt
    staffMember { id }
    billingAddress { ...MailingAddressFields }
    customer { ...CustomerFields }
    discountApplications(first: 250) {
      nodes { ...DiscountApplicationFields }
    }
    fulfillments { id }
    lineItems(first: 250) {
      nodes {
        id
        currentQuantity
        fulfillableQuantity
        fulfillmentService { handle }
        fulfillmentStatus
        isGiftCard
        weight { value unit }
        name
        originalUnitPrice
        originalUnitPriceSet { ...MoneyBagFields }
        product { id }
        customAttributes { key value }
        quantity
        requiresShipping
        lineItemGroup { id }
        sku
        taxable
        title
        totalDiscount
        totalDiscountSet { ...MoneyBagFields }
        variant { id }
        variantTitle
        vendor
        staffMember { id }
        taxLines { ...TaxLineFields }
        duties {
          id
          countryCodeOfOrigin
          harmonizedSystemCode
          price { ...MoneyBagFields }
          taxLines { ...TaxLineFields }
        }
        discountAllocations {
          allocatedAmount { amount currencyCode }
          allocatedAmountSet { ...MoneyBagFields }
          discountApplication { ...DiscountApplicationFields }
        }
      }
    }
    paymentTerms { id }
    refunds { id }
    shippingAddress { ...MailingAddressFields }
    shippingLines(first: 250, includeRemovals: true) {
      nodes {
        id
        carrierIdentifier
        code
        currentDiscountedPriceSet { ...MoneyBagFields }
        discountedPrice { amount currencyCode }
        discountedPriceSet { ...MoneyBagFields }
        isRemoved
        phone
        originalPrice { amount currencyCode }
        originalPriceSet { ...MoneyBagFields }
        source
        title
        taxLines { ...TaxLineFields }
        discountAllocations {
          allocatedAmount { amount currencyCode }
          allocatedAmountSet { ...MoneyBagFields }
          discountApplication { ...DiscountApplicationFields }
        }
      }
    }
    returns(first: 250) { nodes { id } }
  }
}


fragment MoneyBagFields on MoneyBag {
  shopMoney { amount currencyCode }
  presentmentMoney { amount currencyCode }
}


fragment MailingAddressFields on MailingAddress {
  firstName
  lastName
  company
  address1
  address2
  city
  province
  country
  zip
  phone
  name
  provinceCode
  countryCode
  latitude
  longitude
}


fragment CustomerFields on Customer {
  id
  createdAt
  updatedAt
  firstName
  lastName
  state
  note
  verifiedEmail
  multipassIdentifier
  taxExempt
  email
  phone
  taxExemptions
  defaultAddress { ...MailingAddressFields }
}


fragment TaxLineFields on TaxLine {
  title
  rate
  price
  priceSet { ...MoneyBagFields }
  channelLiable
}


fragment DiscountApplicationFields on DiscountApplication {
  __typename
  index
  allocationMethod
  targetSelection
  targetType
  value {
    ... on MoneyV2 { amount currencyCode }
    ... on PricingPercentageValue { percentage }
  }
  ... on AutomaticDiscountApplication { title }
  ... on DiscountCodeApplication { code }
  ... on ManualDiscountApplication { title }
  ... on ScriptDiscountApplication { title }
}
```

## Starter query: orders/cancelled

```graphql
query order_cancelled_starter($orderId: ID!) {
  order(id: $orderId) {
    id
    app { id }
    clientIp
    customerAcceptsMarketing
    name
    cancelReason
    cancelledAt
    cartToken
    checkoutToken
    closedAt
    confirmationNumber
    confirmed
    email
    createdAt
    currencyCode
    currentShippingPriceSet { ...MoneyBagFields }
    currentSubtotalPriceSet { ...MoneyBagFields }
    currentTotalAdditionalFeesSet { ...MoneyBagFields }
    currentTotalDiscountsSet { ...MoneyBagFields }
    currentTotalDutiesSet { ...MoneyBagFields }
    currentTotalPriceSet { ...MoneyBagFields }
    currentTotalTaxSet { ...MoneyBagFields }
    customerLocale
    discountCodes
    dutiesIncluded
    estimatedTaxes
    displayFinancialStatus
    displayFulfillmentStatus
    landingPageUrl
    landingPageDisplayText
    physicalLocation { id }
    retailLocation { id }
    merchantBusinessEntity { id }
    merchantOfRecordApp { id }
    note
    customAttributes { key value }
    number
    statusPageUrl
    originalTotalAdditionalFeesSet { ...MoneyBagFields }
    originalTotalDutiesSet { ...MoneyBagFields }
    paymentGatewayNames
    phone
    poNumber
    presentmentCurrencyCode
    processedAt
    referrerUrl
    sourceIdentifier
    sourceName
    registeredSourceUrl
    subtotalPriceSet { ...MoneyBagFields }
    tags
    taxExempt
    taxLines { ...TaxLineFields }
    taxesIncluded
    test
    totalCashRoundingAdjustment {
      paymentSet { ...MoneyBagFields }
      refundSet { ...MoneyBagFields }
    }
    totalDiscountsSet { ...MoneyBagFields }
    totalOutstandingSet { ...MoneyBagFields }
    totalPriceSet { ...MoneyBagFields }
    totalShippingPriceSet { ...MoneyBagFields }
    totalTaxSet { ...MoneyBagFields }
    totalTipReceived { amount currencyCode }
    totalWeight
    updatedAt
    staffMember { id }
    billingAddress { ...MailingAddressFields }
    customer { ...CustomerFields }
    discountApplications(first: 250) {
      nodes { ...DiscountApplicationFields }
    }
    fulfillments { id }
    lineItems(first: 250) {
      nodes {
        id
        currentQuantity
        fulfillableQuantity
        fulfillmentService { handle }
        fulfillmentStatus
        isGiftCard
        weight { value unit }
        name
        originalUnitPrice
        originalUnitPriceSet { ...MoneyBagFields }
        product { id }
        customAttributes { key value }
        quantity
        requiresShipping
        lineItemGroup { id }
        sku
        taxable
        title
        totalDiscount
        totalDiscountSet { ...MoneyBagFields }
        variant { id }
        variantTitle
        vendor
        staffMember { id }
        taxLines { ...TaxLineFields }
        duties {
          id
          countryCodeOfOrigin
          harmonizedSystemCode
          price { ...MoneyBagFields }
          taxLines { ...TaxLineFields }
        }
        discountAllocations {
          allocatedAmount { amount currencyCode }
          allocatedAmountSet { ...MoneyBagFields }
          discountApplication { ...DiscountApplicationFields }
        }
      }
    }
    paymentTerms { id }
    refunds { id }
    shippingAddress { ...MailingAddressFields }
    shippingLines(first: 250, includeRemovals: true) {
      nodes {
        id
        carrierIdentifier
        code
        currentDiscountedPriceSet { ...MoneyBagFields }
        discountedPrice { amount currencyCode }
        discountedPriceSet { ...MoneyBagFields }
        isRemoved
        phone
        originalPrice { amount currencyCode }
        originalPriceSet { ...MoneyBagFields }
        source
        title
        taxLines { ...TaxLineFields }
        discountAllocations {
          allocatedAmount { amount currencyCode }
          allocatedAmountSet { ...MoneyBagFields }
          discountApplication { ...DiscountApplicationFields }
        }
      }
    }
    returns(first: 250) { nodes { id } }
  }
}


fragment MoneyBagFields on MoneyBag {
  shopMoney { amount currencyCode }
  presentmentMoney { amount currencyCode }
}


fragment MailingAddressFields on MailingAddress {
  firstName
  lastName
  company
  address1
  address2
  city
  province
  country
  zip
  phone
  name
  provinceCode
  countryCode
  latitude
  longitude
}


fragment CustomerFields on Customer {
  id
  createdAt
  updatedAt
  firstName
  lastName
  state
  note
  verifiedEmail
  multipassIdentifier
  taxExempt
  email
  phone
  taxExemptions
  defaultAddress { ...MailingAddressFields }
}


fragment TaxLineFields on TaxLine {
  title
  rate
  price
  priceSet { ...MoneyBagFields }
  channelLiable
}


fragment DiscountApplicationFields on DiscountApplication {
  __typename
  index
  allocationMethod
  targetSelection
  targetType
  value {
    ... on MoneyV2 { amount currencyCode }
    ... on PricingPercentageValue { percentage }
  }
  ... on AutomaticDiscountApplication { title }
  ... on DiscountCodeApplication { code }
  ... on ManualDiscountApplication { title }
  ... on ScriptDiscountApplication { title }
}
```

## Starter query: orders/fulfilled

```graphql
query order_fulfilled_starter($orderId: ID!) {
  order(id: $orderId) {
    id
    app { id }
    clientIp
    customerAcceptsMarketing
    cancelReason
    cancelledAt
    cartToken
    checkoutToken
    closedAt
    confirmationNumber
    confirmed
    email
    createdAt
    currencyCode
    currentShippingPriceSet { ...MoneyBagFields }
    currentSubtotalPriceSet { ...MoneyBagFields }
    currentTotalAdditionalFeesSet { ...MoneyBagFields }
    currentTotalDiscountsSet { ...MoneyBagFields }
    currentTotalDutiesSet { ...MoneyBagFields }
    currentTotalPriceSet { ...MoneyBagFields }
    currentTotalTaxSet { ...MoneyBagFields }
    customerLocale
    discountCodes
    dutiesIncluded
    estimatedTaxes
    name
    displayFinancialStatus
    displayFulfillmentStatus
    landingPageUrl
    landingPageDisplayText
    physicalLocation { id }
    retailLocation { id }
    merchantBusinessEntity { id }
    merchantOfRecordApp { id }
    note
    customAttributes { key value }
    number
    statusPageUrl
    originalTotalAdditionalFeesSet { ...MoneyBagFields }
    originalTotalDutiesSet { ...MoneyBagFields }
    paymentGatewayNames
    phone
    poNumber
    presentmentCurrencyCode
    processedAt
    referrerUrl
    sourceIdentifier
    sourceName
    registeredSourceUrl
    subtotalPriceSet { ...MoneyBagFields }
    tags
    taxExempt
    taxLines { ...TaxLineFields }
    taxesIncluded
    test
    totalCashRoundingAdjustment {
      paymentSet { ...MoneyBagFields }
      refundSet { ...MoneyBagFields }
    }
    totalDiscountsSet { ...MoneyBagFields }
    totalOutstandingSet { ...MoneyBagFields }
    totalPriceSet { ...MoneyBagFields }
    totalShippingPriceSet { ...MoneyBagFields }
    totalTaxSet { ...MoneyBagFields }
    totalTipReceived { amount currencyCode }
    totalWeight
    updatedAt
    staffMember { id }
    billingAddress { ...MailingAddressFields }
    customer { ...CustomerFields }
    discountApplications(first: 250) {
      nodes { ...DiscountApplicationFields }
    }
    fulfillments { id }
    lineItems(first: 250) {
      nodes {
        id
        currentQuantity
        fulfillableQuantity
        fulfillmentService { handle }
        fulfillmentStatus
        isGiftCard
        weight { value unit }
        name
        originalUnitPrice
        originalUnitPriceSet { ...MoneyBagFields }
        product { id }
        customAttributes { key value }
        quantity
        requiresShipping
        lineItemGroup { id }
        sku
        taxable
        title
        totalDiscount
        totalDiscountSet { ...MoneyBagFields }
        variant { id }
        variantTitle
        vendor
        staffMember { id }
        taxLines { ...TaxLineFields }
        duties {
          id
          countryCodeOfOrigin
          harmonizedSystemCode
          price { ...MoneyBagFields }
          taxLines { ...TaxLineFields }
        }
        discountAllocations {
          allocatedAmount { amount currencyCode }
          allocatedAmountSet { ...MoneyBagFields }
          discountApplication { ...DiscountApplicationFields }
        }
      }
    }
    paymentTerms { id }
    refunds { id }
    shippingAddress { ...MailingAddressFields }
    shippingLines(first: 250, includeRemovals: true) {
      nodes {
        id
        carrierIdentifier
        code
        currentDiscountedPriceSet { ...MoneyBagFields }
        discountedPrice { amount currencyCode }
        discountedPriceSet { ...MoneyBagFields }
        isRemoved
        phone
        originalPrice { amount currencyCode }
        originalPriceSet { ...MoneyBagFields }
        source
        title
        taxLines { ...TaxLineFields }
        discountAllocations {
          allocatedAmount { amount currencyCode }
          allocatedAmountSet { ...MoneyBagFields }
          discountApplication { ...DiscountApplicationFields }
        }
      }
    }
    returns(first: 250) { nodes { id } }
  }
}


fragment MoneyBagFields on MoneyBag {
  shopMoney { amount currencyCode }
  presentmentMoney { amount currencyCode }
}


fragment MailingAddressFields on MailingAddress {
  firstName
  lastName
  company
  address1
  address2
  city
  province
  country
  zip
  phone
  name
  provinceCode
  countryCode
  latitude
  longitude
}


fragment CustomerFields on Customer {
  id
  createdAt
  updatedAt
  firstName
  lastName
  state
  note
  verifiedEmail
  multipassIdentifier
  taxExempt
  email
  phone
  taxExemptions
  defaultAddress { ...MailingAddressFields }
}


fragment TaxLineFields on TaxLine {
  title
  rate
  price
  priceSet { ...MoneyBagFields }
  channelLiable
}


fragment DiscountApplicationFields on DiscountApplication {
  __typename
  index
  allocationMethod
  targetSelection
  targetType
  value {
    ... on MoneyV2 { amount currencyCode }
    ... on PricingPercentageValue { percentage }
  }
  ... on AutomaticDiscountApplication { title }
  ... on DiscountCodeApplication { code }
  ... on ManualDiscountApplication { title }
  ... on ScriptDiscountApplication { title }
}
```

## Starter query: orders/partially\_fulfilled

```graphql
query order_partially_fulfilled_starter($orderId: ID!) {
  order(id: $orderId) {
    id
    app { id }
    clientIp
    customerAcceptsMarketing
    cancelReason
    cancelledAt
    cartToken
    checkoutToken
    closedAt
    confirmationNumber
    confirmed
    email
    createdAt
    currencyCode
    currentShippingPriceSet { ...MoneyBagFields }
    currentSubtotalPriceSet { ...MoneyBagFields }
    currentTotalAdditionalFeesSet { ...MoneyBagFields }
    currentTotalDiscountsSet { ...MoneyBagFields }
    currentTotalDutiesSet { ...MoneyBagFields }
    currentTotalPriceSet { ...MoneyBagFields }
    currentTotalTaxSet { ...MoneyBagFields }
    customerLocale
    discountCodes
    dutiesIncluded
    estimatedTaxes
    name
    displayFinancialStatus
    displayFulfillmentStatus
    landingPageUrl
    landingPageDisplayText
    physicalLocation { id }
    retailLocation { id }
    merchantBusinessEntity { id }
    merchantOfRecordApp { id }
    note
    customAttributes { key value }
    number
    statusPageUrl
    originalTotalAdditionalFeesSet { ...MoneyBagFields }
    originalTotalDutiesSet { ...MoneyBagFields }
    paymentGatewayNames
    phone
    poNumber
    presentmentCurrencyCode
    processedAt
    referrerUrl
    sourceIdentifier
    sourceName
    registeredSourceUrl
    subtotalPriceSet { ...MoneyBagFields }
    tags
    taxExempt
    taxLines { ...TaxLineFields }
    taxesIncluded
    test
    totalCashRoundingAdjustment {
      paymentSet { ...MoneyBagFields }
      refundSet { ...MoneyBagFields }
    }
    totalDiscountsSet { ...MoneyBagFields }
    totalOutstandingSet { ...MoneyBagFields }
    totalPriceSet { ...MoneyBagFields }
    totalShippingPriceSet { ...MoneyBagFields }
    totalTaxSet { ...MoneyBagFields }
    totalTipReceived { amount currencyCode }
    totalWeight
    updatedAt
    staffMember { id }
    billingAddress { ...MailingAddressFields }
    customer { ...CustomerFields }
    discountApplications(first: 250) {
      nodes { ...DiscountApplicationFields }
    }
    fulfillments { id }
    lineItems(first: 250) {
      nodes {
        id
        currentQuantity
        fulfillableQuantity
        fulfillmentService { handle }
        fulfillmentStatus
        isGiftCard
        weight { value unit }
        name
        originalUnitPrice
        originalUnitPriceSet { ...MoneyBagFields }
        product { id }
        customAttributes { key value }
        quantity
        requiresShipping
        lineItemGroup { id }
        sku
        taxable
        title
        totalDiscount
        totalDiscountSet { ...MoneyBagFields }
        variant { id }
        variantTitle
        vendor
        staffMember { id }
        taxLines { ...TaxLineFields }
        duties {
          id
          countryCodeOfOrigin
          harmonizedSystemCode
          price { ...MoneyBagFields }
          taxLines { ...TaxLineFields }
        }
        discountAllocations {
          allocatedAmount { amount currencyCode }
          allocatedAmountSet { ...MoneyBagFields }
          discountApplication { ...DiscountApplicationFields }
        }
      }
    }
    paymentTerms { id }
    refunds { id }
    shippingAddress { ...MailingAddressFields }
    shippingLines(first: 250, includeRemovals: true) {
      nodes {
        id
        carrierIdentifier
        code
        currentDiscountedPriceSet { ...MoneyBagFields }
        discountedPrice { amount currencyCode }
        discountedPriceSet { ...MoneyBagFields }
        isRemoved
        phone
        originalPrice { amount currencyCode }
        originalPriceSet { ...MoneyBagFields }
        source
        title
        taxLines { ...TaxLineFields }
        discountAllocations {
          allocatedAmount { amount currencyCode }
          allocatedAmountSet { ...MoneyBagFields }
          discountApplication { ...DiscountApplicationFields }
        }
      }
    }
    returns(first: 250) { nodes { id } }
  }
}


fragment MoneyBagFields on MoneyBag {
  shopMoney { amount currencyCode }
  presentmentMoney { amount currencyCode }
}


fragment MailingAddressFields on MailingAddress {
  firstName
  lastName
  company
  address1
  address2
  city
  province
  country
  zip
  phone
  name
  provinceCode
  countryCode
  latitude
  longitude
}


fragment CustomerFields on Customer {
  id
  createdAt
  updatedAt
  firstName
  lastName
  state
  note
  verifiedEmail
  multipassIdentifier
  taxExempt
  email
  phone
  taxExemptions
  defaultAddress { ...MailingAddressFields }
}


fragment TaxLineFields on TaxLine {
  title
  rate
  price
  priceSet { ...MoneyBagFields }
  channelLiable
}


fragment DiscountApplicationFields on DiscountApplication {
  __typename
  index
  allocationMethod
  targetSelection
  targetType
  value {
    ... on MoneyV2 { amount currencyCode }
    ... on PricingPercentageValue { percentage }
  }
  ... on AutomaticDiscountApplication { title }
  ... on DiscountCodeApplication { code }
  ... on ManualDiscountApplication { title }
  ... on ScriptDiscountApplication { title }
}
```

## Starter query: orders/paid

```graphql
query order_paid_starter($orderId: ID!) {
  order(id: $orderId) {
    id
    app { id }
    clientIp
    customerAcceptsMarketing
    cancelReason
    cancelledAt
    cartToken
    checkoutToken
    closedAt
    confirmationNumber
    confirmed
    email
    createdAt
    currencyCode
    currentShippingPriceSet { ...MoneyBagFields }
    currentSubtotalPriceSet { ...MoneyBagFields }
    currentTotalAdditionalFeesSet { ...MoneyBagFields }
    currentTotalDiscountsSet { ...MoneyBagFields }
    currentTotalDutiesSet { ...MoneyBagFields }
    currentTotalPriceSet { ...MoneyBagFields }
    currentTotalTaxSet { ...MoneyBagFields }
    customerLocale
    discountCodes
    dutiesIncluded
    estimatedTaxes
    name
    displayFinancialStatus
    displayFulfillmentStatus
    landingPageUrl
    landingPageDisplayText
    physicalLocation { id }
    retailLocation { id }
    merchantBusinessEntity { id }
    merchantOfRecordApp { id }
    note
    customAttributes { key value }
    number
    statusPageUrl
    originalTotalAdditionalFeesSet { ...MoneyBagFields }
    originalTotalDutiesSet { ...MoneyBagFields }
    paymentGatewayNames
    phone
    poNumber
    presentmentCurrencyCode
    processedAt
    referrerUrl
    sourceIdentifier
    sourceName
    registeredSourceUrl
    subtotalPriceSet { ...MoneyBagFields }
    tags
    taxExempt
    taxLines { ...TaxLineFields }
    taxesIncluded
    test
    totalCashRoundingAdjustment {
      paymentSet { ...MoneyBagFields }
      refundSet { ...MoneyBagFields }
    }
    totalDiscountsSet { ...MoneyBagFields }
    totalOutstandingSet { ...MoneyBagFields }
    totalPriceSet { ...MoneyBagFields }
    totalShippingPriceSet { ...MoneyBagFields }
    totalTaxSet { ...MoneyBagFields }
    totalTipReceived { amount currencyCode }
    totalWeight
    updatedAt
    staffMember { id }
    billingAddress { ...MailingAddressFields }
    customer { ...CustomerFields }
    discountApplications(first: 250) {
      nodes { ...DiscountApplicationFields }
    }
    fulfillments { id }
    lineItems(first: 250) {
      nodes {
        id
        currentQuantity
        fulfillableQuantity
        fulfillmentService { handle }
        fulfillmentStatus
        isGiftCard
        weight { value unit }
        name
        originalUnitPrice
        originalUnitPriceSet { ...MoneyBagFields }
        product { id }
        customAttributes { key value }
        quantity
        requiresShipping
        lineItemGroup { id }
        sku
        taxable
        title
        totalDiscount
        totalDiscountSet { ...MoneyBagFields }
        variant { id }
        variantTitle
        vendor
        staffMember { id }
        taxLines { ...TaxLineFields }
        duties {
          id
          countryCodeOfOrigin
          harmonizedSystemCode
          price { ...MoneyBagFields }
          taxLines { ...TaxLineFields }
        }
        discountAllocations {
          allocatedAmount { amount currencyCode }
          allocatedAmountSet { ...MoneyBagFields }
          discountApplication { ...DiscountApplicationFields }
        }
      }
    }
    paymentTerms { id }
    refunds { id }
    shippingAddress { ...MailingAddressFields }
    shippingLines(first: 250, includeRemovals: true) {
      nodes {
        id
        carrierIdentifier
        code
        currentDiscountedPriceSet { ...MoneyBagFields }
        discountedPrice { amount currencyCode }
        discountedPriceSet { ...MoneyBagFields }
        isRemoved
        phone
        originalPrice { amount currencyCode }
        originalPriceSet { ...MoneyBagFields }
        source
        title
        taxLines { ...TaxLineFields }
        discountAllocations {
          allocatedAmount { amount currencyCode }
          allocatedAmountSet { ...MoneyBagFields }
          discountApplication { ...DiscountApplicationFields }
        }
      }
    }
    returns(first: 250) { nodes { id } }
  }
}


fragment MoneyBagFields on MoneyBag {
  shopMoney { amount currencyCode }
  presentmentMoney { amount currencyCode }
}


fragment MailingAddressFields on MailingAddress {
  firstName
  lastName
  company
  address1
  address2
  city
  province
  country
  zip
  phone
  name
  provinceCode
  countryCode
  latitude
  longitude
}


fragment CustomerFields on Customer {
  id
  createdAt
  updatedAt
  firstName
  lastName
  state
  note
  verifiedEmail
  multipassIdentifier
  taxExempt
  email
  phone
  taxExemptions
  defaultAddress { ...MailingAddressFields }
}


fragment TaxLineFields on TaxLine {
  title
  rate
  price
  priceSet { ...MoneyBagFields }
  channelLiable
}


fragment DiscountApplicationFields on DiscountApplication {
  __typename
  index
  allocationMethod
  targetSelection
  targetType
  value {
    ... on MoneyV2 { amount currencyCode }
    ... on PricingPercentageValue { percentage }
  }
  ... on AutomaticDiscountApplication { title }
  ... on DiscountCodeApplication { code }
  ... on ManualDiscountApplication { title }
  ... on ScriptDiscountApplication { title }
}
```

### `Product`

Use the [`Product`](https://shopify.dev/docs/api/events/latest/product) reference to choose supported triggers and query variables for your app.

`variants/in_stock` and `variants/out_of_stock` describe availability transitions. `Product` doesn't expose an inventory quantity trigger. Review the `InventoryItem` inventory-level triggers for quantity changes.

##### `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_html` becomes `bodyHtml`.
* `category` becomes `category { id name fullName }`.
* `has_variants_that_requires_components` becomes `hasVariantsThatRequiresComponents`.
* The webhook's `admin_graphql_api_id` becomes `id`. Numeric REST IDs don't have an Events equivalent.
* `image` (featured image) becomes `featuredImage`.
* `image_id` on variants becomes `image { id url altText }`.
* The classic webhook embeds `images`, `media`, and `variants` as flat arrays. The query selects them through connections with `nodes`. Variant `id` values correspond to `variant_gids`; connection pagination can omit variants.
* `inventory_item_id` on variants becomes `inventoryItem { id }`.
* `inventory_policy` on variants and `status` use uppercase enum values (`DENY`/`CONTINUE` and `ACTIVE`/`DRAFT`/`ARCHIVED`).
* `option1`/`option2`/`option3` on variants become `selectedOptions { name value }`.
* `tags` returns an array instead of a comma-separated string.
* `old_inventory_quantity` on variants and `published_scope` have 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](https://shopify.dev/docs/apps/build/events/optimizing-your-subscriptions).

##### `products/delete`

No query is needed for a deleted `Product`. Use `query_variables.productId` to identify it.

## Starter query: products/create

```graphql
query product_create_starter($productId: ID!) {
  product(id: $productId) {
    id
    title
    handle
    status
    vendor
    productType
    bodyHtml
    createdAt
    updatedAt
    publishedAt
    templateSuffix
    tags
    hasVariantsThatRequiresComponents
    options {
      id
      name
      position
      values
    }
    featuredImage {
      id
      altText
      url
      width
      height
    }
    category {
      id
      name
      fullName
    }
    variants(first: 250) {
      nodes {
        id
        title
        price
        compareAtPrice
        sku
        barcode
        position
        createdAt
        updatedAt
        taxable
        inventoryPolicy
        inventoryQuantity
        product { id }
        image { id url altText }
        inventoryItem { id }
        selectedOptions { name value }
      }
    }
    media(first: 250) {
      nodes {
        ... on Media {
          id
          alt
          mediaContentType
          status
          preview {
            status
            image {
              id
              altText
              url
              width
              height
            }
          }
        }
        ... on MediaImage {
          image {
            id
            altText
            url
            width
            height
          }
        }
        ... on Video {
          duration
          sources {
            fileSize
            format
            height
            mimeType
            url
            width
          }
        }
        ... on Model3d {
          sources {
            filesize
            format
            mimeType
            url
          }
        }
        ... on ExternalVideo {
          embedUrl
          host
          originUrl
        }
      }
    }
    images(first: 250) {
      nodes {
        id
        altText
        url
        width
        height
      }
    }
  }
}
```

## Starter query: products/update

```graphql
query product_update_starter($productId: ID!) {
  product(id: $productId) {
    id
    title
    handle
    status
    vendor
    productType
    bodyHtml
    createdAt
    updatedAt
    publishedAt
    templateSuffix
    tags
    hasVariantsThatRequiresComponents
    options {
      id
      name
      position
      values
    }
    featuredImage {
      id
      altText
      url
      width
      height
    }
    category {
      id
      name
      fullName
    }
    variants(first: 250) {
      nodes {
        id
        title
        price
        compareAtPrice
        sku
        barcode
        position
        createdAt
        updatedAt
        taxable
        inventoryPolicy
        inventoryQuantity
        product { id }
        image { id url altText }
        inventoryItem { id }
        selectedOptions { name value }
      }
    }
    media(first: 250) {
      nodes {
        ... on Media {
          id
          alt
          mediaContentType
          status
          preview {
            status
            image {
              id
              altText
              url
              width
              height
            }
          }
        }
        ... on MediaImage {
          image {
            id
            altText
            url
            width
            height
          }
        }
        ... on Video {
          duration
          sources {
            fileSize
            format
            height
            mimeType
            url
            width
          }
        }
        ... on Model3d {
          sources {
            filesize
            format
            mimeType
            url
          }
        }
        ... on ExternalVideo {
          embedUrl
          host
          originUrl
        }
      }
    }
    images(first: 250) {
      nodes {
        id
        altText
        url
        width
        height
      }
    }
  }
}
```

### `Refund`

Use the [`Refund`](https://shopify.dev/docs/api/events/latest/refund) reference to choose supported actions and query variables for your app. Refunds also support `update` actions for supported refund line item, adjustment, and transaction changes. They don't support a `delete` action.

##### `refunds/create`

Use this query to translate the fields from a refund creation webhook. Subscribe to the `create` action and query with `refundId`.

Field differences:

* `created_at` becomes `createdAt`.
* `order_id` becomes `order { id }`.
* The webhook's `admin_graphql_api_id` becomes `id`. Numeric REST IDs don't have an Events equivalent.

Query the parent refund and select the child data your handler needs. Don't assume child IDs in `fields_changed` are available as payload query variables.

## Starter query: refunds/create

```graphql
query refunds_create_starter($refundId: ID!) {
  refund(id: $refundId) {
    id
    createdAt
    note
    order { id }
    totalRefundedSet {
      shopMoney { amount currencyCode }
      presentmentMoney { amount currencyCode }
    }
  }
}
```

### `Return`

Use the [`Return`](https://shopify.dev/docs/api/events/latest/return) reference to choose supported triggers and query variables for your app. Query the return with `returnId`. Returns can start in either `REQUESTED` or already-approved `OPEN` status. Later approval, decline, cancellation, closing, and reopening use supported update triggers and the resulting state.

Events queries return the state when the query runs, which can differ from the state when the event occurred. A requested return can be approved before its `create` query runs, and an already-approved return can close or cancel before that query runs. Don't use the queried status to determine which operation created the return.

`returnLineItems` contains interface values. Use an inline fragment on `ReturnLineItem` when selecting concrete fields such as `restockingFee`. The reference includes examples for these fields.

##### `returns/request`

Keep the `returns/request` webhook when your app needs a notification for each return request. The Events `create` action includes both requested returns and already-approved returns created through `returnCreate`. Checking the queried `status` for `REQUESTED` can miss a request that Shopify has already approved.

For current-state synchronization, subscribe to the `create` action and the `update` action with the `return.status` trigger. Use the starter query to retrieve the return's current state, not to reconstruct a request notification.

The webhook's `admin_graphql_api_id` becomes `id`. Select `order { id }` for the related order's GraphQL ID.

##### `returns/approve`

Keep the `returns/approve` webhook when your app needs approval notifications, including for returns created already approved through `returnCreate`. Checking an Events `create` delivery's queried status for `OPEN` doesn't reliably identify those operations. A return can reach `OPEN` after creation or leave it before the query runs.

To refresh approval data with Events, subscribe to the `create` action and the `update` action with the `return.requestApprovedAt` trigger. Query `status` and `requestApprovedAt` as current values. A `create` delivery and a later approval update can both show the same approval, so don't run an approval operation for each delivery. A status change alone doesn't necessarily indicate approval either. For example, reopening a return also changes its status to `OPEN`.

##### `returns/decline`

Review `return.decline` and `return.status` for decline changes. Add the decline selections your handler needs and evaluate the resulting state.

##### `returns/cancel`

Review `return.status` and evaluate the resulting state for cancellation. Cancellation is an update, not deletion of the return.

##### `returns/close`

Review `return.closedAt` and `return.status` for closing changes. Use the queried status and timestamp to evaluate the resulting state.

##### `returns/reopen`

Review `return.status` and `return.closedAt` for reopening changes.

##### `returns/process`

Review `return.returnLineItems.refundedQuantity` and `return.returnLineItems.unprocessedQuantity` for processing changes. Add the current line item quantities your handler needs to the query.

##### `returns/update`

Review supported return field and line item quantity triggers. Add the fields your handler reads to the query and use `fields_changed` to identify changed fields.

##### `reverse_fulfillment_orders/dispose`

Continue using the `reverse_fulfillment_orders/dispose` webhook for disposal notifications. Events doesn't provide an equivalent disposition trigger in `2026-10`. A disposal can occur without changing the reverse fulfillment order's status or removing a line item, so `return.reverseFulfillmentOrders.*` doesn't reliably capture these operations.

## Starter query: returns/request

```graphql
query returns_request_starter($returnId: ID!) {
  return(id: $returnId) {
    id
    name
    status
    closedAt
    order { id }
  }
}
```

## Starter query: returns/approve

```graphql
query returns_approve_starter($returnId: ID!) {
  return(id: $returnId) {
    id
    name
    status
    requestApprovedAt
    closedAt
    order { id }
  }
}
```

## Starter query: returns/decline

```graphql
query returns_decline_starter($returnId: ID!) {
  return(id: $returnId) {
    id
    name
    status
    closedAt
    order { id }
  }
}
```

## Starter query: returns/cancel

```graphql
query returns_cancel_starter($returnId: ID!) {
  return(id: $returnId) {
    id
    name
    status
    closedAt
    order { id }
  }
}
```

## Starter query: returns/close

```graphql
query returns_close_starter($returnId: ID!) {
  return(id: $returnId) {
    id
    name
    status
    closedAt
    order { id }
  }
}
```

## Starter query: returns/reopen

```graphql
query returns_reopen_starter($returnId: ID!) {
  return(id: $returnId) {
    id
    name
    status
    closedAt
    order { id }
  }
}
```

## Starter query: returns/process

```graphql
query returns_process_starter($returnId: ID!) {
  return(id: $returnId) {
    id
    name
    status
    closedAt
    order { id }
  }
}
```

## Starter query: returns/update

```graphql
query returns_update_starter($returnId: ID!) {
  return(id: $returnId) {
    id
    name
    status
    closedAt
    order { id }
  }
}
```

***

## Step 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 include `update` requires triggers.
* For `delete`, identify the deleted resource from `query_variables` instead 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](https://shopify.dev/docs/apps/build/events/delivery-filtering) for trigger and query filter behavior.

***

## Step 3: Optimize your query

Use [Optimizing your subscriptions](https://shopify.dev/docs/apps/build/events/optimizing-your-subscriptions) for the detailed workflow and worked examples. Apply it to each starter query before configuring your replacement subscription:

1. Keep only the fields your handler and `query_filter` need. If your classic webhook uses `include_fields`, then use that list as your starting point.
2. Follow the optimization guide to choose targeted queries, split subscriptions, and reconcile any data that deliveries don't include.
3. [Validate the optimized subscription](https://shopify.dev/docs/apps/build/events/optimizing-your-subscriptions#step-8-validate-the-optimized-subscriptions) 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-release` to validate without releasing the version to users.

***

## Next steps

* [Subscribe to Events](https://shopify.dev/docs/apps/build/events/subscribe): Configure a subscription after you've chosen its actions, triggers, and optimized query.
* [Events delivery structure](https://shopify.dev/docs/apps/build/events/delivery-structure): Update your handler for GraphQL field names, query variables, and delivery metadata.
* [Troubleshoot Events](https://shopify.dev/docs/apps/build/events/troubleshoot): Inspect test deliveries and diagnose failures before changing your existing webhook coverage.

***
