Connecting Shopify to an ERP, WMS or 3PL: How to Design It

What flows each way between Shopify and an ERP, warehouse system or 3PL, who owns each record, and how to handle retries, bulk loads, failures and reconciliation.

In this guide
  1. What usually flows, and in which direction
  2. Decide the source of truth per field, not per system
  3. Two ways to connect a warehouse
  4. Webhooks, polling and bulk operations
  5. Idempotency: writing back without doubling
  6. Failure handling
  7. Reconciliation: proving the two sides agree
  8. Off-the-shelf connector or custom integration
  9. Testing before it touches real orders
  10. FAQ
  11. Getting it built

Once a store outgrows packing orders by hand, Shopify stops being the only system that matters. An ERP holds costs, purchase orders and the books. A warehouse system (WMS) or third-party logistics provider (3PL) holds the stock and does the picking. Each needs some of Shopify’s data, and Shopify needs some of theirs back.

This guide walks through how to design that connection: what usually flows in each direction, which system should own each record, how to move data without losing or doubling it, and how to tell when it has drifted. It assumes you already know the general failure patterns of integrations; if not, read Shopify integrations that don’t break first, because this post builds on it rather than repeating it.

What usually flows, and in which direction

Most ERP, WMS and 3PL connections move the same six kinds of record. The direction is what differs between businesses, so write it down before anyone writes code.

Record Usual owner Usual direction Typical trigger
Orders Shopify Shopify → ERP / WMS / 3PL New or updated order
Stock levels WMS or 3PL Warehouse → Shopify Receipt, count, pick, adjustment
Products and variants ERP or PIM, sometimes Shopify One way only, decided up front New SKU, cost or attribute change
Fulfillments Warehouse or 3PL Warehouse → Shopify Shipment packed and handed to carrier
Tracking numbers Warehouse or 3PL Warehouse → Shopify Label printed
Refunds and returns Shopify for money, warehouse for goods Both, split by field Return received, refund issued

Products and refunds cause most of the trouble. Products are covered by the field table below. For refunds and returns, the money side lives in Shopify: a refund is issued there, and once issued it can’t be cancelled or reversed. The goods side lives in the warehouse: what came back, in what condition, and whether it can be sold again. A good design sends the return receipt from the warehouse to a person or an approval step, and lets that person decide the refund in Shopify. Automating the refund itself is a business decision, not a technical default.

Decide the source of truth per field, not per system

“The ERP is the master” sounds clear until someone fixes a typo in Shopify and the next sync puts the typo back. A field-level table avoids that:

Field Owner Shopify’s role
SKU, barcode ERP Read-only copy
Cost ERP Read-only copy
Title, description, images Shopify Owner
Price Shopify or ERP, never both Owner or copy
Available stock per location WMS or 3PL Copy, updated by the integration
Order status Shopify Owner
Fulfillment and tracking Warehouse or 3PL Updated by the integration

Stock deserves a closer look. Shopify does not have a single stock number per variant. Its inventory states include on_hand, available, committed, incoming, and held-back states such as damaged and safety_stock. committed is stock set aside for unfulfilled orders, and the Admin API can’t adjust it directly; Shopify manages it through orders. So when the warehouse sends “we have 40 units”, decide whether that means on hand or available, and write it to the matching state. Getting this wrong is a common reason stock drifts by exactly the number of open orders.

If more than one Shopify store shares the same warehouse stock, read inventory across multiple Shopify stores before choosing a design.

Two ways to connect a warehouse

Shopify gives warehouses and 3PLs two broad patterns.

  1. The warehouse acts as a fulfillment service. Shopify’s fulfillment service apps model this: creating a fulfillment service makes a location for it, merchants send fulfillment requests, and the service accepts or rejects each one, then creates a fulfillment with tracking. Webhook topics such as fulfillment_orders/fulfillment_request_submitted and fulfillment_orders/cancellation_request_submitted tell the integration when to act.
  2. The integration pulls orders and pushes results. The integration reads new orders, sends them to the WMS or 3PL in its own format, and writes fulfillments and stock back. This is simpler when the warehouse has its own order intake and no concept of accepting requests.

The first keeps a record in Shopify of who was asked to ship what. The second puts that bookkeeping in your integration.

Webhooks, polling and bulk operations

A typical integration uses all three, each for a different job.

Method Use it for Watch out for
Webhooks Reacting within seconds to new orders, refunds, product changes Delivery is not guaranteed; duplicates and out-of-order events happen
Scheduled polling Reconciliation: everything changed since the last run Rate limits; the default 60-day order window
Bulk operations First load, full catalog sync, large stock updates Asynchronous; results come back as a file

Webhooks are for speed. The details of verifying them, answering fast and dropping duplicates are in our integrations checklist.

Polling is for correctness. A job that asks Shopify for orders updated since the last successful run catches anything the webhooks missed. One limit matters for first loads: by default an app sees the last 60 days of orders. Reading older orders, for example to backfill an ERP, needs the read_all_orders scope, which requires Shopify’s approval.

Bulk operations are for volume. Bulk mutations let you upload a JSONL file of inputs and run one mutation over all of them in the background. The file can’t exceed 100MB, an operation must finish within 24 hours, and from API version 2026-01 an app can run up to five bulk mutation operations at once per shop. Shopify can notify you through the bulk_operations/finish webhook, but the docs advise polling the operation’s status as well, because webhook delivery isn’t guaranteed.

