Skip to content
SystemSyncer
Menu

Guide · Fulfillment

How to Synchronize Shipment Tracking Between Shopify and an ERP

Send shipments and tracking numbers from an ERP or WMS to Shopify using fulfillment orders — partial shipments, multiple packages, carriers, and customer notifications.

Last reviewed:
Reading time
4 min read
Intermediate

Short answer

When the ERP or warehouse confirms a shipment, look up the Shopify order's open fulfillment orders, map the shipped lines and quantities to fulfillment order line items, and call fulfillmentCreate with the tracking company, number, and URL. Handle partial shipments and multiple packages explicitly, make the call idempotent using the ERP shipment ID, and decide deliberately whether Shopify notifies the customer.

Data flow Shipment confirmation flow
  1. ERP / WMS Shipment confirmed
  2. Outbox Shipment ID + lines
  3. Resolve Open fulfillment orders
  4. fulfillmentCreate Tracking + line items
  5. Shopify Customer notified

Shipping confirmation closes the loop on an order. When it fails, customers do not get tracking emails, support volume rises, and the order looks unfulfilled in Shopify even though it left the warehouse days ago. This guide covers the Shopify-specific model and an integration design that handles partial and multi-package shipments correctly.

Understand fulfillment orders

Shopify models fulfillment work through fulfillment orders. Shopify documents a fulfillment order as an item or group of items in an order that are to be fulfilled from the same location; an order can have more than one, even at the same location. Each fulfillment order has line items that reference the original order line items and the quantities still to fulfill.

You do not fulfill an order directly. You create a fulfillment against one or more fulfillment orders, specifying which fulfillment order line items and quantities shipped, with tracking information.

Two other documented details matter:

  • Each fulfillment order has a status and a request status. Only fulfillment orders in a state that allows fulfillment should be used.
  • Each fulfillment order has a delivery method (shipping, pickup, local delivery, and others). Shopify advises iterating over all fulfillment orders to determine the delivery method for each.

Step 1: Capture shipments in the ERP

The ERP or warehouse system should emit a shipment event when a package is confirmed (not when picking starts). The event should include:

  • the ERP shipment ID (your idempotency anchor),
  • the ERP order and the Shopify order ID from the external reference,
  • shipped lines: SKU and quantity,
  • carrier, service, tracking number(s), and tracking URL if available,
  • ship date and origin warehouse.

Publish these through an outbox table or equivalent so a crash between “shipment saved” and “Shopify notified” cannot lose the event.

Step 2: Resolve fulfillment order line items

Query the order’s fulfillment orders and their line items:

query FulfillmentOrdersForOrder($id: ID!) {
  order(id: $id) {
    fulfillmentOrders(first: 20) {
      nodes {
        id
        status
        assignedLocation { location { id } }
        deliveryMethod { methodType }
        lineItems(first: 100) {
          nodes {
            id
            remainingQuantity
            lineItem { id sku }
          }
        }
      }
    }
  }
}

Then match shipped lines to fulfillment order line items:

  1. Filter to fulfillment orders that are open for fulfillment and assigned to the location matching the origin warehouse (via your location mapping).
  2. For each shipped SKU, find line items with that SKU and remainingQuantity > 0.
  3. Allocate the shipped quantity across them, never exceeding the remaining quantity.
  4. If any shipped quantity cannot be allocated, stop and route the shipment to review — it usually means the order was edited, already fulfilled, or the warehouse shipped something unexpected.

Match on the order line item ID when your ERP stored it at export time; it is more precise than matching on SKU when the same SKU appears on several lines.

Step 3: Create the fulfillment

mutation CreateFulfillment($fulfillment: FulfillmentInput!) {
  fulfillmentCreate(fulfillment: $fulfillment) {
    fulfillment { id status trackingInfo { company number url } }
    userErrors { field message }
  }
}
{
  "fulfillment": {
    "notifyCustomer": true,
    "trackingInfo": {
      "company": "UPS",
      "number": "1Z999AA10123456784",
      "url": "https://www.ups.com/track?tracknum=1Z999AA10123456784"
    },
    "lineItemsByFulfillmentOrder": [
      {
        "fulfillmentOrderId": "gid://shopify/FulfillmentOrder/1046000828",
        "fulfillmentOrderLineItems": [
          { "id": "gid://shopify/FulfillmentOrderLineItem/1072503286", "quantity": 1 }
        ]
      }
    ]
  }
}

