Automation•9 min read

Inventory sync for Shopify: stop overselling with n8n and one truth

Overselling is rarely a “Shopify problem”. It is usually a “too many systems are allowed to change stock” problem. If Shopify, WooCommerce, Cin7 and a fulfilment tool can all write quantities, you will get stockouts, duplicated SKUs and hours of reconciliation.

This post shows a low risk way to fix it: choose a single source of truth for stock, wire the rest to it with n8n, then add a simple reconciliation loop that alerts you before customers are affected.

The short version

  • Pick one system as the stock source of truth, then make every other system a consumer, not a co-author.
  • Define SKU rules (variants, bundles, aliases) before you automate, because most sync bugs are mapping bugs.
  • Use event triggers where possible, but reconcile on a schedule because webhooks can arrive out of order.
  • Treat refunds, partial shipments and cancellations as stock events with explicit rules, not edge cases.
  • Add monitoring from day one: log every adjustment, alert on drift, and make retries safe and idempotent.

What is a “single source of truth” for inventory, in plain terms?

It is the one place where stock is allowed to change because something real happened: goods received, a pick and pack, a stock adjustment, a return booked back in.

Everything else should either:

  • reserve stock (orders), or
  • display stock (sales channels), or
  • report on stock (sheets, dashboards).

The mistake we see most is letting the sales channel be the truth. Shopify and WooCommerce are excellent at taking orders, but they are not built to be your warehouse ledger when you have multiple locations, 3PLs, bundles and backorders. Shopify’s inventory model is explicitly location based and tracks multiple quantity states for an inventory level, not just one “stock number”. That matters when you try to glue it to other systems. See Shopify’s `InventoryLevel` object description for what it actually represents. Shopify InventoryLevel (GraphQL Admin API)

A pragmatic rule that works for most UK SMEs:

  • If you have Cin7, make Cin7 the inventory truth.
  • If you do not, pick the system closest to the physical stock movement (often ShipStation plus a warehouse process, or a stock spreadsheet if you are very small).

Then implement one write path into Shopify and WooCommerce, not four.

Should stock sync be one way or two way?

Two way sync sounds sensible, but it multiplies failure modes. Webhooks arrive late or out of order, APIs rate limit, and “last write wins” corrupts your data.

Shopify is explicit that webhooks are not guaranteed to arrive in order, even within the same topic, and recommends using timestamps to organise events. That is your warning label against naive two way sync. Shopify webhooks: ordering and timestamps

Use this decision framework instead.

A simple decision framework

One way stock, two way orders is the default pattern.

  • Stock: one way, from truth system to sales channels.
  • Orders: two way, because orders start in the channel, then status changes happen in fulfilment.

Where people go wrong:

  • They push stock changes from Shopify back into Cin7 “to keep Cin7 up to date”. That turns Shopify into a stock author.
  • They update Shopify stock by “adjusting” without a reconciliation step, so drift accumulates silently.

If you are forced into two way stock for business reasons, you need conflict rules (which system wins, per field) and a quarantine process for mismatches. Most teams do not have the discipline for that, and you end up with manual fixes anyway.

The data model you need before you touch n8n

You can build the workflow in a day. Agreeing what a SKU means takes longer. If you do not get this right, you will automate the wrong thing very efficiently.

SKU rules (the bits that actually break)

Define these explicitly and write them down:

  1. Canonical SKU: one unique identifier for each sellable unit.
  2. Aliases: old SKUs, supplier SKUs, barcode, or “pretty” SKUs used on one channel.
  3. Variants: size and colour variants must map to a single canonical SKU each, not to the parent product.
  4. Bundles and kits: decide whether the bundle is a separate SKU that explodes into components, or a purely virtual bundle.

Cin7’s API models “kit” behaviour and exposes kit related stock concepts in its stock model (for example virtual stock for kits). That is useful, but it also means you must decide where the kit expansion happens: in Cin7, or in your integration layer. Cin7 StockUnit model (kit virtual stock field)

