The setup ? two product management systems running side by side

Army & Outdoors has 4 regional Shopify stores: NZ, AU, US, EU. The product catalogue isn't managed in one place ? it's split across two systems based on the type of product:

  • Cin7 Omni ? Shopify handles the regular catalogue. Products that have suppliers, get reordered, and live as ongoing SKUs. Cin7 is the master.
  • AAOsync (the system this case study is about) handles one-off products ? items the team finds, lists, sells out, and never restocks. These don't fit Cin7's model and the team didn't want them mixed in with their regular SKU pipeline.

Both systems coexist. Most products flow through Cin7. The one-offs flow through AAOsync.

The problem with one-offs before AAOsync

Without a system, listing a one-off product looked like this for the product manager:

  1. Create the product on the NZ Shopify store. Set title, description, images, variants, price (NZD), inventory, metafields.
  2. Create the same product on the AU store. Re-enter everything. Convert NZD to AUD by hand using whatever FX rate someone copied off Google.
  3. Repeat for US (USD).
  4. Repeat for EU (EUR).

4? the work for one product. Plus all the inevitable drift ? typos in one store but not the other, image versions out of sync, prices wrong because the FX rate was stale, metafield values missing on the AU store because someone forgot to copy them.

And then the worst problem: inventory. A one-off product has, by definition, limited stock. If 5 units exist and 3 sell on NZ, the AU/US/EU stores still show 5 available. Customers buy phantom inventory. Refunds, complaints, brand damage.

The director and my manager asked me to fix this. Build a system where the product manager creates a one-off once, and everything else is automatic.

The solution ? three webhook patterns

AAOsync is webhook-driven. Three distinct patterns, each handling a different sync direction.

Pattern 1 ? product/create on NZ master

  • The NZ store is the master for one-off products. Product manager creates the product there, with NZD pricing.
  • Shopify fires a products/create webhook to the AAOsync endpoint.
  • HMAC verified, HTTP 200 returned immediately, work queued (the standard webhook reliability pattern).
  • The processor reads the new product, fetches the live FX rates from the local FX table (refreshed daily), converts the NZD price into AUD, USD, EUR.
  • Creates the product on AU/US/EU stores via the Shopify GraphQL Admin API ? same title, description, images, variants, metafields, but with the converted price.

Pattern 2 ? product/update on NZ master

  • Anything the product manager changes on NZ ? title edit, description tweak, image swap, metafield update ? fires a products/update webhook.
  • The processor diffs the change and pushes it to AU/US/EU.
  • If the price changes, FX is reapplied on the way out ? the AU/US/EU stores get the new converted price, not the raw NZD value.
  • Metafields are handled carefully ? Shopify's metafield API has quirks around namespaces and types that took some patience to get right.

Pattern 3 ? inventory sync across all 4 stores

This was the hardest part of the build. Every store can sell. Every sale needs to decrement inventory on the other 3 stores in near real-time.

  • orders/create webhook on every store (not just NZ).
  • For each order line item that's an AAOsync-managed product, the processor calculates the quantity sold and pushes inventory adjustments to the other 3 stores.
  • The processor includes safeguards to avoid infinite loops ? if an inventory change comes from AAOsync itself, it doesn't trigger a re-sync.
  • Result: a product with 5 units in stock that sells 1 on AU drops to 4 on NZ, US, and EU within seconds.

The technical bits worth noting

  • NZ as the master for one-offs ? single source of truth for product data, but bi-directional for inventory. This split is deliberate. Product attributes need a master to avoid edit conflicts; inventory has to flow from wherever the sale happens.
  • FX rates fetched daily from a live API and cached in MySQL. If the API fails on a given day, the system falls back to yesterday's rates rather than breaking.
  • GraphQL bulk mutations ? when several products change in a short window (e.g., a metafield update applied to a category), the processor batches updates into single GraphQL calls. Cuts API call count by ~10x vs the equivalent REST approach.
  • Webhook reliability pattern applied throughout ? every incoming webhook is HMAC-verified, responded to with HTTP 200 immediately, then queued for processing. No retry storms, no missed events.
  • Idempotent processing ? if the queue processor crashes and restarts mid-batch, it picks up where it left off without duplicating updates.
  • Loop prevention ? when AAOsync writes to a store and that triggers another webhook, the processor recognises its own changes and doesn't re-process them. Without this safeguard, an inventory update on NZ ? AU could ping back NZ ? AU forever.

The result ? 3+ years, zero failures

  • Product creation went from 4? the work to 1?. The product manager creates the product on NZ, and the other 3 stores populate themselves.
  • Inventory stays accurate across all 4 stores in near real-time. No more phantom inventory, no more cross-region overselling.
  • FX-correct pricing on every store, every time. Daily-fresh exchange rates applied consistently.
  • 3+ years in production. The system has handled product launches, price updates, inventory changes, and store reconfigurations without a single critical failure.

Why this matters for your store

If you run a multi-region Shopify operation and your product creation, pricing, or inventory are still being touched by hand on each store ? you're carrying invisible cost. Some of it is direct (product manager time). Most of it is indirect (drift, errors, phantom inventory, customer refund handling).

The harder version of this problem is what AAO has: two product management systems running side by side, each handling a different category of products. That's a real-world setup most agencies haven't seen because they typically only deal with one channel at a time.

I built and maintain this for 4 regional stores in production. The same patterns apply to your stack. If multi-store sync is on your roadmap and the in-house solutions are creaking, this is what a production-grade build looks like.