LUCENTCOMMERCEGET A FREE STORE AUDITFREE AUDIT

ERP · INTEGRATIONS · SHOPIFY PLUS · 18 MARCH 2025 · 8 MIN READ

NetSuite and Shopify: the integration decisions that matter

The connector is the easy part. What decides whether this works is which system owns each object, and what happens the week one of them is wrong.

An order moving from storefront to ERP through a queue

You integrate NetSuite with Shopify by deciding, object by object, which system is the record of truth, which direction the data flows, what the shared key is, and how often it moves — and only then choosing between a packaged connector, an integration platform, or bespoke middleware. Products, prices and inventory usually flow out of NetSuite into Shopify; orders, customers and payments flow the other way. Nothing should flow in both directions. The technology is the smallest decision on the list, and picking it first is how these projects go wrong.

IN SHORT

  • Write down the record of truth for each object — product, price, inventory, customer, order, fulfilment — before evaluating any connector.
  • No object should be bidirectional. Two writers and no arbiter produces a field that flips back and forth and nobody can explain why.
  • Have your ERP write inventory to Shopify’s `on_hand` state, not `available`. Shopify calculates `available` by subtracting `committed` and the other unavailable states from `on_hand`, and `committed` is not yours to adjust.
  • Store the NetSuite internal id on the Shopify record in a metafield. Matching on SKU works until the day somebody renames one.
  • Webhooks are delivered at least once and Shopify does not guarantee ordering, so every handler needs to be idempotent and every payload needs a queue behind it.
  • Budget for reconciliation and for monitoring the absence of events. Most ERP integrations fail silently, not loudly.

Five decisions, and a connector is not one of them

Every NetSuite–Shopify conversation we are invited into opens with a shortlist of connectors and a question about which one is best. It is the wrong first question, because a connector is a mechanism for enacting decisions you have not made yet. Make them first and the shortlist narrows on its own; skip them and you will argue about them under deadline, inside a tool that has already assumed answers.

  • Record of truth, per object — not per system. Products, prices, inventory, customers, orders, fulfilments, returns and credit notes each need one named owner.
  • Direction, per object, and one way only. If an object has to move both ways, you have two objects sharing a name, and you need to separate them.
  • The key: what identifies the same thing in both systems, where it is stored, and who assigns it.
  • Latency, per object — real-time, near-real-time, or a nightly batch. Almost nothing needs to be real-time, and everything you make real-time you have to keep up.
  • Failure handling: who reconciles, on what cadence, and how anyone finds out the feed stopped.

Record of truth is a business decision wearing technical clothes

The default split is the one most merchants reach: NetSuite owns the item master, cost, inventory and the financial record; Shopify owns storefront presentation, the customer’s account, and the order as it was placed. The arguments start at the edges, and the edges are where the money is.

Price is the first fight. Finance wants price to come from NetSuite because that is where margin lives. Marketing wants to run a weekend promotion without raising a ticket. Both are right, so split the object: NetSuite owns the base price, Shopify owns promotional pricing through its own discount mechanics, and the integration never writes to a discount.

Product content is the second. Split the record by field — ERP owns SKU, weight, dimensions, cost and status; Shopify owns title, description, images and SEO fields. Be explicit, because a connector configured to sync "the product" will overwrite three months of copywriting with a line from the item master.

Customers are the third, and the most common mistake. A Shopify customer is a shopper; a NetSuite customer is often an accounting entity. They do not map one to one, and forcing them to produces duplicate records in NetSuite at a rate nobody anticipated. Push orders with the customer attached and let NetSuite match or create on its own terms.

Inventory: write `on_hand`, not `available`

This is the one technical decision here we would argue about, because getting it wrong produces a bug that looks like an integration fault and is actually a modelling fault.

Shopify tracks inventory in several named states per location. on_hand is documented as the total units physically stocked at a location; available is the number ready for sale, calculated by subtracting committed, reserved, damaged, safety_stock and quality_control from on_hand. Crucially, committed — units held by orders placed and not yet fulfilled — is managed by Shopify, and the Admin API does not let you adjust quantities in that state.

So an ERP pushing its own available figure into Shopify’s available is competing with arithmetic it cannot see. Unless its picture of committed stock is identical, every sync fights Shopify’s. You get a product that oversells, or one showing out of stock with units on the shelf, and the sync log says success every time.

The correct shape: the ERP owns the physical count, writes it to on_hand, and lets Shopify subtract what it has committed. Use inventorySetQuantities, which sets an explicit value and takes a changeFromQuantity field implementing compare-and-swap — if the current quantity does not match what you supply, the mutation fails with CHANGE_FROM_QUANTITY_STALE rather than overwriting somebody else’s change. Passing null to opt out is documented as the choice for when you are the source of truth, which on a store taking orders while you sync you are not.