Refunds, cancellations and partial shipments

Pick rules you can implement consistently:

  • Cancellation: if an order is cancelled before pick, release reserved stock back to available.
  • Refund: do not treat “refund created” as stock returning. Shopify’s refund webhooks are about a refund record being created, independent of the movement of money, and it says nothing about whether goods are returned to stock. Shopify `refunds/create` webhook topic
  • Return received: only when the return is received and checked in, increase stock (and decide where that action is recorded).
  • Partial shipment: decrement stock at the moment of pick and pack, not when the final shipment goes out, otherwise you will oversell during split fulfilments.

Write these as “if this event happens, we do this stock change in system X” statements. You will use them as test cases.

A practical n8n workflow: Cin7 as truth, Shopify and WooCommerce as consumers

This is the pattern we typically deploy first because it is low risk.

The core flow

  1. Trigger: inventory change in Cin7 (or scheduled poll if you cannot get change events reliably).
  2. Map: Cin7 product option or SKU to canonical SKU.
  3. Write: set Shopify inventory level per location.
  4. Write: update WooCommerce `stock_quantity` only for products where `manage_stock` is true.
  5. Log: append a row to a Google Sheet error log and run log.
  6. Alert: send Slack or email on any mismatch, rate limit or missing mapping.

Shopify: use InventoryLevel set, not “edit product”

When you update stock in Shopify, do it through the InventoryLevel endpoints. Shopify’s REST Admin API has `inventory_levels/set` and `inventory_levels/adjust`. They work on `inventory_item_id` and `location_id`, which is the correct grain for multichannel sellers. Shopify REST Admin API: InventoryLevel resource

In practice that means:

  • You need a lookup table from your canonical SKU to Shopify `inventory_item_id` (and location).
  • You should avoid trying to update stock via product or variant endpoints, because inventory is not actually owned there.

WooCommerce: manage_stock must be true

WooCommerce’s REST API product fields include `manage_stock` and `stock_quantity`. If you push `stock_quantity` into products that are not managing stock, you will get confusing results and operators will “fix it” in the UI, creating drift. WooCommerce REST API docs (products: manage_stock, stock_quantity)

Rate limits and backpressure (why your workflow works in staging then dies on a sale)

If you push 5,000 inventory updates in a burst, Shopify will throttle you. Shopify documents that most API rate limits are enforced with a leaky bucket algorithm. That is not a problem if you treat the sync as a queue, but it is a problem if you treat it as a single linear “do everything now” run. Shopify API usage limits

In n8n terms, you want:

  • batching (group updates per shop and per location),
  • retries with a wait, and
  • idempotency keys or a stored “last applied version” so retries do not double apply changes.

n8n has node level retry settings and execution history you can re-run, but you still need to design for duplicates and partial failure. n8n’s own guidance includes using retry on fail and an error workflow for production handling. n8n User Guide: Retry on Fail and error workflows

How do you catch mismatches before customers see them?

If you do one thing after implementing a sync, do this: add a reconciliation job.

Webhooks are useful, but they are not a ledger. They can be delayed, duplicated or arrive out of order. Your reconciliation is what tells you that reality diverged from your model.

The weekly check you can run this week

Pick 20 SKUs:

  • 10 high velocity items
  • 5 bundles
  • 5 items that regularly go out of stock

For each SKU, record:

  • Shopify available per location
  • WooCommerce stock
  • Cin7 available and on hand
  • ShipStation allocated (if you use it for reservation)

If any SKU differs by more than your tolerance (often 1 unit for low volume, or 0.5 percent for high volume), create a ticket and find the cause. The cause will nearly always be one of:

  • a missing SKU mapping
  • a manual adjustment in the wrong system
  • a bundle exploding differently across channels
  • a refund or cancellation path that does not release reservations

A mismatch alert that is actually useful

Alerts should be actionable. Do not send “inventory mismatch” with no context.

