Scale your operation with a tech-enabled 3PL. Get a quote.

Integrations / Shopify

Shopify integration setup, sync rules, and the pitfalls

This is the implementation companion to our Shopify integration deep dive. It covers the install steps, the sync rules we apply by default, and the specific failure modes that catch new brands by surprise.

8 scopes
Required at install for full fulfillment integration
1 location
Per warehouse, registered as a fulfillment service
Hourly
Inventory reconciliation against Shopify levels
Nightly
Order reconciliation across the prior 72 hours

TL;DR

  • We install as a Shopify app with the minimum scopes needed for orders, inventory, and fulfillment.
  • Each Warpspeed warehouse is registered as a Shopify Location and a fulfillment service so customer notifications fire correctly.
  • We post tracking through the FulfillmentOrder graph, not the legacy fulfillments REST endpoint, to keep per-line splits intact.
  • Reconciliation runs hourly for inventory and nightly for orders. If a webhook is missed, the reconciler catches it.

Section 01

The install path

We use a public Shopify app distributed through a private install link to brands under contract. The OAuth flow returns an offline access token, which we store encrypted and rotate on demand.[1]

  1. Step 1
    Click install

    Brand admin visits a per-merchant install link, signs in to Shopify, and approves the requested scopes.

  2. Step 2
    Scope review

    Shopify presents the scope list. The default for Warpspeed is read_orders, write_fulfillments, read_locations, write_inventory, read_products, read_inventory, write_assigned_fulfillment_orders, and read_fulfillments.

  3. Step 3
    Webhook subscriptions

    On install we subscribe to orders/create, orders/updated, orders/cancelled, fulfillment_orders/order_routing_complete, and inventory_levels/update.

  4. Step 4
    Location registration

    We create or claim a Shopify Location per Warpspeed warehouse and register it as a fulfillment service. Without this step, customer notifications still come from Shopify but tracking attribution gets messy.

  5. Step 5
    Catalog seed

    We pull the full product and variant catalog using GraphQL bulk operations. This is also when we surface missing weights and dimensions for the brand to backfill.

Section 02

Sync rules we apply by default

The default sync direction and cadence per object class.

ObjectDirectionTriggerFrequency
OrderShopify to WMSorders/create webhookWithin seconds
Order editsShopify to WMSorders/updated webhookWithin seconds, locked once shipped
CancellationShopify to WMSorders/cancelled webhookImmediate; rejected if already picked
FulfillmentWMS to ShopifyPack confirmed in WMSWithin seconds via FulfillmentOrder
Inventory deltaWMS to ShopifyReceive, pick, adjust, countStreaming; reconciler catches misses
Reconciliation pullShopify to WMSCronHourly inventory, nightly orders

Order edits and the locked window

Shopify lets the brand edit an order after it is created: addresses, quantities, notes. We accept those edits up until the WMS marks the work order as picked.[3] After that, edits queue up and get applied to the post-shipment fulfillment record only if they are compatible (for example, customer note is fine, line removal is not). Operators who do not understand this lock window get surprised when a "quick edit" does not propagate.

Inventory writes are deltas, not sets

We use inventoryAdjustQuantities with a delta input. A "set to 73" instruction would race against any other admin or app updating the same level.[4] Deltas are concurrency-safe because Shopify resolves them server-side using the current authoritative count.

Section 03

The five pitfalls that catch most brands

1. Webhook silence

Shopify retries webhooks for 48 hours.[5] If your endpoint returns a 5xx for two days straight, those notifications are gone. The fix is the reconciler, which pulls every order from the prior 72 hours every night and replays anything missing.

2. The "Online Store" location

Shopify creates a default location on store setup. Inventory there is not fulfillable from a 3PL warehouse unless the brand explicitly moves it.[6] A common cutover mistake is missing this step, which makes Shopify route orders to the wrong location and not surface them to the 3PL at all.

3. Variant SKU collisions

Shopify allows two variants to share an SKU. Most WMSes do not. The fix is to enforce uniqueness at install and surface duplicates as a blocker. Brands often have legacy "DUMMY" or "PARENT" SKUs from old import scripts that need cleanup.

4. Bundles without a bundle product

Many bundle apps do not surface child SKUs in the order line items, only the parent bundle SKU. Without a bundle definition synced to the WMS, the warehouse picks the parent and ships an empty box. We require either the bundle app's metafield contract or an explicit bundle definition uploaded before going live.

5. Markets and currency math

Shopify Markets adds market-specific pricing and tax rounding. Order totals from a Markets-enabled store can have rounded cents that do not equal the line item sum.[8] The integration treats Shopify's order total as the source of truth and never recomputes from line items.