The documented FulfillmentTrackingInput accepts company, number, numbers, url, and urls. Use a consistent carrier naming convention; Shopify can generate tracking links for carriers it recognizes, and an explicit URL helps for others.

Recommendation, not a platform rule

Map ERP carrier codes to carrier names in a configuration table, and always send the tracking URL when the ERP or carrier integration provides it. This avoids broken tracking links for regional carriers.

Step 4: Make it idempotent

A retry after a timeout must not create a second fulfillment. Defenses:

  • Store a mapping of ERP shipment ID → Shopify fulfillment ID, and check it before creating.
  • Before creating, re-query the fulfillment orders. If the remaining quantities have already dropped by the shipped amounts, the previous attempt likely succeeded; look up the existing fulfillment by tracking number and record it.
  • Process shipments for the same order sequentially.

Handling partial and split shipments

  • Partial shipment: create a fulfillment for the shipped quantities only. The fulfillment order stays open for the rest.
  • Multiple packages at once: either one fulfillment with several tracking numbers, or one fulfillment per package. Per-package fulfillments give customers clearer tracking and line-level detail.
  • Backorders: leave quantities unfulfilled. Do not create zero-quantity fulfillments.
  • Shipped from a different location than assigned: fulfillment orders are tied to locations. If the warehouse ships from somewhere else, the fulfillment order may need to be moved first. Treat this as an exception path and test it with your specific setup.

Customer notifications

Choose one sender for shipping notifications. If Shopify sends them, set notifyCustomer: true. If your ERP, WMS, or a marketing platform sends them, set it to false. Sending both is a common, avoidable complaint.

Error handling

Error Likely cause Action
Quantity exceeds remaining Already fulfilled, order edited, or duplicate event Re-query; if already fulfilled, record and finish; otherwise review
Fulfillment order not in a fulfillable state On hold, cancelled, or closed Review; do not force
Unknown location Missing warehouse mapping Configuration error; alert
Throttled or 5xx Transient Retry with backoff

Security considerations

  • Fulfillment write access lets the integration mark orders shipped; protect the token and scope it narrowly.
  • Validate tracking URLs against an allowlist of carrier domains if they come from external systems, so customers are never sent to an unexpected site.

Testing

  • Single-line, single-package shipment.
  • Partial shipment followed by the remainder a day later.
  • One shipment with two packages and two tracking numbers.
  • Same shipment event delivered twice.
  • Shipment for an order cancelled in Shopify after export.
  • Order whose items are split across two locations.

Common mistakes

  • Trying to fulfill the order as a whole instead of fulfillment order line items.
  • Matching on SKU when the same SKU appears on multiple lines.
  • Creating fulfillments at pick time rather than at shipment confirmation.
  • Double customer notifications.
  • No idempotency link between ERP shipment and Shopify fulfillment.

Summary

Emit a shipment event from the ERP when packages are confirmed, resolve the Shopify order’s open fulfillment orders, allocate shipped quantities to their line items, and create fulfillments with tracking information. Use the ERP shipment ID to make the operation idempotent, handle partial and multi-package shipments explicitly, and choose a single system to notify customers.

Frequently asked questions

What is a fulfillment order in Shopify?

Shopify describes a fulfillment order as an item or group of items in an order that are to be fulfilled from the same location. An order can have several fulfillment orders, and you create fulfillments against their line items.

How do I send multiple tracking numbers for one shipment?

The FulfillmentTrackingInput type accepts lists of numbers and URLs as well as single values. If packages ship at different times, creating one fulfillment per package is usually clearer for customers.

Will Shopify email the customer when I create a fulfillment?

The fulfillmentCreate input includes a notifyCustomer flag. Decide whether Shopify or your own system sends shipping notifications so customers do not receive duplicates.

Can I update a tracking number after creating the fulfillment?

Shopify provides a mutation for updating tracking information on an existing fulfillment. Check the current API reference for its name and inputs in your pinned version.

Sources and further reading

Platform-specific statements on this page were checked against these sources on . Vendor APIs change; confirm details in the current documentation before you build.

  1. [1] Fulfillment service apps and fulfillment orders — Shopify
  2. [2] fulfillmentCreate mutation — Shopify
  3. [3] FulfillmentOrder object — Shopify