---
title: Migrate from webhooks
description: >-
  Map your existing classic webhook subscriptions to Events equivalents, update
  your app configuration, and take advantage of field-level triggers and custom
  GraphQL payloads.
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'
---

# 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](https://shopify.dev/docs/apps/build/events-webhooks) for a full comparison.

**Developer preview:**

Events is in developer preview on the [`unstable`](https://shopify.dev/docs/api/usage/versioning#making-requests-to-an-api-version) API version, available today for a [subset of topics](https://shopify.dev/docs/api/events). 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.

| | Classic webhooks | Events |
| - | - | - |
| **Configuration** | TOML or GraphQL Admin API | TOML only |
| **Topics** | Topic string: `products/update` | Topic + actions: `topic = "Product"` `actions = ["update"]` |
| **Triggers** | Fires on any change to the resource | `update` fires only when listed `triggers` fields change. `create` and `delete` always fire |
| **Payload** | Fixed REST schema | Custom GraphQL query |
| **Delivery filter** | `filter` evaluates against the payload | `query_filter` evaluates against `query` response |

***

## Requirements

Events require 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.

***

## Step 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_version` is 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 `triggers` or `query_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.

## shopify.app.toml

```toml
[webhooks]
api_version = "2026-04"


[[webhooks.subscriptions]]
topics = ["orders/fulfilled"]
uri = "https://your-app.com/webhooks/orders"


[events]
api_version = "unstable"


[[events.subscription]]
handle = "customer-enabled"
topic = "Customer"
actions = ["update"]
triggers = ["customer.state"]
uri = "https://your-app.com/events/customers"


query = """
  query customer_state_payload($customerId: ID!) {
    customer(id: $customerId) {
      id
      state
    }
  }
"""


query_filter = "customer.state:'ENABLED'"
```

### Customer

The [`Customer`](https://shopify.dev/docs/api/events/latest/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 REST 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:

* `addresses` is a connection (`addresses.nodes`).
* `currency` has no Events equivalent. The webhook derived it from store defaults.
* `id` is a GID string instead of an integer. `admin_graphql_api_id` is the same value.
* `last_order_id` and `last_order_name` merge into `lastOrder { id name }`.
* `state` uses uppercase enum values.
* `tags` returns an array instead of a comma-separated string.
* `total_spent` becomes `amountSpent { 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:

* `addresses` is a connection (`addresses.nodes`).
* `currency` has no Events equivalent. The webhook derived it from store defaults.
* `id` is a GID string instead of an integer. `admin_graphql_api_id` is the same value.
* `last_order_id` and `last_order_name` merge into `lastOrder { id name }`.
* `state` uses uppercase enum values.
* `tags` returns an array instead of a comma-separated string.
* `total_spent` becomes `amountSpent { 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:

* `addresses` is a connection (`addresses.nodes`).
* `currency` has no Events equivalent. The webhook derived it from store defaults.
* `id` is a GID string instead of an integer. `admin_graphql_api_id` is the same value.
* `last_order_id` and `last_order_name` merge into `lastOrder { id name }`.
* `state` is always `"DISABLED"`.
* `tags` returns an array instead of a comma-separated string.
* `total_spent` becomes `amountSpent { 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:

* `addresses` is a connection (`addresses.nodes`).
* `currency` has no Events equivalent. The webhook derived it from store defaults.
* `id` is a GID string instead of an integer. `admin_graphql_api_id` is the same value.
* `last_order_id` and `last_order_name` merge into `lastOrder { id name }`.
* `state` is always `"ENABLED"`.
* `tags` returns an array instead of a comma-separated string.
* `total_spent` becomes `amountSpent { amount currencyCode }`.

##### `customer.tags_added`

Fires when tags are added to a customer. Maps to a `Customer` update subscription with `triggers = ["customer.tags"]`.

Unlike `customers/disable` and `customers/enable`, there's no `query_filter` expression that can distinguish a tag addition from a tag removal. `query_filter` evaluates the resource's current state, not the delta. Both `customer.tags_added` and `customer.tags_removed` map to the same Events subscription. Your handler receives the full current tag set and must determine what changed by comparing against its own stored state.

The webhook payload is already GraphQL-flavored. 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`

Fires when tags are removed from a customer. Maps to the same `Customer` update subscription as `customer.tags_added`. There's no `query_filter` expression that can distinguish a tag removal from a tag addition, because `query_filter` evaluates the resource's current state, not the delta. Your handler receives the remaining tag set and must determine what was removed by comparing against its own stored state.

The webhook payload is already GraphQL-flavored. 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`

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:

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

Maps directly to a `Customer` delete subscription. No `triggers` or `query` are needed.

* `admin_graphql_api_id` and `id` are available as `query_variables.customerId`.
* `data.customer` is `null`. The customer is deleted before the query runs.
* `tax_exemptions` has 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.

## Migrating customers/create webhook to Events

##### Webhook (TOML)

```toml
[webhooks]
api_version = "2026-04"

[[webhooks.subscriptions]]
topics = ["customers/create"]
uri = "https://your-app.com/webhooks/customers"
```

##### Webhook (GraphQL Admin)

```graphql
mutation {
  webhookSubscriptionCreate(
    topic: CUSTOMERS_CREATE
    webhookSubscription: {
      callbackUrl: "https://your-app.com/webhooks/customers"
    }
  ) {
    webhookSubscription { id topic callbackUrl }
  }
}
```

##### Events

```toml
[events]
api_version = "unstable"

[[events.subscription]]
handle = "customer-create"

topic = "Customer"
actions = ["create"]

uri = "https://your-app.com/events/customers"

query = """
  query customer_create_payload($customerId: ID!) {
    customer(id: $customerId) {
      id
      createdAt
      updatedAt
      firstName
      lastName
      state
      note
      verifiedEmail
      taxExempt
      email
      phone
      tags
      taxExemptions
      numberOfOrders
      amountSpent {
        amount
        currencyCode
      }
      lastOrder {
        id
        name
      }
      emailMarketingConsent {
        marketingState
        marketingOptInLevel
        consentUpdatedAt
      }
      smsMarketingConsent {
        marketingState
        marketingOptInLevel
        consentUpdatedAt
      }
      defaultAddress {
        id
        firstName
        lastName
        company
        address1
        address2
        city
        province
        country
        zip
        phone
        name
        provinceCode
        countryCode
      }
      addresses(first: 10) {
        nodes {
          id
          firstName
          lastName
          company
          address1
          address2
          city
          province
          country
          zip
          phone
          name
          provinceCode
          countryCode
        }
      }
    }
  }
"""
```

## {} Comparing responses

##### Classic webhook

```json
{
  "id": 706405506930370084,
  "created_at": "2021-12-31T19:00:00-05:00",
  "updated_at": "2021-12-31T19:00:00-05:00",
  "first_name": "Bob",
  "last_name": "Biller",
  "state": "disabled",
  "note": "This customer loves ice cream",
  "verified_email": true,
  "multipass_identifier": null,
  "tax_exempt": false,
  "email": "bob@biller.com",
  "phone": null,
  "currency": "USD",
  "addresses": [],
  "tax_exemptions": [],
  "admin_graphql_api_id": "gid://shopify/Customer/706405506930370084",
  "default_address": {
    "id": 12321,
    "customer_id": 706405506930370084,
    "first_name": "Bob",
    "last_name": "Biller",
    "company": null,
    "address1": "151 O'Connor Street",
    "address2": null,
    "city": "Ottawa",
    "province": "ON",
    "country": "CA",
    "zip": "K2P 2L8",
    "phone": "555-555-5555",
    "name": "Bob Biller",
    "province_code": "ON",
    "country_code": "CA",
    "country_name": "CA",
    "default": true
  }
}
```

##### Events

```json
{
  "topic": "Customer",
  "action": "create",
  "handle": "customer-create",
  "data": {
    "customer": {
      "id": "gid://shopify/Customer/706405506930370084",
      "createdAt": "2021-12-31T19:00:00-05:00",
      "updatedAt": "2021-12-31T19:00:00-05:00",
      "firstName": "Bob",
      "lastName": "Biller",
      "state": "DISABLED",
      "note": "This customer loves ice cream",
      "verifiedEmail": true,
      "taxExempt": false,
      "email": "bob@biller.com",
      "phone": null,
      "tags": [],
      "taxExemptions": [],
      "numberOfOrders": "0",
      "amountSpent": {
        "amount": "0.00",
        "currencyCode": "USD"
      },
      "lastOrder": null,
      "emailMarketingConsent": null,
      "smsMarketingConsent": null,
      "defaultAddress": {
        "id": "gid://shopify/MailingAddress/12321",
        "firstName": "Bob",
        "lastName": "Biller",
        "company": null,
        "address1": "151 O'Connor Street",
        "address2": null,
        "city": "Ottawa",
        "province": "Ontario",
        "country": "Canada",
        "zip": "K2P 2L8",
        "phone": "555-555-5555",
        "name": "Bob Biller",
        "provinceCode": "ON",
        "countryCode": "CA"
      },
      "addresses": {
        "nodes": [
          {
            "id": "gid://shopify/MailingAddress/12321",
            "firstName": "Bob",
            "lastName": "Biller",
            "company": null,
            "address1": "151 O'Connor Street",
            "address2": null,
            "city": "Ottawa",
            "province": "Ontario",
            "country": "Canada",
            "zip": "K2P 2L8",
            "phone": "555-555-5555",
            "name": "Bob Biller",
            "provinceCode": "ON",
            "countryCode": "CA"
          }
        ]
      }
    }
  },
  "fields_changed": [
    "customer[id: 'gid://shopify/Customer/706405506930370084']"
  ],
  "query_variables": {
    "customerId": "gid://shopify/Customer/706405506930370084"
  }
}
```

## customers/update

##### Webhook (TOML)

```toml
[webhooks]
api_version = "2026-04"

[[webhooks.subscriptions]]
topics = ["customers/update"]
uri = "https://your-app.com/webhooks/customers"
```

##### Webhook (GraphQL Admin)

```graphql
mutation {
  webhookSubscriptionCreate(
    topic: CUSTOMERS_UPDATE
    webhookSubscription: {
      callbackUrl: "https://your-app.com/webhooks/customers"
    }
  ) {
    webhookSubscription { id topic callbackUrl }
  }
}
```

##### Events

```toml
[events]
api_version = "unstable"

[[events.subscription]]
handle = "customer-update"

topic = "Customer"
actions = ["update"]

uri = "https://your-app.com/events/customers"

query = """
  query customer_update_payload($customerId: ID!) {
    customer(id: $customerId) {
      id
      createdAt
      updatedAt
      firstName
      lastName
      state
      note
      verifiedEmail
      taxExempt
      email
      phone
      tags
      taxExemptions
      numberOfOrders
      amountSpent {
        amount
        currencyCode
      }
      lastOrder {
        id
        name
      }
      emailMarketingConsent {
        marketingState
        marketingOptInLevel
        consentUpdatedAt
      }
      smsMarketingConsent {
        marketingState
        marketingOptInLevel
        consentUpdatedAt
      }
      defaultAddress {
        id
        firstName
        lastName
        company
        address1
        address2
        city
        province
        country
        zip
        phone
        name
        provinceCode
        countryCode
      }
      addresses(first: 10) {
        nodes {
          id
          firstName
          lastName
          company
          address1
          address2
          city
          province
          country
          zip
          phone
          name
          provinceCode
          countryCode
        }
      }
    }
  }
"""
```

## {} Response

##### Classic webhook

```json
{
  "id": 706405506930370084,
  "created_at": "2021-12-31T19:00:00-05:00",
  "updated_at": "2021-12-31T19:00:00-05:00",
  "first_name": "Bob",
  "last_name": "Biller",
  "state": "disabled",
  "note": "This customer loves ice cream",
  "verified_email": true,
  "multipass_identifier": null,
  "tax_exempt": false,
  "email": "bob@biller.com",
  "phone": null,
  "currency": "USD",
  "addresses": [],
  "tax_exemptions": [],
  "orders_count": 0,
  "total_spent": "0.00",
  "last_order_id": null,
  "tags": "",
  "last_order_name": null,
  "email_marketing_consent": null,
  "sms_marketing_consent": null,
  "admin_graphql_api_id": "gid://shopify/Customer/706405506930370084",
  "default_address": {
    "id": 12321,
    "customer_id": 706405506930370084,
    "first_name": "Bob",
    "last_name": "Biller",
    "company": null,
    "address1": "151 O'Connor Street",
    "address2": null,
    "city": "Ottawa",
    "province": "ON",
    "country": "CA",
    "zip": "K2P 2L8",
    "phone": "555-555-5555",
    "name": "Bob Biller",
    "province_code": "ON",
    "country_code": "CA",
    "country_name": "CA",
    "default": true
  }
}
```

##### Events

```json
{
  "topic": "Customer",
  "action": "update",
  "handle": "customer-update",
  "data": {
    "customer": {
      "id": "gid://shopify/Customer/706405506930370084",
      "createdAt": "2021-12-31T19:00:00-05:00",
      "updatedAt": "2021-12-31T19:00:00-05:00",
      "firstName": "Bob",
      "lastName": "Biller",
      "state": "DISABLED",
      "note": "This customer loves ice cream",
      "verifiedEmail": true,
      "taxExempt": false,
      "email": "bob@biller.com",
      "phone": null,
      "tags": [],
      "taxExemptions": [],
      "numberOfOrders": "0",
      "amountSpent": {
        "amount": "0.00",
        "currencyCode": "USD"
      },
      "lastOrder": null,
      "emailMarketingConsent": null,
      "smsMarketingConsent": null,
      "defaultAddress": {
        "id": "gid://shopify/MailingAddress/12321",
        "firstName": "Bob",
        "lastName": "Biller",
        "company": null,
        "address1": "151 O'Connor Street",
        "address2": null,
        "city": "Ottawa",
        "province": "Ontario",
        "country": "Canada",
        "zip": "K2P 2L8",
        "phone": "555-555-5555",
        "name": "Bob Biller",
        "provinceCode": "ON",
        "countryCode": "CA"
      },
      "addresses": {
        "nodes": [
          {
            "id": "gid://shopify/MailingAddress/12321",
            "firstName": "Bob",
            "lastName": "Biller",
            "company": null,
            "address1": "151 O'Connor Street",
            "address2": null,
            "city": "Ottawa",
            "province": "Ontario",
            "country": "Canada",
            "zip": "K2P 2L8",
            "phone": "555-555-5555",
            "name": "Bob Biller",
            "provinceCode": "ON",
            "countryCode": "CA"
          }
        ]
      }
    }
  },
  "fields_changed": [
    "customer[id: 'gid://shopify/Customer/706405506930370084'].email"
  ],
  "query_variables": {
    "customerId": "gid://shopify/Customer/706405506930370084"
  }
}
```

## customers/disable

##### Webhook (TOML)

```toml
[webhooks]
api_version = "2026-04"

[[webhooks.subscriptions]]
topics = ["customers/disable"]
uri = "https://your-app.com/webhooks/customers"
```

##### Webhook (GraphQL Admin)

```graphql
mutation {
  webhookSubscriptionCreate(
    topic: CUSTOMERS_DISABLE
    webhookSubscription: {
      callbackUrl: "https://your-app.com/webhooks/customers"
    }
  ) {
    webhookSubscription { id topic callbackUrl }
  }
}
```

##### Events

```toml
[events]
api_version = "unstable"

[[events.subscription]]
handle = "customer-disabled"

topic = "Customer"
actions = ["update"]
triggers = ["customer.state"]
query_filter = "customer.state:'DISABLED'"

uri = "https://your-app.com/events/customers"

query = """
  query customer_state_payload($customerId: ID!) {
    customer(id: $customerId) {
      id
      createdAt
      updatedAt
      firstName
      lastName
      state
      note
      verifiedEmail
      taxExempt
      email
      phone
      tags
      taxExemptions
      numberOfOrders
      amountSpent {
        amount
        currencyCode
      }
      lastOrder {
        id
        name
      }
      emailMarketingConsent {
        marketingState
        marketingOptInLevel
        consentUpdatedAt
      }
      smsMarketingConsent {
        marketingState
        marketingOptInLevel
        consentUpdatedAt
      }
      defaultAddress {
        id
        firstName
        lastName
        company
        address1
        address2
        city
        province
        country
        zip
        phone
        name
        provinceCode
        countryCode
      }
      addresses(first: 10) {
        nodes {
          id
          firstName
          lastName
          company
          address1
          address2
          city
          province
          country
          zip
          phone
          name
          provinceCode
          countryCode
        }
      }
    }
  }
"""
```

## {} Response

##### Classic webhook

```json
{
  "id": 706405506930370084,
  "created_at": "2021-12-31T19:00:00-05:00",
  "updated_at": "2021-12-31T19:00:00-05:00",
  "first_name": "Bob",
  "last_name": "Biller",
  "state": "disabled",
  "note": "This customer loves ice cream",
  "verified_email": true,
  "multipass_identifier": null,
  "tax_exempt": false,
  "email": "bob@biller.com",
  "phone": null,
  "currency": "USD",
  "addresses": [],
  "tax_exemptions": [],
  "admin_graphql_api_id": "gid://shopify/Customer/706405506930370084",
  "default_address": {
    "id": 12321,
    "customer_id": 706405506930370084,
    "first_name": "Bob",
    "last_name": "Biller",
    "company": null,
    "address1": "151 O'Connor Street",
    "address2": null,
    "city": "Ottawa",
    "province": "ON",
    "country": "CA",
    "zip": "K2P 2L8",
    "phone": "555-555-5555",
    "name": "Bob Biller",
    "province_code": "ON",
    "country_code": "CA",
    "country_name": "CA",
    "default": true
  }
}
```

##### Events

```json
{
  "topic": "Customer",
  "action": "update",
  "handle": "customer-disabled",
  "data": {
    "customer": {
      "id": "gid://shopify/Customer/706405506930370084",
      "createdAt": "2021-12-31T19:00:00-05:00",
      "updatedAt": "2021-12-31T19:00:00-05:00",
      "firstName": "Bob",
      "lastName": "Biller",
      "state": "DISABLED",
      "note": "This customer loves ice cream",
      "verifiedEmail": true,
      "taxExempt": false,
      "email": "bob@biller.com",
      "phone": null,
      "tags": [],
      "taxExemptions": [],
      "numberOfOrders": "0",
      "amountSpent": {
        "amount": "0.00",
        "currencyCode": "USD"
      },
      "lastOrder": null,
      "emailMarketingConsent": null,
      "smsMarketingConsent": null,
      "defaultAddress": {
        "id": "gid://shopify/MailingAddress/12321",
        "firstName": "Bob",
        "lastName": "Biller",
        "company": null,
        "address1": "151 O'Connor Street",
        "address2": null,
        "city": "Ottawa",
        "province": "Ontario",
        "country": "Canada",
        "zip": "K2P 2L8",
        "phone": "555-555-5555",
        "name": "Bob Biller",
        "provinceCode": "ON",
        "countryCode": "CA"
      },
      "addresses": {
        "nodes": [
          {
            "id": "gid://shopify/MailingAddress/12321",
            "firstName": "Bob",
            "lastName": "Biller",
            "company": null,
            "address1": "151 O'Connor Street",
            "address2": null,
            "city": "Ottawa",
            "province": "Ontario",
            "country": "Canada",
            "zip": "K2P 2L8",
            "phone": "555-555-5555",
            "name": "Bob Biller",
            "provinceCode": "ON",
            "countryCode": "CA"
          }
        ]
      }
    }
  },
  "fields_changed": [
    "customer[id: 'gid://shopify/Customer/706405506930370084'].state"
  ],
  "query_variables": {
    "customerId": "gid://shopify/Customer/706405506930370084"
  }
}
```

## customers/enable

##### Webhook (TOML)

```toml
[webhooks]
api_version = "2026-04"

[[webhooks.subscriptions]]
topics = ["customers/enable"]
uri = "https://your-app.com/webhooks/customers"
```

##### Webhook (GraphQL Admin)

```graphql
mutation {
  webhookSubscriptionCreate(
    topic: CUSTOMERS_ENABLE
    webhookSubscription: {
      callbackUrl: "https://your-app.com/webhooks/customers"
    }
  ) {
    webhookSubscription { id topic callbackUrl }
  }
}
```

##### Events

```toml
[events]
api_version = "unstable"

[[events.subscription]]
handle = "customer-enabled"

topic = "Customer"
actions = ["update"]
triggers = ["customer.state"]
query_filter = "customer.state:'ENABLED'"

uri = "https://your-app.com/events/customers"

query = """
  query customer_state_payload($customerId: ID!) {
    customer(id: $customerId) {
      id
      createdAt
      updatedAt
      firstName
      lastName
      state
      note
      verifiedEmail
      taxExempt
      email
      phone
      tags
      taxExemptions
      numberOfOrders
      amountSpent {
        amount
        currencyCode
      }
      lastOrder {
        id
        name
      }
      emailMarketingConsent {
        marketingState
        marketingOptInLevel
        consentUpdatedAt
      }
      smsMarketingConsent {
        marketingState
        marketingOptInLevel
        consentUpdatedAt
      }
      defaultAddress {
        id
        firstName
        lastName
        company
        address1
        address2
        city
        province
        country
        zip
        phone
        name
        provinceCode
        countryCode
      }
      addresses(first: 10) {
        nodes {
          id
          firstName
          lastName
          company
          address1
          address2
          city
          province
          country
          zip
          phone
          name
          provinceCode
          countryCode
        }
      }
    }
  }
"""
```

## {} Response

##### Classic webhook

```json
{
  "id": 706405506930370084,
  "created_at": "2021-12-31T19:00:00-05:00",
  "updated_at": "2021-12-31T19:00:00-05:00",
  "first_name": "Bob",
  "last_name": "Biller",
  "state": "enabled",
  "note": "This customer loves ice cream",
  "verified_email": true,
  "multipass_identifier": null,
  "tax_exempt": false,
  "email": "bob@biller.com",
  "phone": null,
  "currency": "USD",
  "addresses": [],
  "tax_exemptions": [],
  "admin_graphql_api_id": "gid://shopify/Customer/706405506930370084",
  "default_address": {
    "id": 12321,
    "customer_id": 706405506930370084,
    "first_name": "Bob",
    "last_name": "Biller",
    "company": null,
    "address1": "151 O'Connor Street",
    "address2": null,
    "city": "Ottawa",
    "province": "ON",
    "country": "CA",
    "zip": "K2P 2L8",
    "phone": "555-555-5555",
    "name": "Bob Biller",
    "province_code": "ON",
    "country_code": "CA",
    "country_name": "CA",
    "default": true
  }
}
```

##### Events

```json
{
  "topic": "Customer",
  "action": "update",
  "handle": "customer-enabled",
  "data": {
    "customer": {
      "id": "gid://shopify/Customer/706405506930370084",
      "createdAt": "2021-12-31T19:00:00-05:00",
      "updatedAt": "2021-12-31T19:00:00-05:00",
      "firstName": "Bob",
      "lastName": "Biller",
      "state": "ENABLED",
      "note": "This customer loves ice cream",
      "verifiedEmail": true,
      "taxExempt": false,
      "email": "bob@biller.com",
      "phone": null,
      "tags": [],
      "taxExemptions": [],
      "numberOfOrders": "0",
      "amountSpent": {
        "amount": "0.00",
        "currencyCode": "USD"
      },
      "lastOrder": null,
      "emailMarketingConsent": null,
      "smsMarketingConsent": null,
      "defaultAddress": {
        "id": "gid://shopify/MailingAddress/12321",
        "firstName": "Bob",
        "lastName": "Biller",
        "company": null,
        "address1": "151 O'Connor Street",
        "address2": null,
        "city": "Ottawa",
        "province": "Ontario",
        "country": "Canada",
        "zip": "K2P 2L8",
        "phone": "555-555-5555",
        "name": "Bob Biller",
        "provinceCode": "ON",
        "countryCode": "CA"
      },
      "addresses": {
        "nodes": [
          {
            "id": "gid://shopify/MailingAddress/12321",
            "firstName": "Bob",
            "lastName": "Biller",
            "company": null,
            "address1": "151 O'Connor Street",
            "address2": null,
            "city": "Ottawa",
            "province": "Ontario",
            "country": "Canada",
            "zip": "K2P 2L8",
            "phone": "555-555-5555",
            "name": "Bob Biller",
            "provinceCode": "ON",
            "countryCode": "CA"
          }
        ]
      }
    }
  },
  "fields_changed": [
    "customer[id: 'gid://shopify/Customer/706405506930370084'].state"
  ],
  "query_variables": {
    "customerId": "gid://shopify/Customer/706405506930370084"
  }
}
```

## customer.tags\_added

##### Webhook (TOML)

```toml
[webhooks]
api_version = "2026-04"

[[webhooks.subscriptions]]
topics = ["customer.tags_added"]
uri = "https://your-app.com/webhooks/customers"
```

##### Webhook (GraphQL Admin)

```graphql
mutation {
  webhookSubscriptionCreate(
    topic: CUSTOMER_TAGS_ADDED
    webhookSubscription: {
      callbackUrl: "https://your-app.com/webhooks/customers"
    }
  ) {
    webhookSubscription { id topic callbackUrl }
  }
}
```

##### Events

```toml
[events]
api_version = "unstable"

[[events.subscription]]
handle = "customer-tags"

topic = "Customer"
actions = ["update"]
triggers = ["customer.tags"]

uri = "https://your-app.com/events/customers"

query = """
  query customer_tags_payload($customerId: ID!) {
    customer(id: $customerId) {
      id
      updatedAt
      tags
    }
  }
"""
```

## {} Response

##### Classic webhook

```json
{
  "customerId": "gid://shopify/Customer/1",
  "tags": ["tag1", "tag2"],
  "occurredAt": "2005-05-05T06:00:00.000Z"
}
```

##### Events

```json
{
  "topic": "Customer",
  "action": "update",
  "handle": "customer-tags",
  "data": {
    "customer": {
      "id": "gid://shopify/Customer/706405506930370084",
      "updatedAt": "2005-05-05T06:00:00.000Z",
      "tags": ["tag1", "tag2"]
    }
  },
  "fields_changed": [
    "customer[id: 'gid://shopify/Customer/706405506930370084'].tags"
  ],
  "query_variables": {
    "customerId": "gid://shopify/Customer/706405506930370084"
  }
}
```

## customer.tags\_removed

##### Webhook (TOML)

```toml
[webhooks]
api_version = "2026-04"

[[webhooks.subscriptions]]
topics = ["customer.tags_removed"]
uri = "https://your-app.com/webhooks/customers"
```

##### Webhook (GraphQL Admin)

```graphql
mutation {
  webhookSubscriptionCreate(
    topic: CUSTOMER_TAGS_REMOVED
    webhookSubscription: {
      callbackUrl: "https://your-app.com/webhooks/customers"
    }
  ) {
    webhookSubscription { id topic callbackUrl }
  }
}
```

##### Events

```toml
[events]
api_version = "unstable"

[[events.subscription]]
handle = "customer-tags"

topic = "Customer"
actions = ["update"]
triggers = ["customer.tags"]

uri = "https://your-app.com/events/customers"

query = """
  query customer_tags_payload($customerId: ID!) {
    customer(id: $customerId) {
      id
      updatedAt
      tags
    }
  }
"""
```

## {} Response

##### Classic webhook

```json
{
  "customerId": "gid://shopify/Customer/1",
  "tags": ["tag1", "tag2"],
  "occurredAt": "2005-05-05T06:00:00.000Z"
}
```

##### Events

```json
{
  "topic": "Customer",
  "action": "update",
  "handle": "customer-tags",
  "data": {
    "customer": {
      "id": "gid://shopify/Customer/706405506930370084",
      "updatedAt": "2005-05-05T06:00:00.000Z",
      "tags": ["tag1"]
    }
  },
  "fields_changed": [
    "customer[id: 'gid://shopify/Customer/706405506930370084'].tags"
  ],
  "query_variables": {
    "customerId": "gid://shopify/Customer/706405506930370084"
  }
}
```

## customers/purchasing\_summary

##### Webhook (TOML)

```toml
[webhooks]
api_version = "2026-04"

[[webhooks.subscriptions]]
topics = ["customers/purchasing_summary"]
uri = "https://your-app.com/webhooks/customers"
```

##### Webhook (GraphQL Admin)

```graphql
mutation {
  webhookSubscriptionCreate(
    topic: CUSTOMERS_PURCHASING_SUMMARY
    webhookSubscription: {
      callbackUrl: "https://your-app.com/webhooks/customers"
    }
  ) {
    webhookSubscription { id topic callbackUrl }
  }
}
```

##### Events

```toml
[events]
api_version = "unstable"

[[events.subscription]]
handle = "customer-purchasing-summary"

topic = "Customer"
actions = ["update"]
triggers = ["customer.amountSpent", "customer.numberOfOrders", "customer.lastOrder"]

uri = "https://your-app.com/events/customers"

query = """
  query customer_purchasing_summary_payload($customerId: ID!) {
    customer(id: $customerId) {
      id
      updatedAt
      numberOfOrders
      amountSpent {
        amount
        currencyCode
      }
      lastOrder {
        id
      }
    }
  }
"""
```

## {} Response

##### Classic webhook

```json
{
  "customerId": "gid://shopify/Customer/1",
  "numberOfOrders": 1,
  "amountSpent": {
    "amount": "100.00",
    "currencyCode": "USD"
  },
  "lastOrderId": "gid://shopify/Order/1",
  "occurredAt": "2005-05-05T06:00:00.000Z"
}
```

##### Events

```json
{
  "topic": "Customer",
  "action": "update",
  "handle": "customer-purchasing-summary",
  "data": {
    "customer": {
      "id": "gid://shopify/Customer/706405506930370084",
      "updatedAt": "2005-05-05T06:00:00.000Z",
      "numberOfOrders": "1",
      "amountSpent": {
        "amount": "100.00",
        "currencyCode": "USD"
      },
      "lastOrder": {
        "id": "gid://shopify/Order/1"
      }
    }
  },
  "fields_changed": [
    "customer[id: 'gid://shopify/Customer/706405506930370084'].amountSpent",
    "customer[id: 'gid://shopify/Customer/706405506930370084'].numberOfOrders"
  ],
  "query_variables": {
    "customerId": "gid://shopify/Customer/706405506930370084"
  }
}
```

## customers/delete

##### Webhook (TOML)

```toml
[webhooks]
api_version = "2026-04"

[[webhooks.subscriptions]]
topics = ["customers/delete"]
uri = "https://your-app.com/webhooks/customers"
```

##### Webhook (GraphQL Admin)

```graphql
mutation {
  webhookSubscriptionCreate(
    topic: CUSTOMERS_DELETE
    webhookSubscription: {
      callbackUrl: "https://your-app.com/webhooks/customers"
    }
  ) {
    webhookSubscription { id topic callbackUrl }
  }
}
```

##### Events

```toml
[events]
api_version = "unstable"

[[events.subscription]]
handle = "customer-delete"

topic = "Customer"
actions = ["delete"]

uri = "https://your-app.com/events/customers"
```

## {} Response

##### Classic webhook

```json
{
  "id": 706405506930370084,
  "tax_exemptions": [],
  "admin_graphql_api_id": "gid://shopify/Customer/706405506930370084"
}
```

##### Events

```json
{
  "topic": "Customer",
  "action": "delete",
  "handle": "customer-delete",
  "fields_changed": [],
  "query_variables": {
    "customerId": "gid://shopify/Customer/706405506930370084"
  }
}
```



### Product

The [`Product`](https://shopify.dev/docs/api/events/latest/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 REST 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_html` becomes `descriptionHtml`.
* `category` becomes `category { id name }`.
* `id` is a GID string instead of an integer. `admin_graphql_api_id` is the same value.
* `image` (featured image) becomes `featuredImage`.
* `image_id` on variants becomes `image { id url altText }`.
* `images` and `variants` are connections (`images.nodes`, `variants.nodes`) instead of flat arrays. Use `variants.nodes[*].id` in place of `variant_gids`.
* `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.
* `has_variants_that_requires_components`, `old_inventory_quantity` on variants, and `published_scope` have 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](https://shopify.dev/docs/apps/build/events/delivery-filtering).

##### `products/delete`

Maps directly to a `Product` delete subscription. No `triggers` or `query` are needed.

* `data.product` is `null`. The product is deleted before the query runs.
* `id` in the webhook payload is available as `query_variables.productId`.

## products/create

##### Webhook (TOML)

```toml
[webhooks]
api_version = "2026-04"

[[webhooks.subscriptions]]
topics = ["products/create"]
uri = "https://your-app.com/webhooks/products"
```

##### Webhook (GraphQL Admin)

```graphql
mutation {
  webhookSubscriptionCreate(
    topic: PRODUCTS_CREATE
    webhookSubscription: {
      callbackUrl: "https://your-app.com/webhooks/products"
    }
  ) {
    webhookSubscription { id topic callbackUrl }
  }
}
```

##### Events

```toml
[events]
api_version = "unstable"

[[events.subscription]]
handle = "product-create"

topic = "Product"
actions = ["create"]

uri = "https://your-app.com/events/products"

query = """
  query product_create_payload($productId: ID!) {
    product(id: $productId) {
      id
      title
      handle
      status
      vendor
      productType
      descriptionHtml
      createdAt
      updatedAt
      publishedAt
      templateSuffix
      tags
      options {
        id
        name
        position
        values
      }
      variants(first: 100) {
        nodes {
          id
          title
          price
          compareAtPrice
          sku
          barcode
          position
          createdAt
          updatedAt
          taxable
          inventoryPolicy
          inventoryQuantity
          image {
            id
            url
            altText
          }
          inventoryItem {
            id
          }
          selectedOptions {
            name
            value
          }
        }
      }
      images(first: 20) {
        nodes {
          id
          altText
          url
          width
          height
        }
      }
      featuredImage {
        id
        altText
        url
      }
      category {
        id
        name
      }
    }
  }
"""
```

## {} Response

##### Classic webhook

```json
{
  "admin_graphql_api_id": "gid://shopify/Product/788032119674292922",
  "body_html": "An example T-Shirt",
  "created_at": null,
  "handle": "example-t-shirt",
  "id": 788032119674292922,
  "product_type": "Shirts",
  "published_at": "2021-12-31T19:00:00-05:00",
  "template_suffix": null,
  "title": "Example T-Shirt",
  "updated_at": "2021-12-31T19:00:00-05:00",
  "vendor": "Acme",
  "status": "active",
  "published_scope": "web",
  "tags": "example, mens, t-shirt",
  "variants": [
    {
      "admin_graphql_api_id": "gid://shopify/ProductVariant/642667041472713922",
      "barcode": null,
      "compare_at_price": "24.99",
      "created_at": "2021-12-29T19:00:00-05:00",
      "id": 642667041472713922,
      "inventory_policy": "deny",
      "position": 1,
      "price": "19.99",
      "product_id": 788032119674292922,
      "sku": null,
      "taxable": true,
      "title": "Small",
      "updated_at": "2021-12-30T19:00:00-05:00",
      "option1": "Small",
      "option2": null,
      "option3": null,
      "image_id": null,
      "inventory_item_id": null,
      "inventory_quantity": 75,
      "old_inventory_quantity": 75
    },
    {
      "admin_graphql_api_id": "gid://shopify/ProductVariant/757650484644203962",
      "barcode": null,
      "compare_at_price": "24.99",
      "created_at": "2021-12-29T19:00:00-05:00",
      "id": 757650484644203962,
      "inventory_policy": "deny",
      "position": 2,
      "price": "19.99",
      "product_id": 788032119674292922,
      "sku": null,
      "taxable": true,
      "title": "Medium",
      "updated_at": "2021-12-31T19:00:00-05:00",
      "option1": "Medium",
      "option2": null,
      "option3": null,
      "image_id": null,
      "inventory_item_id": null,
      "inventory_quantity": 50,
      "old_inventory_quantity": 50
    }
  ],
  "options": [],
  "images": [],
  "image": null,
  "media": [],
  "variant_gids": [
    {
      "admin_graphql_api_id": "gid://shopify/ProductVariant/757650484644203962",
      "updated_at": "2022-01-01T00:00:00.000Z"
    },
    {
      "admin_graphql_api_id": "gid://shopify/ProductVariant/642667041472713922",
      "updated_at": "2021-12-31T00:00:00.000Z"
    }
  ],
  "has_variants_that_requires_components": false,
  "category": null
}
```

##### Events

```json
{
  "topic": "Product",
  "action": "create",
  "handle": "product-create",
  "data": {
    "product": {
      "id": "gid://shopify/Product/788032119674292922",
      "title": "Example T-Shirt",
      "handle": "example-t-shirt",
      "status": "ACTIVE",
      "vendor": "Acme",
      "productType": "Shirts",
      "descriptionHtml": "An example T-Shirt",
      "createdAt": null,
      "updatedAt": "2021-12-31T19:00:00-05:00",
      "publishedAt": "2021-12-31T19:00:00-05:00",
      "templateSuffix": null,
      "tags": ["example", "mens", "t-shirt"],
      "options": [],
      "variants": {
        "nodes": [
          {
            "id": "gid://shopify/ProductVariant/642667041472713922",
            "title": "Small",
            "price": "19.99",
            "compareAtPrice": "24.99",
            "sku": null,
            "barcode": null,
            "position": 1,
            "createdAt": "2021-12-29T19:00:00-05:00",
            "updatedAt": "2021-12-30T19:00:00-05:00",
            "taxable": true,
            "inventoryPolicy": "DENY",
            "inventoryQuantity": 75,
            "image": null,
            "inventoryItem": { "id": "gid://shopify/InventoryItem/642667041472713922" },
            "selectedOptions": [{ "name": "Size", "value": "Small" }]
          },
          {
            "id": "gid://shopify/ProductVariant/757650484644203962",
            "title": "Medium",
            "price": "19.99",
            "compareAtPrice": "24.99",
            "sku": null,
            "barcode": null,
            "position": 2,
            "createdAt": "2021-12-29T19:00:00-05:00",
            "updatedAt": "2021-12-31T19:00:00-05:00",
            "taxable": true,
            "inventoryPolicy": "DENY",
            "inventoryQuantity": 50,
            "image": null,
            "inventoryItem": { "id": "gid://shopify/InventoryItem/757650484644203962" },
            "selectedOptions": [{ "name": "Size", "value": "Medium" }]
          }
        ]
      },
      "images": { "nodes": [] },
      "featuredImage": null,
      "category": null
    }
  },
  "fields_changed": [
    "product[id: 'gid://shopify/Product/788032119674292922']"
  ],
  "query_variables": {
    "productId": "gid://shopify/Product/788032119674292922"
  }
}
```

## products/update

##### Webhook (TOML)

```toml
[webhooks]
api_version = "2026-04"

[[webhooks.subscriptions]]
topics = ["products/update"]
uri = "https://your-app.com/webhooks/products"
```

##### Webhook (GraphQL Admin)

```graphql
mutation {
  webhookSubscriptionCreate(
    topic: PRODUCTS_UPDATE
    webhookSubscription: {
      callbackUrl: "https://your-app.com/webhooks/products"
    }
  ) {
    webhookSubscription { id topic callbackUrl }
  }
}
```

##### Events

```toml
[events]
api_version = "unstable"

[[events.subscription]]
handle = "product-update"

topic = "Product"
actions = ["update"]

uri = "https://your-app.com/events/products"

query = """
  query product_update_payload($productId: ID!) {
    product(id: $productId) {
      id
      title
      handle
      status
      vendor
      productType
      descriptionHtml
      updatedAt
      publishedAt
      templateSuffix
      tags
      options {
        id
        name
        position
        values
      }
      variants(first: 100) {
        nodes {
          id
          title
          price
          compareAtPrice
          sku
          barcode
          position
          createdAt
          updatedAt
          taxable
          inventoryPolicy
          inventoryQuantity
          image {
            id
            url
            altText
          }
          inventoryItem {
            id
          }
          selectedOptions {
            name
            value
          }
        }
      }
      images(first: 20) {
        nodes {
          id
          altText
          url
          width
          height
        }
      }
      featuredImage {
        id
        altText
        url
      }
      category {
        id
        name
      }
    }
  }
"""
```

## {} Response

##### Classic webhook

```json
{
  "admin_graphql_api_id": "gid://shopify/Product/788032119674292922",
  "body_html": "An example T-Shirt",
  "created_at": null,
  "handle": "example-t-shirt",
  "id": 788032119674292922,
  "product_type": "Shirts",
  "published_at": "2021-12-31T19:00:00-05:00",
  "template_suffix": null,
  "title": "Example T-Shirt",
  "updated_at": "2021-12-31T19:00:00-05:00",
  "vendor": "Acme",
  "status": "active",
  "published_scope": "web",
  "tags": "example, mens, t-shirt",
  "variants": [
    {
      "admin_graphql_api_id": "gid://shopify/ProductVariant/642667041472713922",
      "barcode": null,
      "compare_at_price": "24.99",
      "created_at": "2021-12-29T19:00:00-05:00",
      "id": 642667041472713922,
      "inventory_policy": "deny",
      "position": 1,
      "price": "19.99",
      "product_id": 788032119674292922,
      "sku": null,
      "taxable": true,
      "title": "Small",
      "updated_at": "2021-12-30T19:00:00-05:00",
      "option1": "Small",
      "option2": null,
      "option3": null,
      "image_id": null,
      "inventory_item_id": null,
      "inventory_quantity": 75,
      "old_inventory_quantity": 75
    },
    {
      "admin_graphql_api_id": "gid://shopify/ProductVariant/757650484644203962",
      "barcode": null,
      "compare_at_price": "24.99",
      "created_at": "2021-12-29T19:00:00-05:00",
      "id": 757650484644203962,
      "inventory_policy": "deny",
      "position": 2,
      "price": "19.99",
      "product_id": 788032119674292922,
      "sku": null,
      "taxable": true,
      "title": "Medium",
      "updated_at": "2021-12-31T19:00:00-05:00",
      "option1": "Medium",
      "option2": null,
      "option3": null,
      "image_id": null,
      "inventory_item_id": null,
      "inventory_quantity": 50,
      "old_inventory_quantity": 50
    }
  ],
  "options": [],
  "images": [],
  "image": null,
  "media": [],
  "variant_gids": [
    {
      "admin_graphql_api_id": "gid://shopify/ProductVariant/757650484644203962",
      "updated_at": "2022-01-01T00:00:00.000Z"
    },
    {
      "admin_graphql_api_id": "gid://shopify/ProductVariant/642667041472713922",
      "updated_at": "2021-12-31T00:00:00.000Z"
    }
  ],
  "has_variants_that_requires_components": false,
  "category": null
}
```

##### Events

```json
{
  "topic": "Product",
  "action": "update",
  "handle": "product-update",
  "data": {
    "product": {
      "id": "gid://shopify/Product/788032119674292922",
      "title": "Example T-Shirt",
      "handle": "example-t-shirt",
      "status": "ACTIVE",
      "vendor": "Acme",
      "productType": "Shirts",
      "descriptionHtml": "An example T-Shirt",
      "updatedAt": "2021-12-31T19:00:00-05:00",
      "publishedAt": "2021-12-31T19:00:00-05:00",
      "templateSuffix": null,
      "tags": ["example", "mens", "t-shirt"],
      "options": [],
      "variants": {
        "nodes": [
          {
            "id": "gid://shopify/ProductVariant/642667041472713922",
            "title": "Small",
            "price": "19.99",
            "compareAtPrice": "24.99",
            "sku": null,
            "barcode": null,
            "position": 1,
            "createdAt": "2021-12-29T19:00:00-05:00",
            "updatedAt": "2021-12-30T19:00:00-05:00",
            "taxable": true,
            "inventoryPolicy": "DENY",
            "inventoryQuantity": 75,
            "image": null,
            "inventoryItem": { "id": "gid://shopify/InventoryItem/642667041472713922" },
            "selectedOptions": [{ "name": "Size", "value": "Small" }]
          },
          {
            "id": "gid://shopify/ProductVariant/757650484644203962",
            "title": "Medium",
            "price": "19.99",
            "compareAtPrice": "24.99",
            "sku": null,
            "barcode": null,
            "position": 2,
            "createdAt": "2021-12-29T19:00:00-05:00",
            "updatedAt": "2021-12-31T19:00:00-05:00",
            "taxable": true,
            "inventoryPolicy": "DENY",
            "inventoryQuantity": 50,
            "image": null,
            "inventoryItem": { "id": "gid://shopify/InventoryItem/757650484644203962" },
            "selectedOptions": [{ "name": "Size", "value": "Medium" }]
          }
        ]
      },
      "images": { "nodes": [] },
      "featuredImage": null,
      "category": null
    }
  },
  "fields_changed": [
    "product[id: 'gid://shopify/Product/788032119674292922'].status"
  ],
  "query_variables": {
    "productId": "gid://shopify/Product/788032119674292922"
  }
}
```

## products/delete

##### Webhook (TOML)

```toml
[webhooks]
api_version = "2026-04"

[[webhooks.subscriptions]]
topics = ["products/delete"]
uri = "https://your-app.com/webhooks/products"
```

##### Webhook (GraphQL Admin)

```graphql
mutation {
  webhookSubscriptionCreate(
    topic: PRODUCTS_DELETE
    webhookSubscription: {
      callbackUrl: "https://your-app.com/webhooks/products"
    }
  ) {
    webhookSubscription { id topic callbackUrl }
  }
}
```

##### Events

```toml
[events]
api_version = "unstable"

[[events.subscription]]
handle = "product-delete"

topic = "Product"
actions = ["delete"]

uri = "https://your-app.com/events/products"
```

## {} Response

##### Classic webhook

```json
{
  "id": 788032119674292922
}
```

##### Events

```json
{
  "topic": "Product",
  "action": "delete",
  "handle": "product-delete",
  "fields_changed": [],
  "query_variables": {
    "productId": "gid://shopify/Product/788032119674292922"
  }
}
```

***

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

## Mapping include\_fields to a query

##### Webhook (TOML)

```toml
[[webhooks.subscriptions]]
topics = ["products/update"]
include_fields = [
  "id",
  "status",
  "product_type",
  "variants.taxable",
  "variants.price",
  "variants.title",
  "updated_at"
]
```

##### Events

```toml
topic = "Product"
actions = ["update"]

query = """
  query product_update($productId: ID!) {
    product(id: $productId) {
      id
      status
      productType
      updatedAt
      variants(first: 20) {
        nodes {
          taxable
          price
          title
        }
      }
    }
  }
"""
```

***

## Step 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_type` becomes `product.productType`: camelCase, prefixed with the topic name.
* `status:active` becomes `product.status:'ACTIVE'`: enum values are uppercase in the GraphQL Admin API.
* `variants.price` becomes `product.variants.price`: camelCase, prefixed with the topic name.

## Mapping filter to query\_filter

##### Webhook filter

```toml
filter = "id:* AND status:active AND (product_type:Music OR product_type:Movies) AND variants.taxable:true AND variants.price:>=100 AND variants.title:'The Miseducation of'"
```

##### Events query\_filter

```toml
query_filter = "product.status:'ACTIVE' AND (product.productType:'Music' OR product.productType:'Movies') AND product.variants.taxable:true AND product.variants.price:>=100 AND product.variants.title:'The Miseducation of'"
```

***

## Step 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 `data` object keyed by the topic name (for example, `data.customer` or `data.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_changed` lists which fields triggered the delivery. Use it to avoid re-processing unchanged data.
* For `delete` subscriptions, `data` is absent. Use `query_variables` to identify what was deleted.

See the [Events reference](https://shopify.dev/docs/api/events) for the full payload structure and available fields.

***

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

***

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

***

## Next steps

* **Narrow delivery volume**: Add `triggers` to your `update` subscriptions so deliveries fire only when specific fields change. See [Delivery filtering](https://shopify.dev/docs/apps/build/events/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](https://shopify.dev/docs/apps/build/events/delivery-structure).
* **Consolidate subscriptions**: You can collapse multiple webhook topic strings for the same resource into one [Events subscription](https://shopify.dev/docs/apps/build/events/subscribe#example) with multiple `actions` and a single handler.
* [Events reference](https://shopify.dev/docs/api/events): Full topic and trigger path reference.

***
