「届いてから判断する」から「欲しい変更を宣言する」へ。Shopify Next Gen Eventsで変わるWebhook設計
こんにちは。FlagshipのバックエンドデベロッパーのKeitaです。
Shopifyアプリを開発していると、Webhookはさまざまなインテグレーションの入口になります。商品が更新されたら外部の商品管理システムへ同期する。注文が入ったら基幹システムへ送る。在庫が変わったら別のサービスへ通知する。Shopify上で起きた変化を外部システムへつなぐために、Webhookは長くその中心にありました。
一方、実際にインテグレーションを作っていると、アプリが本当に知りたいのは「商品が更新された」という大きなイベントではなく、「Variantの価格が変わった」「特定のMetafieldの値が変わった」「このフィールドが追加・更新・削除された」という、もう一段具体的な「変化」であることがあります。
これまではWebhookを受け取ったあと、アプリ側で「今回の更新は自分たちに関係があるのか?」を判断したり、処理に必要なデータを追加でAdmin APIから取得したりする実装も少なくありませんでした。
2026年10月1日、Shopifyの Next Gen Events がAPI version 2026-10 でGA(Generally Available)になりました。GA時点で18 topicsに対応し、ShopifyはEventsをclassic webhooksの後継となる宣言的な仕組みとして位置づけています。
Eventsのポイントは、単に新しいWebhook payloadが登場したことではありません。「更新が起きたら受け取り、あとから必要か判断する」から、「どんな変更を、どんなデータと一緒に受け取りたいかを、あらかじめ宣言する」へ。 インテグレーションの入口そのものを変えられる仕組みです。
この記事でわかること
- Next Gen Eventsとclassic webhooksは何が違うのか
-
triggers、query、query_filterはそれぞれ何をするのか - Variantの価格変更では、WebhookとEventsのpayloadがどう違うのか
- 特定のMetafield変更だけを購読すると何が変わるのか
- Eventsでできること、できないこと
- 既存Webhookからどこから移行すると効果が見えやすいのか
1. Webhookでは「何が変わったか」が分かりにくい
例えば、Shopifyの商品価格を外部の商品管理システムへ同期するアプリを考えてみます。アプリが本当に興味を持っているのは、Product Variantの価格変更だけです。
classic webhookであれば、products/update を購読する構成が考えられます。しかし、Productには価格以外にもさまざまな変更が起こります。タイトルが変わる。説明文が変わる。タグが変わる。Metafieldが変わる。Variantの価格が変わる。アプリが知りたいのは価格変更だけでも、入口は同じ products/update です。
Webhookにもfilterはある
ここは少し注意が必要です。現在のclassic webhooksには、filter と include_fields があります。例えば、次のようなSubscriptionを設定できます。
[webhooks]
api_version = "2026-10"
[[webhooks.subscriptions]]
topics = ["products/update"]
uri = "/webhooks/products-update"
include_fields = [
"id",
"status",
"variants.id",
"variants.price",
"updated_at"
]
filter = "status:active AND variants.price:>=100"
include_fields はpayloadに含めるフィールドを絞る機能です。
filter はイベント発生後、配送時点のpayloadの現在値に対して条件を評価し、条件を満たさないWebhookを配送しないための機能です。
ただし、「価格100以上のVariantを持つProductが更新された」ことと、「今回、Variantの価格そのものが変更された」ことは同じではありません。Shopifyのドキュメントでも、variants.price をfilter条件に使った場合、条件を満たすVariantを持つProductであれば、タイトル変更のような価格とは無関係な更新でもWebhookが配送されると説明されています。
つまり、classic webhookの filter が見ているのは、
今回どのフィールドが変わったか
ではなく、
イベント発生後のpayloadが、いま条件を満たしているか
です。
従来のインテグレーションを単純化すると、次のようになりがちでした。
products/update を受信
↓
今回、自分たちが気にする
変更だった?
↓
必要なら追加データを取得
↓
外部システムへ同期
Eventsが変えるのは、この入口です。
2. Eventsでは「欲しい変化」を先に宣言する
Eventsでは、Updateに対してフィールド単位のTriggerを指定できます。Product Variantの price が変わったときだけEventを受け取りたいなら、次のように書けます。
triggers = ["product.variants.price"]
product.variants.price は、Product Variantの price フィールドが変更されたときに発火するTriggerです。ShopifyはEventsを、field-level trigger、conditional filter、custom GraphQL payloadを使って、必要な変更と必要なデータだけを受け取れる仕組みとして説明しています。
Trigger / Query / Query Filter
Eventsを理解するうえで中心になるのが、次の3つです。
Trigger
何が変わったらEventの対象にするか。
triggers = ["product.variants.price"]
なら、
Variantのpriceが変わったら
という条件です。
Query
その変更が起きたとき、どのデータをpayloadに含めるか。GraphQL Admin APIのqueryをSubscriptionに定義でき、Shopifyがqueryを実行した結果をEvent payloadの data に含めます。
Query Filter
Queryで取得した現在値を使って、最終的に配送するかどうかを判断します。例えば、
query_filter = "product.status:'ACTIVE'"
なら、
Productが現在ACTIVEの場合だけ配送する
という条件です。query_filter を使う場合は query も必要で、Shopifyはまずqueryを実行して data を作り、その結果に対して query_filter を評価します。
まとめると、次のとおりです。
何が変わった?
↓
Trigger
何のデータが必要?
↓
Query
どんな場合だけ受け取りたい?
↓
Query Filter
これまでWebhook handlerの中に書いていた判断やデータ取得の一部を、Subscriptionそのものへ移せるようになります。
3. 価格変更で見る、WebhookとEventsの違い
ここまでの違いを、「ACTIVEな商品のVariant価格が変更されたときだけ、外部の価格管理システムへ同期したい」というケースで見てみます。
classic webhookでは「更新後の商品データ」が届く
例えば次のようなWebhookを設定します。
[webhooks]
api_version = "2026-10"
[[webhooks.subscriptions]]
topics = ["products/update"]
uri = "/webhooks/products-update"
include_fields = [
"id",
"status",
"variants.id",
"variants.price",
"updated_at"
]
filter = "status:active"
この場合、payloadは例えば次のようになります。
{
"id": 9554194432293,
"status": "active",
"variants": [
{
"id": 123456789,
"price": "5500.00"
}
],
"updated_at": "2026-10-02T12:00:00Z"
}
classic webhookのpayloadは固定されたREST形式で、include_fields を指定しなければresourceのpayload全体、指定すれば選択したフィールドが送られます。
このpayloadからは、
Productは現在ACTIVE
Variant #123456789 の現在価格は5,500円
ということは分かります。しかし、このpayloadだけを見ても、
今回の更新でpriceが変わったのか?
は直接分かりません。例えば、
price
5,000円 → 5,500円
という更新だった場合も、
title
"T-Shirt" → "New T-Shirt"
price
5,500円のまま
という更新だった場合も、どちらも products/update の対象になり得ます。後者でもpayloadには現在値として、
{
"price": "5500.00"
}
が入ります。
つまりclassic webhookのpayloadが主に伝えてくれるのは、「更新後、そのresourceがどうなっているか」です。include_fields でpayloadを小さくすることはできますが、それは「送るフィールド」を絞る仕組みであって、「今回変更されたフィールド」を示す仕組みではありません。
Eventsでは「priceが変わった」こと自体を購読する
同じ処理をEventsで考えてみます。
[events]
api_version = "2026-10"
[[events.subscription]]
handle = "product-price-change"
topic = "Product"
actions = ["update"]
triggers = ["product.variants.price"]
uri = "/events/products"
query = """
query price_change(
$productId: ID!,
$variantsId: ID!
) {
product(id: $productId) {
id
status
}
productVariant(id: $variantsId) {
id
price
}
}
"""
query_filter = "product.status:'ACTIVE'"
Shopifyの公式ドキュメントでも、product.variants.price をTriggerにしてProductとProductVariantをqueryし、ProductがACTIVEの場合だけ配送する同様の例が紹介されています。
このSubscriptionでは、
Variantのpriceが変わった?
↓
YES
↓
Product / Variantの
必要なデータをquery
↓
ProductはACTIVE?
↓
YES
↓
Eventを配送
という処理をShopify側に宣言しています。Event payloadは、例えば次のようになります。
{
"topic": "Product",
"action": "update",
"handle": "product-price-change",
"data": {
"product": {
"id": "gid://shopify/Product/123",
"status": "ACTIVE"
},
"productVariant": {
"id": "gid://shopify/ProductVariant/456",
"price": "5500.00"
}
},
"fields_changed": {
"added": [],
"updated": [
"product[id: 'gid://shopify/Product/123'].variants[id: 'gid://shopify/ProductVariant/456'].price"
],
"removed": []
},
"query_variables": {
"productId": "gid://shopify/Product/123",
"variantsId": "gid://shopify/ProductVariant/456"
}
}
Eventsのdeliveryには、topic、action、handle に加え、変更内容を示す fields_changed と、変更に関係したentity IDを示す query_variables が含まれます。query を設定した場合は、その結果も data に含まれます。
このpayloadからは、次の3種類の情報を得られます。
-
fields_changed: Product #123 のVariant #456でpriceが更新された -
query_variables: 変更に関係したProductとVariantのID -
data: Productは現在ACTIVEで、Variantの現在価格は5,500円
ここがclassic webhookとの大きな違いです。
| classic webhook | Events | |
|---|---|---|
| 購読 | products/update |
Product + product.variants.price
|
| 何を条件にするか | resourceの更新 | 特定フィールドの変更 |
| payload | 固定されたREST形式 | Event metadata + custom GraphQL data |
| 現在のprice | resource payloadに含められる |
query の data に含められる |
| 今回priceが変わったか | payloadだけでは直接分からない |
fields_changed で分かる |
| 変更されたVariant | payloadから判断 |
fields_changed / query_variables で特定 |
| 必要な関連データ | 必要なら追加API call |
query でEventに含められる |
Shopify自身も、Eventsの特徴を「fewer deliveries, richer payloads, no follow-up queries」と表現しています。必要な変更を絞り、処理に必要なデータを一緒に受け取れるためです。
fields_changed はbefore / afterではない
ここで、fields_changed についてもう少し見ておきます。fields_changed には、
{
"added": [],
"updated": [],
"removed": []
}
の3つの配列があり、それぞれに変更されたフィールドのpathが入ります。pathにはGIDも埋め込まれるため、「どのProductの、どのVariantの、どのフィールドが変わったのか」まで追跡できます。
ただし、
price
5,000円 → 5,500円
という変更があったとしても、Eventに自動的に、
before: 5,000円
after: 5,500円
が入るわけではありません。分かるのは、
fields_changed
→ priceがupdatedされた
data
→ queryで取得したpriceは5,500円
という情報です。
さらに、query は条件となる変更が起きたあとに実行されます。そのため、data は変更が発生した瞬間の固定snapshotではなく、queryを実行した時点で取得した現在の状態です。Shopify自身も、変更を理解するには fields_changed、現在のcontextには data を使うという形で説明しています。
つまり、fields_changed = 何が変わったか、data = query実行時点でどうなっているかです。
もし「5,000円 → 5,500円」という厳密なbefore / afterを履歴として残したければ、外部DBに以前の値を保持して比較するなど、別の仕組みが必要になります。Eventsは「何が変わったか」を扱いやすくしてくれますが、それだけで完全な変更履歴システムになるわけではありません。
4. MetafieldではEventsのメリットがさらに分かりやすい
Eventsが特に便利そうなのが、Metafieldを多く利用しているインテグレーションです。例えばPIMやERPなどからProductへ、
custom.material
custom.country_of_origin
custom.brand_code
custom.external_category
integration.erp_id
integration.sync_status
...
といった多数のMetafieldを同期しているとします。しかし、あるアプリが気にしているのは、
integration.sync_status
だけだとします。Eventsでは、namespaceとkeyを指定して、
triggers = [
"product.metafield(namespace: 'integration', key: 'sync_status').value"
]
と購読できます。これにより、integration.sync_status の value が変更された場合だけをEventの対象にできます。Product Events APIでは、このTriggerに対して metafieldKey、metafieldNamespace、productId がquery variablesとして提供されます。
さらに、keyを省略して、
triggers = [
"product.metafield(namespace: 'integration').value"
]
のようにnamespace単位で購読することもできます。この場合も、実際に変更されたMetafieldのnamespaceとkeyが query_variables に入るため、それをGraphQL queryの変数として使えます。例えば、
query = """
query product_metafield_change(
$productId: ID!,
$metafieldNamespace: String!,
$metafieldKey: String!
) {
product(id: $productId) {
id
metafield(
namespace: $metafieldNamespace,
key: $metafieldKey
) {
namespace
key
value
}
}
}
"""
とすれば、「変更されたMetafieldそのもの」を data に含めて配送できます。
従来の設計を単純化すると、
大量の商品・Metafield更新
↓
products/update
↓
アプリが受信
↓
今回の変更は
自分に関係ある?
↓
処理する / 捨てる
となっていたものを、
integration.sync_status
のvalueが変わった?
↓
YES
↓
変更されたMetafieldをquery
↓
Eventを配送
↓
アプリで処理
という方向へ変えられます。
受け取ってから不要な更新を捨てるのではなく、必要な変化を先に宣言する。 Metafieldは、そのメリットが特に分かりやすいケースだと思います。
5. Eventsになっても、アプリ側のロジックがなくなるわけではない
ここまで見ると、Webhook handlerに書いていた処理をかなりEvents側へ移せそうに見えます。実際、次のような処理は、Trigger / Query / Query Filterへ移せます。
- 特定フィールドの変更だけを受け取る
- 関連するデータを一緒に取得する
- 現在値によって配送を止める
ただし、Eventsですべてのビジネスロジックを表現できるわけではありません。例えば query_filter では、変更前と変更後の値を比較するといった条件は表現できません。Shopifyも、query_filter はprevious-valueとnew-valueの比較や、複数ステップのビジネスロジックを置き換えるものではないと説明しています。
つまり、「価格が変わった」はTriggerで表現できます。「現在価格が100以上」はQuery Filterで表現できます。しかし、「価格が20%以上上昇した」のようにbefore / afterの比較が必要なロジックは、引き続きアプリ側で処理する必要があります。
Eventsの目的は、handlerをなくすことではありません。handlerへ届く前に判断できることを、Subscription側へ移すことと考える方がよさそうです。
6. Eventsへの移行は、どこから始めるべきか
EventsがGAしたからといって、既存のWebhookを一気に置き換える必要はありません。Eventsとclassic webhooksは、同じ shopify.app.toml の中で共存できます。Shopifyも、既存のclassic webhookインテグレーションはそのまま動かしながら、Eventsが対応しているworkflowをひとつずつ移行する方法を案内しています。
また、現時点では両者の得意分野も異なります。Eventsは、field-level trigger、custom GraphQL query、query_filter が使える一方、対応しているのはShopify resourceの一部です。classic webhooksはより広いtopicをカバーし、shopify.app.toml だけでなくGraphQL Admin APIからShop単位でSubscriptionを管理することもできます。
したがって、「Eventsは新しいから使う、Webhookは古いからやめる」ではなく、インテグレーションごとの要件で使い分ける必要があります。
Handlerを逆から読んでみる
Eventsへの移行候補を探すなら、まず既存のWebhook handlerを見るのが分かりやすいと思います。例えば、
products/update
↓
ACTIVE商品?
↓
YES
↓
価格が変わった?
↓
YES
↓
GraphQLでVariant情報を取得
↓
外部システムへ同期
という処理があるとします。これを逆から考えます。
-
何が変わったときだけ処理したい? →
product.variants.price - 処理には何のデータが必要? → Product status、Variant ID、price
- どんな状態の場合だけ処理したい? → ProductがACTIVE
これをそのまま、
Trigger
↓
Query
↓
Query Filter
へ移せないか考えます。Shopifyも、Eventsへの移行候補として次のworkflowから始めることを勧めています。
- 多くの更新を受け取っているが、大半を捨てているworkflow
- deliveryのたびに追加API callをしているworkflow
つまりEventsへの移行は、「Webhookを新しいAPIに書き換える作業」というより、「いまhandlerでやっている判断とデータ取得を、どこまでSubscription側へ移せるか考える作業」と捉えると分かりやすいと思います。
7. 「届いてから判断する」から「欲しい変更を宣言する」へ
Next Gen Eventsを一言で表すなら、「届いてから判断する」から「欲しい変更を宣言する」へという変化だと思います。classic webhookでは、
Shopifyでresourceが更新された
↓
Webhookが届く
↓
自分たちに必要な変更だった?
↓
必要なデータは揃っている?
↓
必要なら追加API call
↓
処理する
となっていたインテグレーションを、Eventsでは、
自分たちが必要な
フィールドが変わった?
↓
必要なデータをquery
↓
現在の状態は条件を満たす?
↓
Eventを届ける
↓
処理する
という方向へ設計できます。
ここで重要なのは、単にhandlerのコードが少し短くなることではありません。classic webhookでは、基本的に「更新されたresourceを受け取る」ところから処理が始まります。Eventsでは、「自分たちのシステムにとって意味のある変更と、その処理に必要なデータを受け取る」ところまで、Subscriptionとして宣言できます。
大量の商品更新が発生するEC。多くのMetafieldをPIMやERPと同期しているEC。複数システム間でProductやInventoryを連携しているEC。そうしたインテグレーションほど、「受け取ったあとに判断する」「必要なデータを取りに行く」「結局使わず捨てる」という処理が積み重なります。
Eventsは、その判断とデータ取得の一部をShopify側へ移せる仕組みです。だからこそ、まず既存のWebhook handlerをひとつ開いて、「この処理、本当にこのtopicのすべての更新を受け取る必要があるだろうか?」と考えてみる。もし答えがNoなら、そのhandlerはNext Gen Eventsへの移行を検討する最初の候補かもしれません。
ECの規模が大きくなるほど、Shopifyと外部システムの間を流れる更新も増えていきます。必要な変更だけを受け取り、必要なデータをそのまま処理につなげられるようになれば、システム連携をよりシンプルに保ちやすくなります。
Next Gen Eventsは、Shopifyを中心とした連携を、より効率よく設計するための選択肢になりそうです。
参考資料
-
Shopify Developer Changelog「More control over commerce updates with Next Gen Events」 — 2026年10月1日のGA発表。API version
2026-10、18 topics、Eventsの位置づけなど - Shopify Developers「About Events and webhooks」 — Eventsとclassic webhooksの機能差、共存、Subscription管理方法
-
Shopify Developers「Events」 —
fields_changed、query_variables、data、custom query、query_filterの仕様 - Shopify Developers「Product — Events API」 — Product / Variant / Metafieldで利用できるTriggerとquery variables
-
Shopify Developers「Filter webhook deliveries」 — classic webhookの
filterの評価方法と、Variant fieldを条件にした場合の挙動 -
Shopify Developers「Webhooks delivery structure」 — classic webhookのpayloadと
include_fieldsの仕様