HomeShopify & E-commerceHow to Sync Inventory Between Two…

How to Sync Inventory Between Two Shopify Stores With n8n

How to Sync Inventory Between Two Shopify Stores With n8n









If you run two Shopify stores that sell the same products, a wholesale storefront and a retail one, or a US and an EU store, you already know the pain: a unit sells on one store and the other keeps advertising stock it no longer has. This guide shows you how to sync inventory between two Shopify stores with n8n, so every sale or manual stock edit on one store updates the matching product on the other in seconds. You will build a free, real-time workflow that catches each inventory change, matches products by SKU, and writes the new quantity to your second store. No paid multi-store app, no nightly spreadsheet reconciliation.

Prefer to skip the setup? Have us build and install it for you → and be live in a day.

What it does

The workflow listens for stock changes on your primary store and mirrors them onto your second store. When a variant’s quantity changes, Shopify sends an inventory_levels/update webhook to n8n. The workflow reads which item changed, looks up that item’s SKU, then searches the second store for the variant carrying the same SKU and sets its available quantity to match.

Here is the full path a single change travels:

┌──────────────────────────────────────────────────────────────────┐
│  SYNC INVENTORY BETWEEN TWO SHOPIFY STORES                         │
│                                                                    │
│  Store A sells 1 unit                                              │
│        │                                                           │
│        ▼                                                           │
│  [Webhook: inventory_levels/update]                               │
│        │  { inventory_item_id, available }                        │
│        ▼                                                           │
│  [Get SKU from Store A]  ──►  sku: "TSHIRT-BLK-M"                  │
│        │                                                           │
│        ▼                                                           │
│  [Has SKU?] ──► yes                                               │
│        │                                                           │
│        ▼                                                           │
│  [Find variant in Store B by SKU]  ──►  inventoryItem id          │
│        │                                                           │
│        ▼                                                           │
│  [Variant exists?] ──► yes                                        │
│        │                                                           │
│        ▼                                                           │
│  [Set quantity in Store B]  ──►  available = new stock            │
│        │                                                           │
│        ▼                                                           │
│  [Log to Slack]                                                   │
└──────────────────────────────────────────────────────────────────┘
  

Why it beats the default

The two usual answers to multi-store stock are a paid sync app or a manual export. Dedicated apps such as the popular inventory-sync tools charge a monthly fee that climbs with your product count and often bill per synced SKU. A CSV export and re-import is free but slow, error-prone, and always out of date the moment a customer checks out.

This n8n build gives you a middle path that most merchants never realize is available:

  • Real time. Stock moves within seconds of the sale, not on a nightly batch.
  • Flat cost. You pay for one n8n instance regardless of whether you sync 50 SKUs or 5,000.
  • SKU based, so it works even when the two stores have different product IDs, handles, and themes.
  • Fully yours. The logic sits in your own workflow, so you can add rules, buffers, or a Slack log without waiting on an app’s feature roadmap.

It slots neatly into a wider stack too. If you already run any of our other n8n Shopify automations, this uses the same connection and the same Admin API you have already set up.

What you need

  • Two Shopify stores where you are the admin on both.
  • An n8n instance, either n8n Cloud or a self-hosted install.
  • An Admin API access token on each store, created with the 2026 Shopify Dev Dashboard method. If you have not connected Shopify to n8n yet, follow how to connect Shopify to n8n in 2026 first, once per store.
  • The read_inventory and write_inventory access scopes on both tokens, plus read_products.
  • Matching SKUs. Every variant you want to keep in sync must carry the identical SKU string on both stores. This is the join key, so it has to be exact.
  • The location ID of the destination store, which you will drop into a config node once.

All API calls in this guide use Admin API version 2026-04. Build time is around 40 minutes from scratch, or a few minutes if you import the ready-made template.

Node-by-node list

Eight nodes carry a change from the first store to the second. Here is the whole map before we build it.

# Node Type Job
1 Store A inventory webhook Webhook Receives inventory_levels/update from the source store
2 Config Set / Edit Fields Holds the destination store domain and location ID
3 Get SKU from Store A HTTP Request Looks up the SKU for the changed inventory item
4 Has SKU? Filter Skips items with no SKU set
5 Find variant in Store B HTTP Request GraphQL search for the matching SKU in the destination store
6 Variant exists? Filter Continues only if the destination store has that SKU
7 Set quantity in Store B HTTP Request GraphQL mutation that writes the new available quantity
8 Log to Slack Slack Posts a one-line record of each sync

