Skip to main content

Create transfers in an external system and receive them in POS

Use this guide when an ERP, WMS or IMS creates transfer orders and store staff receive them in Shopify POS. The external system stays the source of truth. Your integration mirrors each transfer to Shopify, then writes receipts back.

The flow from an external system, through your integration and the Shopify Admin API, to receiving in Shopify POS and back to the external system.

#SystemEventShopify API
1External systemTransfer order approved-
2IntegrationCreate transfer, ready to shipinventoryTransferCreateAsReadyToShip
3IntegrationEach shipment sent: create an in-transit shipment (one or many per transfer)inventoryShipmentCreateInTransit
4Shopify POSStore scans and receives each shipment-
5ShopifyWebhook to integrationinventory_shipments/receive_items
6IntegrationRead received quantities per shipment lineInventoryShipmentLineItem
7IntegrationWrite receipt to the external system-

The transfer stays open until every quantity is accepted, rejected or canceled. Steps 3 to 7 repeat for each shipment.


Anchor to Step 1. Set up access onceStep 1. Set up access once

  1. Pick an API version. Use 2026-10 or later. See API versioning.
  2. Request scopes: write_inventory_transfers, write_inventory_shipments, write_inventory_shipments_received_items and read_locations (to read Location metafields), plus the matching read_ scopes for webhooks. Add write_locations if your app writes the location mapping metafield. Each mutation page lists its scopes.
  3. Send an idempotency key on every write. Required from 2026-04 on the mutations in this guide. Use the @idempotent directive. See idempotent requests.
  4. Map locations by ID, not address. Add a custom metafield on each Location holding your system's location ID. Read it through LocationSnapshot.location on a transfer. See metafields.
  5. Map SKUs. Store the Shopify inventoryItemId for every item.
  6. Subscribe to webhooks with webhookSubscriptionCreate. Overview: webhooks. Topics are in WebhookSubscriptionTopic:

Anchor to Step 2. Create the transfer from the source transfer orderStep 2. Create the transfer from the source transfer order

  1. Trigger: transfer order approved.
  2. Call inventoryTransferCreateAsReadyToShip.
  3. Set: origin and destination location IDs, line items, referenceName = your transfer order number, note, tags.
  4. Idempotency key: your transfer order ID. Use one create key per source order. A new key creates a second transfer, so never reuse create for edits.
  5. Store the returned InventoryTransfer ID on your transfer order.
  6. Route later edits to the stored transfer ID (for example inventoryTransferSetItems), or flag edits your integration does not support.

Anchor to Step 3. Create a shipment each time the warehouse shipsStep 3. Create a shipment each time the warehouse ships

  1. Trigger: a shipment is confirmed in your system. A transfer can have one shipment or many.
  2. Call inventoryShipmentCreateInTransit once per shipment, with the transfer ID, the line items for that shipment, carrier and tracking.
  3. Idempotency key: your shipment or fulfillment ID.
  4. One shipment record = one InventoryShipment. For carton-level scanning in POS, send one carton per shipment.
  5. Note: carrier and tracking live on the shipment, not on the transfer.
  6. Account for 100% of every transfer quantity. A transfer can't close while any quantity is unresolved. There are two cases for a short shipment:
    • You know before shipping that the warehouse will send less. Edit the transfer first. Lower the line to the quantity you will ship with inventoryTransferSetItems, or drop a line with inventoryTransferRemoveItems. Both work on DRAFT and READY_TO_SHIP transfers. Removing items returns the reserved quantity to available at the origin.
    • You learn it at receiving. The missing units are still on the shipment you created. Do not edit the transfer. Cancel those units in the Shopify admin transfer page, or call inventoryShipmentReceive with reason CANCELED. Decide who owns this step (see Step 4).

Anchor to Step 4. Store receives in POSStep 4. Store receives in POS

