Topics covering webhook creation & management, event handling, Pub/Sub, and Eventbridge, in Shopify apps.
Hello,
I am developing a multi-user/store application that registers a number of webhooks when each new user that is onboarded. In order to identify the store that is firing off the hook, it looks like my only option is to consider the "X-Shopify-Shop-Domain" request header, hoping that it will match what I know as a possible store name internally within my app. While I can verify the authenticity of the shopify webhook payload via hmac, it doesn't contain any way to authenticate data outside of the payload, such as the request headers. Thus, depending solely on the shop-domain header it is possible for a replay style attack: the payload portion to replayed on a different user-store account, simply by copying the hmac value and payload data, while changing the shop-domain header. While the risk of this could be mitigated by checking to see if this particular hmac has already been processed by my application, this solution doesn't scale well with tens of millions of values that must be stored. Further, other operational issues persist -- what happens if a store changes their domain?
To work around this issue, I would like to include a custom metafield in ALL of the payloads recevied via webhooks for my app. If I can include a custom "clientstoreid" identifier (generated exclusively by my app) within the webhook payload data, I can easily file and filter the data based on this value. While it's trivial to include this custom "clientstoreid" as part of the webhook address, it's crucial that this data is included as part of the webhook payload for HMAC signature verification.
I've tried registering a storewide metafield, but this is not included in any of the webhook payloads that I have tried (mainly of the 'orders' topic range). I would like to avoid making additional API calls to validate the shop-domain or my custom "clientstoreid" metafield, if possible.
Is there a way to accomplish what I've described above? I'm new to the shopify API, so if I've missed something or if this is simply a case of overthinking the problem, please do let me know.
Thanks
You have a timestamp in the HMAC so you can narrow the window that it is valid down to a couple of seconds if you want.
I have seen other webhook services use a nonce header to mitigate replay attacks - not sure if shopify do this.
With regard to the domain changing - I assume they always use the myshopify_domain parameter of the Shop resource and not the domain value in the x-header (not sure though).
Thanks for the reply - I'm specifically refering to webhooks in this case, which do not include hmac timestamps, according to the documentation and what I can see being piped through ngrok. Further, there's no shop resource included the payload for the hooks I have tested (of the 'orders' topic variety). The only shop identifier I can find is through the x-header.
The example data payload on the webhook api help pretty much represents exactly what I'm seeing on my side.
Yes I get what you mean now. I just looked at it on an order create webhook request and there is no other store identifier other than the header as you say.
I didn't check that that timestamp was not included in the webhook HMAC - you are right, you could always throttle webhook requests app side after the first one, but hardly a solution.
Does transport over HTTPS provide replay attack protection?
Yeah, a nonce could work as well to solve the replay attack issue, but I still need something that helps indentify the shop-domain thats actually calling the webhook. Crazy that this data isn't included as part of the webhook payload already, considering all the other reference data that it does include.
The only tactical solution I can come up with at the moment is to store the hmac sigs received thus far and query using a bloom filter. As long as the hmac is verified and not previously used, I suppose I can take the risk of trusting x-shopify-shop-domain. Definitely not ideal, and still problematic if the domain changes.
Hopefully someone from the Shopify API team can contribute to this discussion and point us to the proper solution, or confirm that this is actually an issue with the current version of their webhook api.
It's a bit dirty but when you create an order could you include your unique nonce as an order note - that way you will get it in the webhook request and you can discard any other duplicate requests as bogus - then delete the nonce note from the order.
*sorry thats dumb - it would only work from orders created by your app 🙂 *
Just a final note as you mentioned the domain changing.
x-shopify-shop-domain will be the myshopify_domain parameter that is fixed from sign-up and won't ever change for a store.
I think you summed it up with "As long as the hmac is verified and not previously used, I suppose I can take the risk of trusting x-shopify-shop-domain"
All the best
Hey Bzzz,
As heyhost has stated, the X-Shopify-Shop-Domain header will always refer to the myshopify domain and is immutable.
With regards to your other concerns, thanks for raising these. We'll explore the possiblility of adding the shop domain name into the signed fields, however this would change the way that webhooks are verified for all API clients and won't be an easy switch.
To learn more visit the Shopify Help Center or the Community Blog.
Thanks for the reply. Why would adding additional fields in the webhook payload break compatibility for signature purposes? The entire payload would be signed, as it is now - the only difference is the number/type of additional fields included.
I can understand how adding these fields would break compatibility for fixed schema relations, but that's a development decision that should have been planned for by the API consumer.
With regards to the replay attack issue, does shopify support basic authentication in the webhook callback address? See this older discussion regarding a similar issue with the stripe webhook: https://groups.google.com/a/lists.stripe.com/forum/#!topic/api-discuss/-zqruS0lTig