Shopify ERP Integration: Sync Inventory & Orders (2026 Guide) — Cesar Ayala
← All posts

Shopify ERP Integration: Sync Inventory & Orders (2026 Guide)

Pick one source of truth per entity: the ERP owns inventory, products, customers and pricing; Shopify owns the order at checkout. Flow orders up to the ERP, inventory and catalog down to Shopify, fulfillment back. Sync with webhooks plus a nightly reconciliation job and idempotency keys. Buy a connector for standard flows; build custom for B2B logic.

How do you sync Shopify with an ERP?

Pick one source of truth per entity, then move data in one direction per field. The ERP owns inventory, products, customers and pricing; Shopify owns the order. Orders flow up to the ERP, inventory and catalog flow down to Shopify, fulfillment flows back. Sync the hot path with webhooks, reconcile nightly, and make every write idempotent.

Here’s the thing most people get wrong on day one: they ask “which connector should I buy?” That’s the wrong first question. An ERP integration is not a connector you buy — it’s a contract you design. The technology is genuinely the easy part. I’ve built Shopify storefronts for Hazil Studios and the integration layer for Mercanto, a B2B wholesale marketplace, and in every one of those projects the code was never what kept me up at night. What keeps you up at night is the moment two systems both believe they own inventory. That’s when you oversell on Black Friday, double-fulfill an order, and spend a week reconciling a spreadsheet by hand.

So this post is built around the decision that actually decides whether your integration survives: who owns what. Everything downstream — the sync mechanism, the build approach, the cost — falls out of that one matrix. The sync architecture itself is the same webhook plus reconciliation pattern I lean on for any serious integration: real-time for speed, a scheduled sweep for truth. Let’s design the contract first.

Who should own which entity? (the source-of-truth question)

The single hardest design call in a Shopify-ERP integration is deciding source of truth, and the mistake nearly everyone makes is deciding it at the system level. “Is Shopify the master, or the ERP?” is the wrong question. The right question is asked entity by entity, and sometimes field by field.

The clean, defensible default looks like this: the ERP (or your WMS) owns inventory quantities, because the warehouse is physical reality and only the ERP sees commitments across every channel. The ERP owns product master data — SKU, cost, attributes — and it owns pricing and customer credit terms. Shopify owns the order at the moment of checkout, because that’s where the sale physically happened and Shopify mints the order ID. Shopify also owns storefront merchandising: images, SEO copy, collections, metafields. Fulfillment is shared in the sense that the ERP or 3PL posts the shipment and tracking, and Shopify reflects it back to close the order for the buyer.

Here is the spine of the whole project — the ownership matrix. Write this on paper before you touch an API:

Entity Source of truth Sync direction Trigger / cadence
Inventory (available) ERP / WMS ERP -> Shopify On stock change + nightly snapshot
Orders Shopify Shopify -> ERP Webhook on orders/create, orders/paid
Products / catalog ERP or PIM ERP -> Shopify On change + nightly batch
Pricing / price lists ERP ERP -> Shopify On change (low frequency)
Customers Split by field Shopify creates, ERP owns A/R On account create; mapped
Fulfillment + tracking ERP / 3PL ERP -> Shopify Webhook on shipment posted
Returns / refunds Shopify Shopify -> ERP (credit memo + restock) Webhook on refunds/create

The one rule that prevents 90% of the pain

Exactly one writer per field. Everyone else is read-only on that field. The instant you let both Shopify and the ERP write the same inventory number, you get data ping-pong: the ERP pushes stock=10, Shopify fires a webhook reporting “inventory changed to 10,” the ERP reads that as a new event and re-applies it, and the loop never ends. Drift, oversell, double-fulfillment — all of it traces back to a field with two writers. Field-level ownership beats system-level ownership every time, and it’s the call that separates an integration that lasts from one you rebuild in eight months.

What are the six data flows, and which way do they go?

