Filtered products/update fails with multiple metafields

I’m running into a weird one with a filtered products/update webhook and would love a sanity check from the community.

What I’m trying to do
I want products/update to fire only when the product has a metafield custom.trigger = true. Simple enough. The catch: sometimes we save multiple metafields at once (e.g., custom.colors, custom.shape) together with custom.trigger.

What actually happens

  • If I set only custom.trigger = true and save → the webhook arrives as expected.

  • If I save custom.trigger = true and add/update other metafields in the same save (e.g., colors/shape) → no webhook is delivered.

  • If I remove the filter completely, I get products/update on any change (so the endpoint is fine).

Environment

  • Admin API: GraphQL 2025-01

  • Topic: PRODUCTS_UPDATE

  • Metafields are product-level (not variant)

  • Namespace: custom

  • Callback: Laravel endpoint that just logs and queues a job (working fine)

$variables = [
  "topic" => "PRODUCTS_UPDATE",
  "webhookSubscription" => [
    "callbackUrl" => "https://xxxxx.com/xxx/xxx/xxx/update",
    "format" => "JSON",
    "metafieldNamespaces" => ["custom"],
    "filter" => "metafields.namespace:custom AND metafields.key:trigger AND metafields.value:true"
  ]
];
public function handle(Request $request)
{
    $topic      = $request->header('X-Shopify-Topic');
    $shopDomain = $request->header('X-Shopify-Shop-Domain');
    $webhookId  = $request->header('X-Shopify-Webhook-Id');
    $payload    = $request->json()->all();

    Log::info('Shopify webhook received', compact('topic','shopDomain','webhookId') + ['payload'=>$payload]);

    // persist + queue job...
    return response('OK', 200);
}

What I already tried

  • Kept the filter in the “explicit” form above (namespace/key/value).

  • Tested with and without includeFields (and with/without metafields included there).

  • Double-checked that custom.trigger is product-level, not variant.

  • Read back the stored subscription with:

query {
  webhookSubscriptions(first: 50) {
    edges { node { id topic filter includeFields metafieldNamespaces } }
  }
}

Everything matches what I create.

Repro steps

  1. Create a product-level metafield custom.trigger (boolean).

  2. Save product with custom.trigger = trueproducts/update arrives :white_check_mark:

  3. Now, in the same save, set custom.trigger = true and add/update other product metafields like custom.colors or custom.shape → no webhook :cross_mark:

I’ve attached a screenshot in my admin to illustrate: when only trigger=true exists, the webhook fires; as soon as I also add Colors/Shape (while keeping trigger true), it stops firing.

Questions

  1. Is the metafield filter on products/update evaluated only against metafields that are present/changed in that specific event?

  2. If so, shouldn’t trigger still be present when I set it to true in the same save as other metafields?

  3. Do I need includeFields: ["metafields"] for metafield filtering to work reliably, or is that unrelated?

  4. Has anything changed recently in how metafield writes are aggregated into products/update vs. metafields/update?

  5. If my rule is “deliver whenever the product has trigger=true (regardless of what else changed)”, is the recommended approach to avoid the filter and just check trigger server-side after receiving an unfiltered products/update?

Happy to provide a webhook_id and timestamps if that helps. Thanks a ton for any pointers!

Hey @Josh_C

I was able to replicate this behavior. When a product has only custom.trigger = true, the webhook fires as expected. But as soon as I added custom.colors and custom.shape alongside it (keeping trigger true), the webhook stopped firing. Even updating just the product title afterward (without touching metafields) didn’t trigger the webhook, which answers your first question: the filter evaluates the product’s current metafield state in the webhook payload, not just what changed in that specific event.

I’m going to get clarity on this for you, but I believe the issue comes down to how the : operator works in webhook filters. According to the webhook filter docs, the : operator is an equality operator for filters, so when additional metafields are present beyond what the filter specifies, the equality check fails.

Regarding, includeFields: ["metafields"], this is required for metafield filtering to work (the debugging section notes that filters won’t be created without it).

Removing the filter and checking trigger server-side may be the most reliable approach.

Hey @Josh_C, After digging in to this, I can confirm the behavior you’re seeing is currently expected, but it’s definitely not ideal and we’re actively working to improve it.

For now, your best option for complex metafield scenarios is client-side filtering.

Thank you, yeah i figure out how to bypass this, in my case i’ve created a new metafield and at namespace i didnt user word “custom” i used something unique and now is working very well.

Thanks again for helping and explaining the webhooks filter.

1 Like