Idempotency: writing back without doubling

Retries are normal. A request times out, the integration does not know whether it landed, and it tries again. For stock, that can double an adjustment.

Shopify has two tools for this:

  • Idempotency keys. With idempotent requests, a mutation that supports the @idempotent directive runs only once for a given key, however many times it is retried. Generate one key per business event (one receipt, one count) and reuse it on retry. In bulk mutations, the key is passed per row.
  • Compare-and-set for stock. inventorySetQuantities only updates a quantity if the stored value matches the compareQuantity you send. If something else changed the stock in between, the write fails instead of overwriting it, and your integration can re-read and decide. Skipping that check is possible, but Shopify warns it can produce inaccurate quantities under concurrent requests. From API version 2026-04, the docs say an idempotency key is required on this mutation.

Also record where each change came from: Shopify’s inventory mutations accept a referenceDocumentUri, so a stock change can point back to the warehouse receipt or count that caused it.

Failure handling

Every integration fails sometimes. What matters is that failures are kept, visible and retried safely.

  1. Queue, don’t drop. Put each incoming event on a queue before processing it. If the ERP is down, the queue waits; nothing is lost.
  2. Retry with backoff, then park. Retry a failed record a few times with growing delays. After that, move it to a “needs attention” list with the error, rather than retrying forever.
  3. Separate bad data from outages. A missing SKU in the ERP will fail every time; a timeout may succeed in a minute. Treat them differently so one bad order does not block the rest.
  4. Fetch current state when unsure. If an update arrives for an order the ERP has never seen, fetch the order from Shopify rather than guessing.
  5. Alert a person, in one place, with the store, the record and the reason.

Reconciliation: proving the two sides agree

Even a well-built integration drifts. Reconciliation is a scheduled comparison that catches it.

Check How often What a mismatch means
Orders in Shopify vs orders in ERP / WMS for the day Hourly or daily An order was missed or duplicated
Fulfilled orders vs shipments reported by the warehouse Daily Tracking not written back
Stock per SKU and location Daily, or after each count Drift from manual edits or wrong state
Refund totals vs credit notes in the ERP Daily Books and Shopify disagree
Product fields owned by the ERP Weekly Someone edited a read-only field in Shopify

Report mismatches first. A job that silently overwrites stock hides the real cause. Start with a list for a person to review, and automate only the fixes you have watched it get right.

Off-the-shelf connector or custom integration

Many ERPs and 3PLs offer a ready-made Shopify connector. Start there, and check it against your rules.

Question Connector usually fits Custom usually fits
Is the data model standard (one SKU = one stock item)? Yes No: kits, bundles, one sale using several items
One store, one warehouse? Yes Several stores or locations with their own rules
Do field owners match the connector’s defaults? Yes You need per-field ownership it can’t express
Can you see what it skipped and why? It has a clear error log It hides failures
Do refunds need an approval step? Not needed A person must decide before money moves
Does it use a current API version? Maintained by the vendor The vendor lags behind Shopify’s changes

A connector also does not free you from the platform calendar. Shopify releases a new API version every three months, and the REST Admin API is legacy; whoever runs the integration needs to keep up. Our guides to moving from REST to GraphQL and maintaining a custom Shopify app cover what that involves.

If logic needs to run inside checkout, such as keeping items from one warehouse together, that is a job for Shopify Functions rather than the integration. And if you want an AI tool to prepare stock or refund changes from warehouse data, see building an AI agent with human approval.

Testing before it touches real orders

  1. On a development store, load your largest catalog with a bulk operation and check the counts on both sides.
  2. Send the same warehouse receipt twice and confirm stock changes once.
  3. Change the stock in Shopify between a read and a write, and confirm the stale write is rejected.
  4. Take the ERP offline for an hour and confirm the queue holds and drains.
  5. Create a mismatch on purpose and check reconciliation reports it.

FAQ

Should the ERP or Shopify be the source of truth for products?

Decide per field. Commonly the ERP owns SKU, cost and weight, and Shopify owns title, description and images. What matters is that each field has exactly one owner and the integration only writes in that direction.

How do I stop stock updates from being applied twice?

Use an idempotency key per stock event, reused on every retry, and use inventorySetQuantities with compareQuantity so a write based on a stale value fails instead of overwriting newer stock.

Can I import all historical orders into my ERP?

By default an app sees the last 60 days of orders. Older orders need the read_all_orders scope, which Shopify must approve. For large volumes, use bulk operations rather than paging through orders one request at a time.

Should my 3PL be set up as a fulfillment service?

It helps when the 3PL should accept or reject each request and handle cancellation requests before shipping. If the 3PL takes orders through its own intake and only reports shipments, a pull-and-push integration can be simpler.

Do I still need reconciliation if webhooks work?

Yes. Shopify does not guarantee webhook delivery, and manual edits happen on both sides. A scheduled comparison is how you find drift before it becomes an oversell or a wrong invoice.

Getting it built

When a connector can’t express your field owners, your stock rules or your approval steps, a custom integration built around them fits better. Our custom solutions page explains how we approach integrations, and how to brief a developer lists what to send.

Sign in to your workspace

Each workspace has its own address: <workspace>.getstorefleet.com. Enter your workspace name, or paste its address, and we take you to its sign-in page.

You will go to

New to StoreFleet? Create a workspace