Most of the integration support tickets we close in week one are about a missing scope, a missed location migration, or a bundle app the brand forgot to mention.

Section 04

The checks we run quietly

The checks below run on every Shopify integration we operate. They are boring on purpose. The goal is to find the problem before the brand opens a ticket.

  • Webhook health: per-topic delivery success rate over the last hour, paged if any topic drops below 99 percent.
  • Rate-limit headroom: response headers from the Admin API parsed and graphed.[7] Alert at 80 percent budget usage so we back off before we get throttled.
  • Reconciler delta: orders found by the reconciler that were not previously in the WMS. A non-zero count is normal; a sudden spike is investigated.
  • Inventory delta: SKUs where Shopify and the WMS disagree by more than a tolerance. Tolerances are SKU-specific because cycle counts have lag.
  • Stranded fulfillment orders: any FulfillmentOrder in unfulfilled state older than the brand's promised SLA. Pages on-call.

Section 05

Why we use the FulfillmentOrder graph, not legacy fulfillments

Shopify offers two ways to mark an order as shipped. The legacy REST fulfillment endpoint is older, simpler, and still works. The FulfillmentOrder graph is newer, more granular, and the path Shopify recommends.[9] We default to the graph because it handles every modern fulfillment pattern correctly.

Per-line and per-package fulfillment

A real order often ships in pieces. A bundle may pick from two warehouses. A pre-order may ship later than an in-stock item on the same order. A backorder may resolve weeks after the original. The legacy endpoint squashes these into a single fulfillment record per order; the graph represents each as a discrete FulfillmentOrder. That distinction is what makes correct customer notifications possible. Without it, the second shipment on a multi-package order does not trigger a customer email.

Claiming the order

When a fulfillment service is registered against a Location, Shopify routes new fulfillment orders to that service automatically. The integration calls fulfillmentOrderAcceptFulfillmentRequest when it is ready to ship and fulfillmentCreate when the package is in carrier hands. The state machine is explicit, which is what makes auditing what happened to a specific order tractable.

Section 06

B2B, wholesale draft orders, and Plus B2B Edition

Most Shopify integrations focus on DTC. The other side of the store is B2B, which has its own order shape, its own SLAs, and its own integration touchpoints.[11]

Draft orders and net terms

On standard Shopify, B2B is usually shaped as draft orders the brand creates manually. The 3PL receives them through the same orders/create webhook once the draft is finalized and paid. Net-terms invoicing happens outside Shopify, so the integration does not need to model it; we treat the order as paid for fulfillment purposes once it has the paid status, even if the actual cash is collected weeks later.

Plus B2B Edition

Plus B2B Edition adds first-class company accounts, customer-group pricing, and quote-to-order flow inside Shopify. Orders from B2B Edition arrive through the same webhook surface, with additional metafields for company and quote context. We map company-level identifiers to the WMS customer hierarchy so packing slips and BOLs can route correctly to the company's receiving dock, not to the individual buyer.

Operational differences

B2B orders typically pick larger units, ship to commercial addresses with receiving hours, and may need BOL paperwork or palletized shipment. The integration tags B2B orders for a different pick path inside the warehouse, which often means a different cutoff (LTL pickup happens earlier than parcel) and a different pack station. None of this is invisible; we surface the per-channel cutoff and pack pattern in the brand admin so the brand can set buyer expectations correctly.

Section 07

What the brand sees, embedded in Shopify admin

The integration is not just a data pipe. It is also a UI inside the brand's own admin. We use App Bridge to render an embedded surface that looks native to Shopify.[10] The brand never has to tab over to a separate console for routine operations.

The four tiles that close most tickets

The embedded admin's home view is four tiles. Order lag (P50 and P95 order-to-pick time over the last 24 hours). Fulfillment lag (pick to tracking-back time). Inventory variance (per-SKU disagreement count between Shopify and the WMS). Reconciler health (events resolved by reconciler in the last hour, with link to detail). When the brand asks "is something wrong", the answer is on this view.

Per-order audit drill-down

Clicking into an order shows the full timeline: webhook ingest timestamp, GraphQL fetch result, work-order creation, pick assignment, pack confirm, label generation, tracking write-back. Every step has a millisecond-level timestamp and a payload preview. When a customer asks "where is my order", the answer is in this view.

Catalog hygiene console

The catalog hygiene tab lists every variant that has missing or invalid fulfillment-relevant data: missing weight, missing dim, missing HS code on an internationally-shipping SKU, duplicate SKU collisions, ambiguous bundle definitions. Each item links to the Shopify admin row that needs fixing. The brand can clear these without our help.