Include:

  • canonical SKU
  • the quantities in each system
  • the last update timestamp per system
  • the workflow execution ID
  • the recommended action (for example “missing mapping”, “rate limited”, “manual edit detected”)

Google Sheets is a fine place to start for the log and checklist. Google publishes usage guidance for the Sheets API, including that there is no per day request limit as long as you stay within per minute quotas. That helps you avoid building a database before you need it. Google Sheets API usage limits

Worked example: mapping one SKU, one bundle, one refund path

Here is a small example you can copy into your own mapping sheet.

Canonical SKUChannel SKUTypeBundle componentsTruth stock fieldShopify write actionWoo write action
TSHIRT-BLK-MBLKTS-MSimpleCin7 availableInventoryLevel set at Location ASet stock_quantity
GIFTSET-01GIFT-SET-01BundleTSHIRT-BLK-M x1, MUG-WHT x1Cin7 available (kit)Set bundle SKU stock only (do not set components)Same
MUG-WHTMUG-WHITESimpleCin7 availableInventoryLevel set at Location ASet stock_quantity

Rules behind this table:

  • We only push stock to Shopify and WooCommerce from Cin7.
  • Bundle stock is written as the bundle SKU only, because the customer buys the bundle SKU. Component stock is managed by Cin7’s kit logic.
  • A Shopify refund webhook is logged, but does not change stock. Stock increases only when a return is received and booked in Cin7.

This is also where you decide “who owns SKUs”. If marketing wants to rename SKUs in Shopify, fine, but your canonical SKU should not change. Treat channels as views of your catalogue, not the source.

A minimal n8n build that is safe to run unattended

This is the smallest workflow we are comfortable leaving on in production.

  1. Trigger: Cron every 5 minutes.
  2. Fetch: list changed products from Cin7 since last run.
  3. For each SKU:
  • Validate mapping exists.
  • Fetch current Shopify inventory level for the inventory item and location.
  • If different, call `inventory_levels/set`.
  • Update WooCommerce stock.
  1. Log: write one row per SKU adjustment to Google Sheets.
  2. Error workflow: on any failed execution, post the SKU, payload and error details to a channel and write to the error log.

Two implementation notes that prevent real incidents:

  • Store your “last successful run timestamp” somewhere durable. If n8n restarts, you should not re-process the last week by accident.
  • Make updates idempotent. If you run the same update twice, it should result in the same final stock. Using “set to this quantity” is usually safer than “adjust by delta” for sync jobs, because deltas double apply on retries.

If you are also syncing fulfilment status back to Shopify, ShipStation supports webhook subscriptions and documents webhook event types in its API v2 guides. That is the right mechanism for status updates, but you still need a reconciliation check for missed events. ShipStation webhooks overview (API v2)

If you want more background on building order automations that do not miss events, we wrote up the webhooks failure modes in our ShipStation webhook guide.

Related: how we approach AI automation and the integrations we already build.

Inventory sync automation and monitoring for e-commerce teams

If you want this running reliably, the hard part is not the first workflow. It is the monitoring, the change control, and keeping pace with API changes across Shopify, WooCommerce, Cin7 and ShipStation.

Swarm Labs is a UK software studio in Manchester. We build and run managed n8n automations, and this includes our E-commerce Inventory Sync Automation and Monitoring service. If you want help mapping your flow and shipping a monitored sync safely, talk to us about your integration.

Sources

  1. Shopify Developers: InventoryLevel (GraphQL Admin API)
  2. Shopify Developers: InventoryLevel (REST Admin API resource)
  3. Shopify Developers: About webhooks (ordering and timestamps)
  4. Shopify Developers: API usage limits
  5. WooCommerce: REST API Documentation
  6. Cin7 Omni API: StockUnit model
  7. ShipStation Developer Docs: Webhooks overview (API v2)
  8. Google for Developers: Google Sheets API usage limits

Want this wired up
for you?