A developer seeks to prevent Shopify webhooks from triggering when product updates originate from their own API calls rather than from shop owners. Currently, API-initiated updates trigger product/update webhooks, causing duplicate processing on their server.
Confirmed Limitation:
Shopify does not natively support filtering webhooks by update source (API vs. UI)
No official plans mentioned to add this functionality
Proposed Workarounds Discussed:
Metafields approach (explored but deemed problematic):
Add custom “actor” metafield to track update source
Fails for updates because the field persists across subsequent user edits
Creates logical loops when trying to toggle between “API” and “UI” values
Redis cache solution (suggested workaround):
Store product IDs in cache before API updates
Webhook handler checks cache and skips processing if ID exists
Use short TTL (few seconds) to avoid blocking legitimate user updates
I have set up a webhook that will call my server anytime there is an update on a shop’s product.
It’s working nicely but I would like to know if it’s possible to make this webhook not call my server when the updates was not made by the shop’s owner directly but through shopify’s API.
The problem currently is that when I use shopify API to make an update to a product this triggers the product/update webhook which in turns call my server and duplicates the effect of such product update on my side.
Basically I’ve setup webhooks for when a user creates or updates a product.
This webhook calls my server and updates the state of my app which is
tracking those changes.
But sometimes my app is the one initiating those product creates & updates
through a call to the Shopify api. In these situations I don’t want the
shopify webhook to tell me again about those changes, because I’ve already
updated the state of my app accordingly. These webhook calls are
duplicating those changes.
My solution right now is to keep deleting & re-creating these shopify
webhooks when the product create & update are not done by a user.
But this is cumbersome, it generates traffic to your api for nothing and
it’s error prone..
I don’t think deleting & re-creating webhook is a good solution.
But sometimes my app is the one initiating those product creates & updates> through a call to the Shopify api>
In this case, you can add a custom field named “actor” with value “API”, so that it’s easy for you to filter if the product create/update events caused by yourself or by users from webhooks payload. Do nothing and response with status 200 when receiving an event from “actor”: “API” . How do you think?
Sounds like an interesting solution yes, I didn’t know you could arbitrarily create product fields like that, aren’t you supposed to respect the fields listed in the Product API payload when you create/update a product via the API ?
I’ve tried but yeah it seems Shopify doesn’t include this custom field inside the webhook call..
Here is the webhook call payload from the doc: https://shopify.dev/docs/api/admin-rest/2022-10/resources/webhook#event-topics
If you check the products/create topic there seems to be a restricted list of fields that can be attached to a product..
Ah yes so you need to register metafields etc, it’s quite a process !
This can do the trick for Creates, but this is not suitable for Updates because once I set “actor”: “API” for a product update made through the API, it will stay with this value forever even when more recent updates were made by the user through the UI..
Or that would mean any time the user makes an update to a Product he should also update the Actor metafield value from “API” to “UI” which is definitely a terrible UX.
update the Actor metafield value from “API” to “UI”
It would be updated by webhooks handler instead of end-user. It means, after you receive an event from webhooks with Actor: API, you have to do 2 things:
1st, do your business work
2nd, revert Actor empty or UI or whatever you want.
But the webhook can be triggered by both UI & API ! I need to know who has triggered that webhook in order to set in the metafields who has triggered that webhook in order to know who has triggered that webhook. It’s an infinite loop ://
I appreciate the time you take to look for a solution here though ! Thanks for this man ! I’m just frustrated by this missing feature in the Webhook api :))
I think you did not really want to try to solve your issue, you’re finding an easy way to do it. There is no infinite loop, let’s have a look as the sample code below:
if (actor === 'API') {
// handle business logic here
// update product actor = 'UI' -> it would call the webhook one more time and return -> there is no thing happens after that -> no infinity loop
} else {
return; // do nothing
}
Hey !
Let me recap this so you can see why this is not solving the isssue:
if (actor == 'API') {
// No business logic here, I just want to discard the call because the update came from my service through the API
// update product actor = 'UI' -> This will trigger the Shopify webhook again which will call this same handler and go to the "else" part now that actor != 'API'.
} else {
// If I reach this code that means the shopify webhook has been triggered by the user through the UI (because actor != 'API')
// In that case I need to process this update and send the data to my other service. But I really only want to do this if the user has done the update. If it's not the user that means my service was the originator of the update and has already processed it on its end. We don't to do this process twice.
// But because just above we update the metafields to actor == 'UI', we trigger again the update webhook which will call again this handler and this time will actually reach that code here even if it's not the result of a UI update.. So my service will get called here for an update it doesn't want to process.
}
if (actor == 'API') {
// No business logic here, I just want to discard the call because the update came from my service through the API
} else {
// do business logic here
// update product actor = 'API' -> This will trigger the Shopify webhook again which will call this same handler and go to the "if" part then do nothing.
I thought about this but this doesn’t work because actor never gets set to ‘UI’ anymore with this logic. So updates triggered by UI will never reach the “else” part.
Hence the infinite loop I was talking about.
And all this just because there is no way to differentiate the origin of a webhook trigger, it’s really frustrating..