Once ownership is fixed, the data flows are no longer a mystery — they’re determined. And the important thing to internalize is that they’re asymmetric. Catalog and inventory flow down to Shopify; transactions flow up to the ERP; fulfillment flows back down. Mapping these arrows explicitly, before you choose a single tool, is what makes the architecture visible.

  • Orders: Shopify -> ERP. On orders/create (or orders/paid if you want payment confirmation first), create a sales order in the ERP. This is the transaction the rest of fulfillment and finance hangs off.
  • Inventory: ERP/WMS -> Shopify. Almost always multi-location. You push an available quantity per location; Shopify is a read replica of “what’s sellable.”
  • Products / prices: ERP or PIM -> Shopify. Master data publishes down. For B2B, this includes per-company catalogs and price lists.
  • Customers: bidirectional, but split by field. Shopify creates the storefront identity; the ERP owns the A/R record, credit terms and tax exemption. You map between the two, you don’t let either overwrite the other’s fields.
  • Fulfillment + tracking: ERP/3PL -> Shopify. Mark fulfilled, write the tracking number, which triggers Shopify’s shipping-confirmation email and closes the order.
  • Returns / refunds: Shopify -> ERP. A Shopify refund becomes an ERP credit memo plus a restock, and the restock flows back as an inventory update. Model this explicitly — leaving returns manual is how A/R and inventory silently desync.

Push ATP, not raw on-hand

This is the single most common cause of overselling, and it’s worth stating loudly: do not sync raw on-hand quantity to Shopify. Sync ATP — available-to-promise — which is roughly On Hand minus Committed minus Safety Stock plus In Transit. If you push raw on-hand, you’ll sell units already committed to open orders, wholesale draws, and other channels. This is also the deepest reason the ERP must own inventory: only the ERP sees commitments across Shopify, a B2B marketplace, Amazon and retail at once. Shopify can’t compute ATP because it can’t see the other channels. This multi-channel reality is the same reason payment and order data has to converge in one place too — it’s why, on Mexican B2B builds, I keep the order-of-record in the ERP rather than scattered across local payment gateways and storefronts.

The six data flows, by direction and trigger

Orders upShopify -> ERP on orders/create — creates the sales order
Inventory downERP -> Shopify, ATP per location, on stock change
Catalog downERP/PIM -> Shopify, SKUs + price lists, batch
Fulfillment backERP/3PL -> Shopify, tracking closes the order
Returns upShopify refund -> ERP credit memo + restock
Customers splitShopify storefront identity <-> ERP A/R record
Catalog and inventory flow down to Shopify; transactions flow up to the ERP; fulfillment flows back to close the order. Each arrow carries its trigger.

Real-time or batch? Why the answer is a hybrid

People treat this as a binary and it’s a false choice. Pure real-time everywhere is expensive and fragile; pure batch oversells during promo spikes. The production answer is a hybrid, chosen per entity.

Real-time webhooks for the hot path — the flows where latency costs money or causes a bad experience: orders/create and orders/paid (you want that sales order in the ERP within seconds), inventory_levels/update, and fulfillments/create. Batch for the high-volume, slow-changing data where instant is unnecessary: full catalog and price-list publishes, customer master syncs, and a bulk inventory snapshot. Catalog master data changes slowly and is huge, so hammering it through webhooks buys you nothing.

Reconciliation is the non-negotiable third leg

Here’s my actual opinion, learned from watching real sync drift: any integration without a reconciliation job is a time bomb. Webhooks are necessary but never sufficient. They are at-least-once and they get missed — your service has downtime, networks drop packets, and Shopify will auto-remove a webhook subscription after enough consecutive delivery failures, silently killing your feed. Worse, some changes don’t fire webhooks at all: a change to committed or safety-stock quantities in the ERP often produces no Shopify event, because the on-hand number didn’t move even though ATP did.

The fix is a scheduled reconciliation sweep — a nightly (hourly for high-velocity stores) job that pulls authoritative state from the source of truth and re-asserts it. The worked example is the one I reach for every time: a nightly full inventory snapshot, ERP -> Shopify, that re-states ATP for every SKU and location and heals whatever the event stream dropped. This is exactly the real-time hot path plus batch correctness sweep pattern from the integrations cluster. Webhooks for speed, reconciliation for truth.

And accept the honest model underneath all of this: eventual consistency. The two systems converge; they are never perfectly identical at every instant. Design buffers and monitoring around that reality instead of fighting it — a small safety buffer on high-velocity SKUs absorbs the sync window and keeps you from overselling in the seconds between an order and the next inventory push.

