---
title: Create transfers in an external system and receive them in POS
description: >-
  Learn how to originate inventory transfers in an ERP, WMS or IMS, sync them to
  Shopify with the GraphQL Admin API, and receive them in Shopify POS.
source_url:
  html: >-
    https://shopify.dev/docs/apps/build/inventory-management-apps/receiving-external-transfers-in-pos
  md: >-
    https://shopify.dev/docs/apps/build/inventory-management-apps/receiving-external-transfers-in-pos.md
api_name: admin
---

# 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.](https://shopify.dev/assets/assets/images/apps/fulfillments/inventory-management-apps/transfer-integration-flow-DT6kcuHj.png)

***

## Flow at a glance

| # | System | Event | Shopify API |
| - | - | - | - |
| 1 | External system | Transfer order approved | - |
| 2 | Integration | Create transfer, ready to ship | [inventoryTransferCreateAsReadyToShip](https://shopify.dev/docs/api/admin-graphql/latest/mutations/inventoryTransferCreateAsReadyToShip) |
| 3 | Integration | Each shipment sent: create an in-transit shipment (one or many per transfer) | [inventoryShipmentCreateInTransit](https://shopify.dev/docs/api/admin-graphql/latest/mutations/inventoryShipmentCreateInTransit) |
| 4 | Shopify POS | Store scans and receives each shipment | - |
| 5 | Shopify | Webhook to integration | [inventory\_shipments/receive\_items](https://shopify.dev/docs/api/admin-graphql/latest/enums/WebhookSubscriptionTopic#enums-INVENTORY_SHIPMENTS_RECEIVE_ITEMS) |
| 6 | Integration | Read received quantities per shipment line | [InventoryShipmentLineItem](https://shopify.dev/docs/api/admin-graphql/latest/objects/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.

***

## Step 1.​Set up access once

1. **Pick an API version.** Use 2026-10 or later. See [API versioning](https://shopify.dev/docs/api/usage/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](https://shopify.dev/docs/api/usage/idempotent-requests).

4. **Map locations by ID, not address.** Add a custom metafield on each [Location](https://shopify.dev/docs/api/admin-graphql/latest/objects/Location) holding your system's location ID. Read it through [LocationSnapshot.location](https://shopify.dev/docs/api/admin-graphql/latest/objects/LocationSnapshot) on a transfer. See [metafields](https://shopify.dev/docs/apps/build/custom-data/metafields).

5. **Map SKUs.** Store the Shopify `inventoryItemId` for every item.

6. **Subscribe to webhooks** with [webhookSubscriptionCreate](https://shopify.dev/docs/api/admin-graphql/latest/mutations/webhookSubscriptionCreate). Overview: [webhooks](https://shopify.dev/docs/apps/build/webhooks). Topics are in [WebhookSubscriptionTopic](https://shopify.dev/docs/api/admin-graphql/latest/enums/WebhookSubscriptionTopic):

   * [`inventory_shipments/receive_items`](https://shopify.dev/docs/api/admin-graphql/latest/enums/WebhookSubscriptionTopic#enums-INVENTORY_SHIPMENTS_RECEIVE_ITEMS): store received items.
   * [`inventory_transfers/updated`](https://shopify.dev/docs/api/admin-graphql/latest/enums/WebhookSubscriptionTopic#enums-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`](https://shopify.dev/docs/api/admin-graphql/latest/enums/WebhookSubscriptionTopic#enums-INVENTORY_TRANSFERS_ADD_ITEMS), [`inventory_transfers/remove_items`](https://shopify.dev/docs/api/admin-graphql/latest/enums/WebhookSubscriptionTopic#enums-INVENTORY_TRANSFERS_REMOVE_ITEMS) and [`inventory_transfers/update_item_quantities`](https://shopify.dev/docs/api/admin-graphql/latest/enums/WebhookSubscriptionTopic#enums-INVENTORY_TRANSFERS_UPDATE_ITEM_QUANTITIES): item edits on the transfer, including your own `inventoryTransferSetItems` and `inventoryTransferRemoveItems` calls.
   * [`inventory_transfers/complete`](https://shopify.dev/docs/api/admin-graphql/latest/enums/WebhookSubscriptionTopic#enums-INVENTORY_TRANSFERS_COMPLETE) and [`inventory_transfers/cancel`](https://shopify.dev/docs/api/admin-graphql/latest/enums/WebhookSubscriptionTopic#enums-INVENTORY_TRANSFERS_CANCEL): close-out.

***

## Step 2.​Create the transfer from the source transfer order

1. **Trigger:** transfer order approved.
2. **Call** [inventoryTransferCreateAsReadyToShip](https://shopify.dev/docs/api/admin-graphql/latest/mutations/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](https://shopify.dev/docs/api/admin-graphql/latest/objects/InventoryTransfer) ID on your transfer order.
6. **Route later edits** to the stored transfer ID (for example [inventoryTransferSetItems](https://shopify.dev/docs/api/admin-graphql/latest/mutations/inventoryTransferSetItems)), or flag edits your integration does not support.

***

## Step 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](https://shopify.dev/docs/api/admin-graphql/latest/mutations/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](https://shopify.dev/docs/api/admin-graphql/latest/objects/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](https://shopify.dev/docs/api/admin-graphql/latest/mutations/inventoryTransferSetItems), or drop a line with [inventoryTransferRemoveItems](https://shopify.dev/docs/api/admin-graphql/latest/mutations/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](https://shopify.dev/docs/api/admin-graphql/latest/mutations/inventoryShipmentReceive) with reason `CANCELED`. Decide who owns this step (see [Step 4](#step-4-store-receives-in-pos)).

***

## Step 4.​Store receives in POS

**The transfer stays open until every quantity is accepted, rejected or canceled.** While it is open, its [status](https://shopify.dev/docs/api/admin-graphql/latest/enums/InventoryTransferStatus) 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](https://help.shopify.com/en/manual/sell-in-person/shopify-pos/inventory-management/receiving-transfers).
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](https://help.shopify.com/en/manual/inventory-and-locations/inventory-transfers/receiving-and-managing-transfers#receive-items)) or through the API. Decide who does it: back-office staff or your integration. See the [API enum](https://shopify.dev/docs/api/admin-graphql/latest/enums/InventoryShipmentReceiveLineItemReason).

| 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](https://shopify.dev/docs/api/admin-graphql/latest/mutations/inventoryShipmentReceive).

***

## Step 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](https://shopify.dev/docs/api/admin-graphql/latest/queries/inventoryShipment) or [inventoryTransfer](https://shopify.dev/docs/api/admin-graphql/latest/queries/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](https://shopify.dev/docs/api/admin-graphql/latest/objects/InventoryShipmentLineItem) (`acceptedQuantity`, `rejectedQuantity`, `canceledQuantity`). See [webhooks](https://shopify.dev/docs/apps/build/webhooks).
5. **Start with review.** Create receipts as pending until your team trusts the mapping, then auto-post.

***

## Step 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.

***

## Step 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](https://shopify.dev/docs/apps/build/webhooks), 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](https://shopify.dev/docs/api/usage/rate-limits) before bulk-creating transfers.

***