Orders: queue first, process later, and expect duplicates

Orders flow one way — Shopify to NetSuite — and the mechanism is webhooks. Three properties decide the design, and all three are in the documentation rather than in anyone’s assumptions. Delivery is at-least-once, so the same order will arrive twice at some point. Ordering is not guaranteed: Shopify states plainly that it does not guarantee ordering within a topic, or across topics for the same resource, and suggests sequencing events yourself from the X-Shopify-Triggered-At header or the payload timestamps. And delivery is not guaranteed at all — the same docs recommend a reconciliation job as a safeguard.

What that adds up to: verify the HMAC, write the raw payload to a queue, return 200, and do the NetSuite work in a worker. Calling NetSuite synchronously inside a handler fails under load, when volume is highest, because the handler takes as long as the slowest thing it talks to. Key idempotency on the order id plus the topic and return 200 on a repeat — a sales order created twice in NetSuite is a financial event somebody unpicks by hand.

Fulfilment goes back the other way, and should use fulfilment orders rather than writing fulfilments against the order. Shopify’s documentation is unambiguous: apps using the Order and Fulfillment objects to fulfil orders are a legacy workflow unsupported since API version 2022-07. A connector that still fulfils that way is telling you how actively it is maintained.

Connector, platform, or bespoke — choose by rate of change

Three shapes of answer, and the deciding question is not cost. It is how often your requirements will change.

A packaged connector suits a model close to standard: one currency, one warehouse, orders in, items and stock out. You are buying somebody else’s answers to the five decisions, which is a bargain when you agree with them. Evaluate one by asking it to do the two unusual things your business does; if the answer is a custom script inside the connector, you have bought a bespoke integration with a licence fee attached.

An integration platform suits several systems rather than two, and a mapping that keeps changing. You get monitoring, replay, and transformation logic visible to somebody who is not a developer — worth more than it sounds on a five-year-old integration.

Bespoke middleware suits logic that is genuinely yours: allocation rules, a pricing model nobody else has, fulfilment decisions that consult a third system. We build these and will still talk you out of one if a platform fits, because the build is the cheap part and being the only people who can change it in year three is not.

What we would not do is put that logic inside a Shopify app or a NetSuite script. Both ends then know about each other, neither can be tested without the other, and replacing either means rewriting the lot.

What actually goes wrong, in month four

Launch is not where these projects fail. They fail quietly, some weeks later, and all four failures are cheaper to design for now than to diagnose then.

  • The key drifted. Somebody renamed a SKU and the matching stopped. Store the NetSuite internal id in a metafield at creation; treat SKU as a label, not an identifier.
  • The webhook subscription went away. Shopify retries a failing endpoint on a decaying schedule and eventually deletes the subscription. Nothing alerts you, because the failure is an absence — monitor for "no orders received in the last hour during trading", not for errors.
  • Nobody reconciles. A nightly job counting orders on both sides and reporting the difference takes an afternoon to write. Same for inventory: compare, report, do not auto-correct.
  • Returns and credit notes were out of scope. They always are, and they always come back. Decide where a refund is authored — one place — before the first order goes through.

Questions this raises

How do you integrate NetSuite with Shopify?

Decide the record of truth for each object, the direction it flows, the shared key and the latency, then choose the mechanism. In practice products, prices and inventory flow from NetSuite to Shopify while orders, customers and payments flow the other way, moved by a packaged connector, an integration platform, or bespoke middleware. Order events arrive by webhook and should be queued before any ERP call is made.

Should inventory sync be bidirectional?

No. Inventory needs one owner, and with an ERP in place that owner is the ERP for physical counts. Have it write Shopify’s `on_hand` state and let Shopify derive `available` by subtracting what it has committed against live orders — the Admin API does not permit adjusting the `committed` state, so an ERP writing `available` directly is competing with arithmetic it cannot see.

Is a packaged NetSuite connector good enough?

Often, yes — if your model is close to standard and you agree with the opinions the connector has already formed about mapping. Test it against the two most unusual things your business does. If the answer involves custom scripting inside the connector, you are paying a licence fee for a bespoke integration and should compare it honestly with building one.

How do you match a Shopify product to a NetSuite item?

On the NetSuite internal id, stored on the Shopify record in a metafield when the record is created. SKU matching is tempting because it needs no setup, and it breaks the first time somebody renames a SKU or creates a second item with the same code in another subsidiary.

Does the integration need to be real-time?

Rarely, and every object you make real-time is an object you have to keep up. Orders should be near-real-time because the warehouse is waiting; inventory wants a fast delta feed plus a nightly full reconciliation; product and price content is fine on a schedule. A full catalogue push every few minutes is how an integration exhausts its API budget achieving nothing.

NEXT STEP

Free store audit

A senior Shopify engineer reviews your storefront, theme performance and checkout, then sends a prioritised list of fixes.