Why does my inventory keep going wrong? (idempotency & mapping)

This is the unglamorous plumbing that breaks go-lives. When someone tells me “my inventory looks wrong,” it’s almost always one of two things: a mapping mismatch or a missed/duplicated webhook with no reconciliation to catch it.

Idempotency, because delivery is at-least-once

Shopify guarantees at-least-once webhook delivery, so duplicates are normal, not an edge case. Shopify retries a failed or timed-out webhook up to about 8 times over roughly a 4-hour window before the subscription is at risk of removal. So your handler must dedupe. Store the X-Shopify-Event-Id (the value that stays stable across all retries of the same event) in a processed-event store with a TTL longer than that retry window, and no-op repeats with a 200. Do not dedupe on X-Shopify-Webhook-Id — that’s the subscription identifier, not a per-event key, and using it will let duplicates through. On the outbound side, key ERP sales-order creation on the Shopify order ID, so a replayed order webhook can never create a duplicate sales order — that’s the number-one cause of double-shipped orders.

A critical timing rule: return a 2xx within 5 seconds and do the real work asynchronously off a queue. If your handler does slow ERP work inline and blows past the timeout, Shopify treats it as a failure and retries — manufacturing the exact duplicates you were trying to avoid. And treat idempotency as part of your own contract, not something the API enforces for you: Shopify does not dedupe your writes, so the processed-event store and the order-ID key are your job, not the platform’s.

Mapping tables are first-class artifacts

SKU is your join key, and it is brutally fragile. Matching in systems like NetSuite is exact and case-sensitive — a single trailing space is enough to break the link. So you maintain explicit mapping tables in your middleware, never inferred:

  • Shopify variant GID (and its inventoryItemId) <-> ERP item code
  • Shopify location ID <-> ERP warehouse

Locations rarely map 1:1 — one NetSuite warehouse with zones may back several Shopify locations, or several sites consolidate to one fulfillment center. And bundles/kits are the classic trap: the ERP decrements components via a bill of materials, while Shopify sees a single sellable SKU, so ATP for the bundle has to be computed from its components. Freeze this mapping table as a go-live gate.

The 2026 Shopify API realities

New public apps must use the GraphQL Admin API — REST has been maintenance-mode since April 2025. Rate limiting is cost-based: a leaky-bucket of points (around 1,000 on standard plans, double on Plus, restoring roughly 50-100 points/sec), priced by query complexity, not request count. A naive loop over 40,000 SKUs will get throttled exactly when it matters. Use Bulk Operations for big reconciliation sweeps. And for inventory writes, use inventorySetQuantities to write an absolute ATP value from the source of truth, batching multiple items per mutation within the documented per-call cap (Shopify’s guidance is up to 100 inventory item quantities at a single location). Pair it with the compare-and-set guard — changeFromQuantity on 2026-01+ API versions; the older compareQuantity field is deprecated and removed in 2026-04 — so concurrent writes don’t clobber each other. Use set only when you are the source of truth, and never confuse it with adjust.

The four sync non-negotiables

One source of truthExactly one writer per field; everyone else read-only
IdempotencyDedupe on X-Shopify-Event-Id; key ERP sales orders on Shopify order ID
Explicit mappingVariant GID to ERP item, location to warehouse, frozen pre-go-live
Reconciliation jobNightly full ATP snapshot ERP -> Shopify to heal drift
These four rules separate a demo that works in the meeting from a system that survives Black Friday. Skip any one and you will drift.

Should you buy a connector or build custom middleware?

Now — and only now, with ownership and flows fixed — does the build-vs-buy question make sense. There are three approaches, and a clear decision rule.

iPaaS / prebuilt connector (Celigo, Boomi, or a native connector like Business Central’s or Odoo’s). Fastest time-to-value for standard B2C flows against a mainstream ERP. Celigo in particular understands NetSuite’s data model out of the box and ships prebuilt flows for orders, items, inventory, fulfillments and refunds. The vendor maintains the mappings against Shopify’s API changes. The cost: your field mapping and edge-case logic are constrained to what the connector exposes.

