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.

Anchor to Flow at a glanceFlow at a glance
| # | System | Event | Shopify API |
|---|---|---|---|
| 1 | External system | Transfer order approved | - |
| 2 | Integration | Create transfer, ready to ship | inventoryTransferCreateAsReadyToShip |
| 3 | Integration | Each shipment sent: create an in-transit shipment (one or many per transfer) | inventoryShipmentCreateInTransit |
| 4 | Shopify POS | Store scans and receives each shipment | - |
| 5 | Shopify | Webhook to integration | inventory_shipments/receive_items |
| 6 | Integration | Read received quantities per shipment line | InventoryShipmentLineItem |
| 7 | Integration | Write 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
- Pick an API version. Use 2026-10 or later. See API versioning.
- Request scopes:
write_inventory_transfers,write_inventory_shipments,write_inventory_shipments_received_itemsandread_locations(to read Location metafields), plus the matchingread_scopes for webhooks. Addwrite_locationsif your app writes the location mapping metafield. Each mutation page lists its scopes. - Send an idempotency key on every write. Required from 2026-04 on the mutations in this guide. Use the
@idempotentdirective. See idempotent requests. - 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.
- Map SKUs. Store the Shopify
inventoryItemIdfor every item. - Subscribe to webhooks with webhookSubscriptionCreate. Overview: webhooks. Topics are in WebhookSubscriptionTopic:
inventory_shipments/receive_items: store received items.inventory_transfers/updated: a shipment on the transfer is created, edited or removed. Re-fetch the transfer. Requires API version 2026-10 or later.inventory_transfers/add_items,inventory_transfers/remove_itemsandinventory_transfers/update_item_quantities: item edits on the transfer, including your owninventoryTransferSetItemsandinventoryTransferRemoveItemscalls.inventory_transfers/completeandinventory_transfers/cancel: close-out.
Anchor to Step 2. Create the transfer from the source transfer orderStep 2. Create the transfer from the source transfer order
- Trigger: transfer order approved.
- Call inventoryTransferCreateAsReadyToShip.
- Set: origin and destination location IDs, line items,
referenceName= your transfer order number,note,tags. - 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.
- Store the returned InventoryTransfer ID on your transfer order.
- 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
- Trigger: a shipment is confirmed in your system. A transfer can have one shipment or many.
- Call inventoryShipmentCreateInTransit once per shipment, with the transfer ID, the line items for that shipment, carrier and tracking.
- Idempotency key: your shipment or fulfillment ID.
- One shipment record = one InventoryShipment. For carton-level scanning in POS, send one carton per shipment.
- Note: carrier and tracking live on the shipment, not on the transfer.
- 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
DRAFTandREADY_TO_SHIPtransfers. 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).
- 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
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.
- Staff open Products > Incoming transfers in POS and scan or enter the quantities they accept for each shipment. See Receiving transfers in POS.
- 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.
| Action | API reason | Effect in Shopify |
|---|---|---|
| Accept | ACCEPTED | Stock becomes available at the store. |
| Reject | REJECTED | Recorded on the transfer. No stock change at any location. |
| Cancel | CANCELED | For 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
- Read the quantities from the webhook payload. Each
inventory_shipments/receive_itemspayload lists the received lines (items_received) with the line ID and theold_andnew_accepted, rejected and canceled quantities, plusremaining_quantity_to_receive, the shipmentstatusandinventory_transfer_id. Query inventoryShipment or inventoryTransfer only for fields the payload doesn't include. - Match to your transfer order by
referenceNameor the stored transfer ID. - Post each event's change, once. For each received line, the change is
new_minusold_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. - 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. - 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
| Case | Shopify action | Suggested source-system side |
|---|---|---|
| Arrived good | Accept | Receipt at that quantity. |
| Damaged, missing or refused in transit | Reject 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 origin | Cancel | No receipt. The units are available at the origin again. |
| Found later | Edit the received quantity to Accept | Adjust the receipt. |
| Over-received | Receive 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
- Transfers stay editable after "Ready to ship." Enforce your own lock in the integration, and restrict staff with the Admin transfer permissions.
- On every transfer webhook (
inventory_transfers/updatedand the item-change topics), compare Shopify with your system. If they differ, flag for review. Do not auto-write back. - Name an owner for the review queue and agree a response time.
- 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.
- Tags do not block edits. Your integration must check any tag it uses as a signal.
- Transfers open at cutover should finish in the system where they started.
- Volume: check rate limits before bulk-creating transfers.