Step-by-step build

1 Store A inventory webhook (Webhook)

This node is the entry point. Add a Webhook node, set the method to POST, and give it a path like store-a-inventory. Copy the production URL n8n shows you. Then register it on your source store by creating a webhook subscription for the inventory_levels/update topic pointing at that URL. When a variant’s stock changes, Shopify posts a small payload here.

{
  "inventory_item_id": 44827362891,
  "location_id": 71034829,
  "available": 7,
  "updated_at": "2026-07-06T14:32:08-05:00"
}
💡

Register the webhook once with a single Admin API call, or use the webhook screen in your store admin. Point it only at the store you treat as the source of truth for that change.

2 Config (Set / Edit Fields)

Add a Set node right after the webhook so the store-specific values live in one place. In an Edit Fields node, add three string assignments so future edits never touch the request nodes.

storeADomain     = "acme-wholesale.myshopify.com"
storeBDomain     = "acme-retail.myshopify.com"
storeBLocationId = "gid://shopify/Location/62918342"
💡

The destination location ID is a GraphQL global ID, not the plain number. You can read it once by running a locations query against Store B, then paste it here.

3 Get SKU from Store A (HTTP Request)

The webhook tells you which inventory item changed but not its SKU, so fetch it. Use an HTTP Request node with a GET to the source store’s inventory item endpoint.

GET https://{{ $('Config').item.json.storeADomain }}/admin/api/2026-04/inventory_items/{{ $('Store A inventory webhook').item.json.body.inventory_item_id }}.json

Authenticate with a generic header credential that sends X-Shopify-Access-Token set to Store A’s token. The response carries the SKU you will match on.

{
  "inventory_item": {
    "id": 44827362891,
    "sku": "TSHIRT-BLK-M",
    "tracked": true
  }
}

4 Has SKU? (Filter)

Some items have no SKU, and those cannot be matched. Add a Filter node with one condition: {{ $json.inventory_item.sku }} is not empty. Only rows that pass move on, which keeps blank-SKU noise out of the rest of the flow.

5 Find variant in Store B (HTTP Request)

Now search the destination store for the variant that shares this SKU. Use an HTTP Request node with a POST to Store B’s GraphQL endpoint, https://{{ $('Config').item.json.storeBDomain }}/admin/api/2026-04/graphql.json, authenticated with Store B’s token. Set the body type to JSON and send this JSON body.

{
  "query": "query($q: String!) { productVariants(first: 1, query: $q) { edges { node { id sku inventoryItem { id } } } } }",
  "variables": {
    "q": "sku:{{ $('Get SKU from Store A').item.json.inventory_item.sku }}"
  }
}

A match returns the destination variant’s inventory item ID, which is the target for the write.

{
  "data": {
    "productVariants": {
      "edges": [
        { "node": { "id": "gid://shopify/ProductVariant/70051839",
                    "sku": "TSHIRT-BLK-M",
                    "inventoryItem": { "id": "gid://shopify/InventoryItem/58120394" } } }
      ]
    }
  }
}
💡

Because the search uses the SKU and not a product ID, the two stores can have completely different products, handles, and themes. Only the SKU has to line up.

6 Variant exists? (Filter)

Guard against SKUs that live in one store but not the other. Add a Filter node that continues only when the edges array has a result: {{ $json.data.productVariants.edges[0]?.node?.inventoryItem?.id }} is not empty. If nothing matched, the item is skipped cleanly with no error.

7 Set quantity in Store B (HTTP Request)

This is the write. Use an HTTP Request node with a POST to the same Store B GraphQL endpoint, JSON body type, and the inventorySetQuantities mutation. It sets the destination variant’s available count to whatever Store A now reports.