Custom middleware — your own service: a webhook receiver, a queue, a dedupe store, a mapping database, a canonical internal schema, and a reconciliation worker. This is the right call when the business logic is the value: B2B per-company price lists, multi-marketplace fan-out, custom allocation, bundles/BoM, or a bespoke ERP no connector handles well. When I built the Mercanto integration layer, the per-company pricing and multi-warehouse allocation logic was precisely the part no off-the-shelf connector could express. This is the work I do under custom Shopify e-commerce development and the middleware and ops dashboards that sit behind it.

Point-to-point direct API. A cron that reads one API and writes the other, no queue, no idempotency, no reconciliation. Fine for exactly one trivial flow. It rots the moment a third system arrives and becomes N-by-N spaghetti with nowhere central to fix a mapping. Avoid it for anything multi-entity.

My verdict, by scenario

Single Shopify store, mainstream ERP, vanilla B2C: buy the connector. Don’t spend months rebuilding what a subscription already maintains against Shopify’s constant API changes. B2B/wholesale, a marketplace, multiple channels, custom allocation or per-company pricing, or an ERP the connectors handle badly (SAP Business One, legacy in-house): build custom — the connector will fight you. And the hybrid 80/20 — iPaaS for the standard plumbing plus a thin custom service for the hard 20% — is where most growing brands actually land, and it’s a legitimate destination, not a failure. Whatever you choose, audit the connector against your real order lifecycle (partial fulfillments, refunds, B2B pricing, multi-currency) before you commit. “Does it sync orders” is not the question; “does it sync my edge cases” is.

Prebuilt connector vs custom middleware

iPaaS / connector

  • Fast time-to-value, weeks not months
  • Vendor maintains mappings vs API changes
  • Subscription + per-volume fees
  • Edge-case logic limited to what it exposes
  • Best for standard B2C + mainstream ERP

Custom middleware

  • Full control of edge-case logic
  • You own webhooks, dedupe, reconciliation
  • One-time build + hosting/maintenance
  • Slower to first go-live
  • Best for B2B, marketplaces, bundles, bespoke ERP
Point-to-point is the avoid case beyond a single trivial flow. Most growing brands land on a hybrid: connector for the standard 80%, thin custom service for the hard 20%.

What does a Shopify-ERP integration actually cost in 2026?

Let me give you real money, because that’s what makes this a practitioner guide and not a brochure. All figures are approximate and 2026-current.

Approach Recurring One-time / implementation Notes
App-store / native connector ~$200-$600/mo Light setup Odoo path cheapest for SMB/mid-market; BC connector is free
Celigo (Shopify -> NetSuite) ~$800-$1,800/mo $15k-$50k partner implementation Implementation at $150-$250/hr
Boomi (mid-market) ~$1,500-$5,000/mo $50k-$200k implementation Watch 2025 connector/message caps
Custom middleware Hosting only Low-tens-of-thousands one-time Cost trade vs recurring volume fees

The number that surprises people is the all-in. Celigo year-one for a Shopify-plus-NetSuite brand realistically lands around $60k-$150k once you add certified-partner implementation. And over a realistic 36-month horizon for, say, a $25M Shopify Plus brand, you’re closer to $182k once you include roughly 0.5 FTE of internal ops time — because none of these are set-and-forget. A connector still needs someone owning monitoring, the dead-letter queue, and the reconciliation job.

Two cautions. First, the 2025 Boomi connector and message caps drove 50-100% renewal price jumps for some teams, so model your renewal, not just year one. Second, custom middleware is a cost trade, not a free lunch: you swap recurring per-volume connector fees for a one-time build plus hosting and the same ops ownership. At high GMV with punitive volume fees, custom can be cheaper over three years; below that, the connector usually wins.

How do you scope and phase the build?

Here’s the go-live playbook, the part that turns all of the above into something you can actually execute without overselling on launch day.

Step 1 — Write the ownership matrix. One row per entity, one owning system, one flow direction per field. This is 80% of the design and it costs you nothing but a meeting.