The transfer stays open until every quantity is accepted, rejected or canceled. While it is open, its status can be READY_TO_SHIP or IN_PROGRESS, for example when one shipment is received and another quantity is not yet shipped. Decide from shipment state, and watch for inventory_transfers/complete to know it closed.

  1. Staff open Products > Incoming transfers in POS and scan or enter the quantities they accept for each shipment. See Receiving transfers in POS.
  2. Each quantity gets one action. You can split a line, for example accept 8 units and reject 2. POS accepts quantities. Reject and Cancel happen in the Shopify admin transfer page (receive items) or through the API. Decide who does it: back-office staff or your integration. See the API enum.
ActionAPI reasonEffect in Shopify
AcceptACCEPTEDStock becomes available at the store.
RejectREJECTEDRecorded on the transfer. No stock change at any location.
CancelCANCELEDFor items never shipped. Stock returns to the origin as available.

Receive through the API with inventoryShipmentReceive.


Anchor to Step 5. Read the receipt and write it backStep 5. Read the receipt and write it back

  1. Read the quantities from the webhook payload. Each inventory_shipments/receive_items payload lists the received lines (items_received) with the line ID and the old_ and new_ accepted, rejected and canceled quantities, plus remaining_quantity_to_receive, the shipment status and inventory_transfer_id. Query inventoryShipment or inventoryTransfer only for fields the payload doesn't include.
  2. Match to your transfer order by referenceName or the stored transfer ID.
  3. Post each event's change, once. For each received line, the change is new_ minus old_ for accepted, rejected and canceled quantity. Moving units between dispositions (for example rejected to accepted) changes two totals: receive the accepted change and reverse the earlier loss posting. Changes add up to the same total in any order, so you don't need to sort events. Post them in the same transaction that records the event ID, so a retry can't post twice.
  4. Deduplicate webhooks by the event ID in the delivery headers, not by timestamp. Two receipts can share a happened_at, so don't use it to order or drop events. Webhooks can also arrive twice or be missed, so a reconciliation job (Step 7) compares your running totals with Shopify's cumulative quantities per InventoryShipmentLineItem (acceptedQuantity, rejectedQuantity, canceledQuantity). See webhooks.
  5. Start with review. Create receipts as pending until your team trusts the mapping, then auto-post.

Anchor to Step 6. Handle discrepanciesStep 6. Handle discrepancies

CaseShopify actionSuggested source-system side
Arrived goodAcceptReceipt at that quantity.
Damaged, missing or refused in transitReject in the admin or API, with a note or metafield (for example "lost")Receipt at 0 for those units, plus a loss or write-off.
Never shipped from the originCancelNo receipt. The units are available at the origin again.
Found laterEdit the received quantity to AcceptAdjust the receipt.
Over-receivedReceive the larger quantity. Shopify allows it.Sync the accepted quantity once. Handle the excess under your own policy, for example an alert.

Choose Cancel or Reject for every unreceived unit, including short-shipped quantity. Every unit of every transfer line needs an action before the transfer can close.

  • Cancel returns the units to the origin. Use it when you find out at receiving that units on the shipment never shipped.
  • Reject keeps the units out of every location. Use it for carrier damage or items lost in transit.
  • Do not use Cancel for lost units. It would put stock back at the origin that no longer exists.

Shopify has no separate "lost" state. Record the reason in the transfer note or a metafield.


Anchor to Step 7. Keep the two systems in stepStep 7. Keep the two systems in step

  1. Transfers stay editable after "Ready to ship." Enforce your own lock in the integration, and restrict staff with the Admin transfer permissions.
  2. On every transfer webhook (inventory_transfers/updated and the item-change topics), compare Shopify with your system. If they differ, flag for review. Do not auto-write back.
  3. Name an owner for the review queue and agree a response time.
  4. Run a reconciliation job on a schedule, not only on webhooks. Webhook delivery isn't always guaranteed, so a missed webhook would otherwise go unnoticed. Compare in-transit and received quantities in both systems, matched on reference.
  5. Tags do not block edits. Your integration must check any tag it uses as a signal.
  6. Transfers open at cutover should finish in the system where they started.
  7. Volume: check rate limits before bulk-creating transfers.

Was this page helpful?