The integration has not earned its place if a brand has to leave Shopify admin to use it. Tabbing to another tool is friction; we keep the brand inside the admin they already know.

Section 08

Holding up at peak, what changes during BFCM

Black Friday and Cyber Monday are the test the integration was built for. Most brands' annual order volume is concentrated in a few weeks; the integration that runs cleanly in March can still fall over in late November because the failure modes that emerge at 10x volume are different from the ones that emerge at 1x.

What we lock down before peak

Two weeks before BFCM we freeze non-critical changes to the integration codebase. New features ship after the New Year. We pre-warm the database connection pool, expand the worker fleet, and run a load test that pushes an order volume equal to the prior year's peak through a staging shop. Anything that fails in staging gets fixed before the actual surge.

The rate-limit math during peak

Shopify's rate-limit budgets are per-shop, not per-app. When a brand has six apps installed and three of them are doing inventory writes, peak traffic exhausts the budget faster. We do not control what other apps do, but we do control how aggressively we consume the budget. During peak, we throttle our own non-critical writes (catalog reconciler, weight backfills, audit-log mirroring) so the budget stays available for orders and fulfillments.[7]

Synthetic order monitoring

During peak we run a synthetic order through the integration every five minutes. The synthetic flows the entire path: webhook ingest, work-order creation, simulated pack confirm, fulfillment write-back. Any latency above the SLO threshold pages the on-call. The synthetic catches problems that real order traffic does not because it covers paths that real orders may not exercise during a particular hour.

Section 09

Migrating off another 3PL onto our Shopify integration

Most Shopify brands switching to us are switching from another 3PL. The migration is faster than a green-field implementation because the catalog, the SKUs, and the shopify-side configuration already exist; the work is the parallel run, the inventory transfer, and the cutover.

The handover from the prior 3PL

The prior 3PL has to disable its Shopify app at a coordinated cutover time, which usually happens after a final batch ships from the old warehouse. We schedule cutover for a Tuesday or Wednesday morning to leave maximum runway for unforeseen issues before the weekend. Both apps can briefly co-exist during the parallel run window, but only one can hold the fulfillment-service registration on a given Location at a time.

Inventory transfer

Inventory moves on its own schedule, usually freight from the prior 3PL to ours. As units arrive, we receive them into the new Location and the integration pushes the new counts to Shopify. Shopify stops routing orders to the old Location once it shows zero stock; brands sometimes prefer to flip the routing earlier and accept short-window backorders for the in-transit inventory. We support both patterns.

Open orders at cutover

Open orders sitting in the prior 3PL's queue need a decision. The clean answer is the prior 3PL ships them and the new 3PL takes responsibility for any new orders from the cutover timestamp forward. The unclean answer, which sometimes happens when the prior relationship is ending badly, is that the new 3PL inherits the open queue. Either way, the integration has to know which orders are "new" so it does not double-fulfill.

Common migration surprises

Three surprises come up reliably. First, a Shopify app the brand forgot about is still writing inventory; we surface this on day one and the brand uninstalls or scopes it. Second, the prior 3PL's app held a fulfillment-service registration that needs to be released before our app can take over; this is a five-minute fix in admin once identified, but it blocks fulfillment writes until done. Third, the legacy integration was using the REST fulfillments endpoint, leaving multi-package orders partially-fulfilled in the graph; we run a one-time cleanup to migrate those to the FulfillmentOrder model so future writes behave correctly.

The fourth surprise that sometimes appears is shipping-zone differences between the old and new warehouses. A brand whose buyers concentrated on the West Coast under the prior 3PL may see different transit-time distributions when our warehouse is in the Midwest. We surface a before-and-after zone-mix preview during onboarding so the brand can decide whether to adjust their shipping promise copy on the storefront.

Talk to us

We can be live on Shopify in days, not weeks

The mechanical part of a Shopify cutover is fast. The slow part is catalog cleanup. We will tell you what to fix before we set a go-live date.

Sources

  1. [src-1]Shopify app installation overviewShopify Dev
  2. [src-2]OAuth scopes referenceShopify Dev
  3. [src-3]FulfillmentService APIShopify Dev
  4. [src-4]InventoryLevel mutationsShopify Dev
  5. [src-5]Webhook delivery and retriesShopify Dev
  6. [src-6]Locations APIShopify Dev
  7. [src-7]API rate limitsShopify Dev
  8. [src-8]Shopify MarketsShopify
  9. [src-9]FulfillmentOrder graph referenceShopify Dev
  10. [src-10]App Bridge embedded admin patternShopify Dev
  11. [src-11]Shopify Plus B2B EditionShopify

Related