Step 2 — Clean data and freeze the mapping table. Dirty SKUs, duplicate customers and stale stock corrupt the integration from day one. Build the Shopify-variant-to-ERP-item and location-to-warehouse mapping, run a SKU/location data-quality gate, and freeze the table as a hard go-live gate.

Step 3 — Stand up the reliability scaffolding before cutover. The dead-letter/error queue, the idempotency store, the reconciliation job, and the drift dashboard with alerting all go live first. Don’t cut over onto plumbing you haven’t tested.

Step 4 — Phased cutover by entity, never big-bang. Start with read-only inventory ERP -> Shopify, prove it converges, then turn on orders Shopify -> ERP, then fulfillment back to Shopify, then customers and pricing. Cutting everything over at once means every bug fires at once.

Step 5 — Instrument everything. Log every sync event, expose per-entity drift counts (Shopify vs ERP) on a dashboard, and alert on dead-letter queue growth. You cannot trust an integration you cannot observe — and “looks fine” is not observability.

Phased go-live for a Shopify-ERP integration

  1. Write the ownership matrixOne owner + one direction per entity, on paper
  2. Clean data, freeze mapping tableSKU/location data-quality gate before go-live
  3. Stand up reliability scaffoldingError queue, idempotency store, reconciliation job, drift dashboard
  4. Phased cutover by entityInventory read-only -> orders -> fulfillment -> customers/pricing
  5. Instrument and monitorLog every event; alert on drift and DLQ growth
Scope by entity, clean data first, scaffold reliability before cutover, then cut over one entity at a time while watching drift counts.

A tiny sketch of what the order-sync mapping looks like in practice — the canonical shape your middleware writes to the ERP, keyed for idempotency:

{
  "idempotency_key": "shopify_order_5821996",
  "source_order_id": "gid://shopify/Order/5821996",
  "erp_sales_order": {
    "customer_ref": "ERP-CUST-00412",
    "warehouse": "WH-CENTRAL",
    "lines": [
      { "erp_item": "SKU-RED-M", "qty": 2, "shopify_variant": "gid://shopify/ProductVariant/4471" }
    ],
    "currency": "MXN"
  }
}

FAQ: Shopify-ERP integration questions

Which system should own inventory?

The ERP or WMS, without exception. It’s the only system that sees commitments across every channel — Shopify, a B2B marketplace, Amazon, retail. Compute ATP (on hand minus committed minus safety stock plus in transit) there and push it one-way to Shopify. Never let Shopify write stock back as authoritative; that’s how oversell starts.

Real-time or batch?

A hybrid, decided per entity. Use webhooks for the hot path — orders and inventory updates — and batch for high-volume slow-changing data like catalog and customers. Then add a nightly reconciliation sweep that re-asserts ERP truth onto Shopify. Webhooks give you speed; the reconciliation job gives you correctness. You need both.

Do I need an iPaaS or can I build custom?

Buy a connector (Celigo, Boomi, native Business Central or Odoo) for standard Shopify-NetSuite-style flows — it’s faster and vendor-maintained. Build custom middleware when your logic is non-standard: B2B price lists, bundles/BoM, multi-marketplace fan-out, custom allocation, or a bespoke ERP. Most growing brands end up with a legitimate hybrid of both.

Why is my inventory wrong?

Almost always one of two causes: SKU or location mapping mismatches (a trailing space or an unmapped warehouse silently drops stock), or missed and duplicated webhooks with no reconciliation job to catch the drift. Fix the mapping table first, then add the nightly reconciliation sweep. Those two changes resolve the overwhelming majority of inventory bugs.

The one decision to get right

If you take one thing from this, make it the order of operations: the ownership matrix goes on paper first, one writer per field, then a hybrid sync with a reconciliation job, and only then do you choose connector versus custom. The build approach is downstream of the design, not the other way around.

And here’s the part that’s true no matter what you buy: the two things you must own yourself are the source-of-truth matrix and the reconciliation job. A connector can run your plumbing, but it cannot decide who owns your inventory, and it will not save you from drift if nobody owns the nightly sweep. Get those two right and you won’t oversell, regardless of how the rest is built. This is exactly the kind of integration work I do — Shopify, ERPs and marketplaces — so if you’re scoping one, reach out.