{
  "query": "mutation($input: InventorySetQuantitiesInput!) { inventorySetQuantities(input: $input) { inventoryAdjustmentGroup { reason } userErrors { field message } } }",
  "variables": {
    "input": {
      "name": "available",
      "reason": "correction",
      "ignoreCompareQuantity": true,
      "quantities": [
        {
          "inventoryItemId": "{{ $('Find variant in Store B').item.json.data.productVariants.edges[0].node.inventoryItem.id }}",
          "locationId": "{{ $('Config').item.json.storeBLocationId }}",
          "quantity": {{ $('Store A inventory webhook').item.json.body.available }}
        }
      ]
    }
  }
}
📌

Keep ignoreCompareQuantity set to true so the mutation overwrites the destination value rather than rejecting the change when the two counts differ. Always read the userErrors field in testing to catch scope or location problems early.

8 Log to Slack (Slack)

Finish with a Slack node so you have a running record and catch failures fast. Set the resource to message, pick your channel with the channel selector, and post a short line.

Synced {{ $('Get SKU from Store A').item.json.inventory_item.sku }} to {{ $('Store A inventory webhook').item.json.body.available }} units on the retail store.

Prefer email or a spreadsheet log instead? Swap this node for a Gmail node or a Google Sheets append and keep the rest of the flow untouched.

Common mistakes

  • SKUs that do not match exactly. A trailing space, a different case, or a missing suffix means the lookup finds nothing. Standardize SKUs across both stores before you switch this on.
  • Using the numeric location ID where a GraphQL global ID is required. The inventorySetQuantities mutation wants gid://shopify/Location/…, not the bare number.
  • Forgetting write_inventory scope on the destination token. The read side will work and the write will fail with a permissions error in userErrors.
  • Untracked inventory. If a variant is not set to track quantity in the destination store, Shopify has nothing to update. Enable inventory tracking on those variants first.
  • Building a two-way sync without a loop guard. If both stores write to each other, one change can bounce back and forth. Skip the write when the incoming quantity already equals the destination count.
  • Hitting the Admin API rate limit during a bulk edit. If you re-sync hundreds of items at once, add a short wait or batch the calls so you stay under the limit.

Cost at realistic volume

The moving parts here are the two Shopify Admin APIs, which cost nothing beyond your existing plans, and the n8n instance that runs the logic. Say a busy pair of stores sees 500 inventory changes a day. Each change fires three API calls, roughly 1,500 calls a day, well within Shopify’s normal limits.

Option Monthly cost Notes
Self-hosted n8n $0 to $10 A small VPS handles this volume with room to spare
n8n Cloud Starter Flat plan fee 500 changes a day is around 15,000 executions a month, inside the entry tier
Shopify Admin API $0 Included on every plan
Typical paid sync app $20 to $100+ Often scales with SKU count, which this build does not

There is no AI model in this workflow and no per-message billing, so the cost stays flat whether you sync 50 SKUs or 5,000. The savings compared with a per-SKU sync app grow as your catalog does.

🚀 Get the two-store inventory sync, done for you

The guide above is free to follow. If you would rather skip the build, download the ready-to-import workflow, then just add your credentials. Prefer it installed and tuned for you? See our done-for-you services.

Download the template ($19) →

Instant download · Works on n8n Cloud and self-hosted

FAQ

Can this workflow sync inventory in both directions?

Yes. Duplicate the workflow, point the second copy at the opposite store, and register the inventory webhook on that store too. To stop an endless update loop, add a small guard that skips a write when the incoming quantity already matches the target store’s stock.

What matches a product across the two stores?

The SKU. Every variant you want to keep in sync must share the exact same SKU string on both stores. The workflow reads the SKU from the store that changed, then searches the second store for the variant that carries the same SKU.

How fast does the second store update?

Almost immediately. Shopify fires the inventory webhook within a few seconds of a sale or manual edit, and n8n processes it in one pass. In practice the second store reflects the new quantity within five to ten seconds of the original change.

Do I need a paid Shopify plan or a multi-store app?

No. This runs on any Shopify plan using the built-in Admin API. You skip recurring multi-store sync apps entirely. Your only cost is the n8n instance, which is free when self-hosted and a flat monthly fee on n8n Cloud.

What happens if a SKU exists in one store but not the other?

The lookup returns no match and the workflow stops for that item without error. A filter node checks that the second store returned a variant before it tries to write. Nothing breaks, and the change is simply skipped until both stores share the SKU.

Related guides

n8n
Shopify
inventory sync
multi-store
automation