Table of Contents
- Key Highlights:
- Introduction
- What changed: origin.id and destination.id in inventory transfer webhooks
- Understanding the Location Global ID (GID) and how to use it
- When origin.id and destination.id will not appear in payloads
- inventoryTransferSetItems: clarified behavior and practical implications
- inventoryTransferRemoveItems: clarified behavior and consequences
- Clearer error messages: what changed and how to respond
- Migration and compatibility: adopting the change with minimal risk
- Real-world scenarios and worked examples
- Testing and validation strategies
- Edge cases and concurrency concerns
- Best practices for robust webhook consumers
- Operational impact for merchants and apps
- Implementation checklist and sample code patterns
- Monitoring and observability recommendations
- Recommendations and next steps for teams
- FAQ
Key Highlights:
- Inventory transfer webhook payloads now include origin.id and destination.id as Location Global IDs (e.g., gid://shopify/Location/123), enabling consumers to identify transfer endpoints without an extra API call.
- Clarifications to inventoryTransferSetItems and inventoryTransferRemoveItems explain upsert semantics, allocation preservation, allowed statuses, and the effect of zero quantities; several user-facing error messages were made more descriptive while error codes remain unchanged.
- The fields are delivered only to webhook subscriptions on an API version that includes the change; when a transfer’s source or destination is not a Location (for example, supplier-fulfilled transfers), the keys are omitted rather than returned as null.
Introduction
Shopify updated its inventory transfer webhooks and corresponding GraphQL mutations to provide clearer, more actionable information to developers. The most notable addition is the inclusion of origin.id and destination.id as Location Global IDs in several inventory transfer webhook topics. This change removes a common friction point for apps that need to reconcile where stock is coming from and going to, because they no longer have to make an extra API call to fetch the transfer to learn the endpoints.
Alongside the webhook payload enhancement, Shopify has rewritten portions of the documentation for two inventory transfer mutations — inventoryTransferSetItems and inventoryTransferRemoveItems — to match actual behavior, and it clarified the text of several error messages returned by inventory transfer mutations. Those error codes remain the same, so existing error handling logic will continue to function, but the updated messages provide more context so engineers can resolve issues faster.
This guide explains the change in detail, shows how to adopt it safely, walks through concrete payload and mutation examples, and outlines testing and migration strategies to avoid surprises. Practical scenarios illustrate how the new fields and clarified behaviors affect inventory routing, allocation, and reconciliation workflows.
What changed: origin.id and destination.id in inventory transfer webhooks
Shopify now includes origin.id and destination.id in the payloads for a set of inventory transfer webhook topics. These fields are Location Global IDs with the standard Shopify format — for example:
- origin.id: gid://shopify/Location/123
- destination.id: gid://shopify/Location/456
The webhook topics that include the new fields are:
- inventory_transfers/add_items
- inventory_transfers/update_item_quantities
- inventory_transfers/remove_items
- inventory_transfers/ready_to_ship
- inventory_transfers/cancel
- inventory_transfers/complete
Why this matters: before this change, webhook consumers that needed to determine the specific origin and destination locations had to retrieve the transfer object with an API call. That additional request introduced latency and complexity—especially for services that process high volumes of transfers or need to update downstream systems (warehouses, ERPs, fulfillment middleware) in near-real time.
Now, when you receive one of the affected webhook topics, you can inspect origin.id and destination.id directly in the payload and immediately route, log, or reconcile inventory movement without an extra transfer fetch.
Important nuance: the new fields are included only when the subscription’s webhook API version includes the change. If the subscription is on an earlier version, or if the transfer’s source or destination is not a Location (for instance, a supplier-fulfilled transfer or an external endpoint), the keys are omitted from the payload entirely rather than returned with null values. This design avoids ambiguity between “no value” and “not applicable.”
Practical example:
- A logistics service receives an inventory_transfers/ready_to_ship webhook. The payload contains:
- origin.id: gid://shopify/Location/123
- destination.id: gid://shopify/Location/789 The service immediately schedules pickup from location 123 and prepares staging at location 789, with no need to call the Admin API to fetch the transfer record.
Understanding the Location Global ID (GID) and how to use it
Shopify encodes resource identifiers as GraphQL Global IDs, which follow the pattern gid://shopify/<Resource>/<id>. The inclusion of origin.id and destination.id as GIDs means:
- You can identify the exact Location resource that serves as the origin or destination.
- GIDs are stable within Shopify’s GraphQL/REST context and can be used to query other APIs (for example, to fetch location name, address, or inventory presence).
- GIDs let you avoid a transfer fetch when all you need is location identity for routing or mapping to an internal warehouse code.
Using the GID:
- If your system stores Shopify location IDs as numeric integers (for example, 123), you should expect to either accept the GID string directly or parse the numeric ID out of it. The numeric portion is the canonical Location ID but keep GID handling robust to handle future format changes.
- When issuing further calls to Shopify’s GraphQL API that expect a Location identifier, you can pass the GID directly in place of an id argument.
Example: extracting the numeric ID from a GID (pseudocode)
function extractLocationNumericId(gid) {
// Expected format: "gid://shopify/Location/<id>"
return gid.split("/").pop()
}
Best practice: prefer storing the full GID where possible. If you need to map it to internal warehouse identifiers, maintain a mapping table keyed by the full GID. This avoids brittle parsing and keeps your system aligned with Shopify’s resource addressing.
When origin.id and destination.id will not appear in payloads
Two scenarios cause omission of the new keys:
- Subscription API version
- If the webhook subscription is pinned to an API version earlier than the one containing this change, the payloads will not include the new fields. Developers must confirm that their webhook subscriptions are using an API version that includes the enhancement. Many Shopify hosted webhook subscription tools default to a stable API version; altering the subscription version requires administrative action or recreation of the subscription with the desired API version.
- Non-Location sources or destinations
- Some transfers do not have a source or destination represented by a Shopify Location resource. Supplier-fulfilled transfers or transfers involving external vendors might use different internal constructs. For those transfers, origin.id and destination.id are omitted entirely. The keys are not present and are not returned as null.
Recommended consumer behavior:
- Always defensively check for the presence of origin.id and destination.id before using them.
- Implement a fallback path that fetches the full transfer via the Admin API if the keys are absent and your business logic requires the endpoint details.
- Log the absence of the fields for monitoring and to capture whether such cases are common for a given shop.
Example defensive pseudocode for webhook consumer:
payload = receiveWebhook()
if (payload.origin && payload.origin.id) {
handleOrigin(payload.origin.id)
} else {
// Optional: fetch transfer only if necessary
transfer = fetchTransfer(payload.transferId)
handleOrigin(transfer.origin)
}
inventoryTransferSetItems: clarified behavior and practical implications
Shopify clarified the behavior of inventoryTransferSetItems. The key points are:
- inventoryTransferSetItems is an upsert operation:
- Only the inventory items included in lineItems are affected.
- Line items already on the transfer that are not referenced remain unchanged.
- Each inventoryItemId can appear at most once per call.
- On READY_TO_SHIP or IN_PROGRESS transfers:
- The quantity you pass replaces only the processableQuantity portion of the line item.
- Already-shipped or already-picked quantities are preserved.
- The resulting total on the transfer equals preserved quantity (shipped/picked) plus the provided processable quantity.
- quantity: 0 is valid only on DRAFT transfers:
- On a DRAFT transfer, passing quantity: 0 leaves a zero-quantity line item on the transfer (you can use this to create a placeholder).
- To remove a line item entirely, call inventoryTransferRemoveItems.
- On READY_TO_SHIP or IN_PROGRESS transfers, submitting quantity: 0 returns an INVALID_QUANTITY error.
Interpretation and examples
Upsert semantics
- Suppose transfer T has two line items:
- Item A: inventoryItemId 100, quantity 10
- Item B: inventoryItemId 200, quantity 5
- You call inventoryTransferSetItems on transfer T with lineItems containing only Item A and a new quantity of 8.
- Result: Item A quantity becomes 8 (subject to processable quantity rules). Item B remains unchanged at quantity 5.
Processable vs. preserved quantities
- Transfer T (READY_TO_SHIP) had Item C:
- Allocated/picked/shipped amount: 3
- ProcessableQuantity (the amount that can still be processed): 7
- If you call inventoryTransferSetItems with quantity 10 for Item C, the mutation replaces the processableQuantity with 10, preserving the 3 already shipped/picked. The transfer’s resulting total becomes 3 + 10 = 13.
Zero quantity behavior
- DRAFT transfer scenario:
- You may want a placeholder line item with zero quantity (e.g., for a future addition or a reserved SKU). Setting quantity: 0 on DRAFT is allowed and keeps the line item present.
- READY_TO_SHIP or IN_PROGRESS:
- Attempting to set quantity: 0 will return INVALID_QUANTITY.
- To remove items, invoke inventoryTransferRemoveItems instead.
Practical developer guidance
- Treat inventoryTransferSetItems as idempotent for the items you pass, but remember it only affects those items.
- When updating quantities for an item on a transfer that is beyond DRAFT, calculate desired processableQuantity by subtracting already shipped/picked quantities, then pass the computed number. This avoids accidental over- or under-allocation.
- Instrument logging to capture whether SetItems calls are being made for transfers in different statuses, and record the preserved vs. replaced quantities for easier debugging.
GraphQL mutation example (conceptual)
mutation {
inventoryTransferSetItems(
transferId: "gid://shopify/InventoryTransfer/999",
lineItems: [
{ inventoryItemId: "gid://shopify/InventoryItem/100", quantity: 8 }
]
) {
inventoryTransfer {
id
lineItems {
inventoryItem {
id
}
quantity
}
}
userErrors {
field
message
code
}
}
}
inventoryTransferRemoveItems: clarified behavior and consequences
The documentation clarifications for inventoryTransferRemoveItems tighten expectations around item removal, allocation preservation, and status constraints:
Key behaviors:
- The mutation can be called on transfers in DRAFT or READY_TO_SHIP status.
- For each referenced line item:
- If the full quantity is still unallocated to a shipment, the line item is removed entirely from the transfer.
- If some or all of the line item’s quantity is already allocated to a shipment (including draft shipments that have been picked), the line item remains on the transfer with the quantity reduced to the allocated portion.
- Quantity allocated to a shipment — whether the shipment is still a draft, in transit, or already received — is preserved.
- On READY_TO_SHIP transfers, removing items returns the affected reserved quantity to available inventory at the origin location.
- Passing an omitted or empty transferLineItemIds is treated as a no-op and returns the transfer unchanged.
- To change the quantity of a line item without removing it, use inventoryTransferSetItems.
Examples to clarify behavior
Scenario A — Unallocated line item removal:
- Transfer T (DRAFT) contains Item X quantity 6. No shipments created. inventoryTransferRemoveItems references Item X.
- Result: Item X is removed from the transfer.
Scenario B — Partially allocated line item:
- Transfer T (READY_TO_SHIP) contains Item Y quantity 10.
- Allocated to Shipment S: 4 (picked)
- inventoryTransferRemoveItems references Item Y.
- Result: Item Y remains on transfer with quantity 4 (the allocated portion preserved). The 6 unallocated units are removed from the transfer. Additionally, on READY_TO_SHIP the 6 units are returned to available inventory at the origin location.
Scenario C — Removing items on READY_TO_SHIP returns reserved inventory:
- Removing unallocated quantity will update inventory levels by returning reserved units to available stock at origin. Systems that track both reserved and available inventory should subscribe to inventory level updates or reconcile shortly after a RemoveItems call.
No-op behavior for empty input
- Passing transferLineItemIds omitted or empty results in no changes. This clarifies that a mistaken empty payload will not clear a transfer accidentally.
Practical guidance
- Before calling inventoryTransferRemoveItems, query shipments associated with the transfer if your business logic requires knowledge of allocation status. Alternatively, rely on the mutation’s preserved allocation semantics to avoid unintentionally affecting shipped quantities.
- Consider race conditions: if a shipment is created concurrently with a remove request, ensure your application handles potential errors or uses retry logic if the remove fails due to allocation changes.
- Monitor inventory levels after removing items from READY_TO_SHIP transfers to capture the returned reserved quantity in your downstream systems.
GraphQL mutation example (conceptual)
mutation {
inventoryTransferRemoveItems(
transferId: "gid://shopify/InventoryTransfer/999",
transferLineItemIds: ["gid://shopify/InventoryTransferLineItem/abc"]
) {
inventoryTransfer {
id
lineItems {
id
quantity
}
}
userErrors {
field
message
code
}
}
}
Clearer error messages: what changed and how to respond
Shopify updated the textual content of several user-facing error messages returned by inventory transfer mutations. The underlying error codes remain unchanged so existing error-handling that checks codes continues to work. The new messages give engineers more context immediately, reducing the need to consult documentation when addressing failures.
Updated messages and how to act on them:
- READY_TO_SHIP_TRANSFER_REQUIRES_AT_LEAST_ONE_ITEM
- New message explains that you cannot remove every line item from a READY_TO_SHIP transfer and suggests canceling the transfer if you intend to empty it.
- Action: If you attempt to remove all items from a READY_TO_SHIP transfer, either cancel the transfer (if your workflow allows) or leave at least one line item. Explicitly handle this error by prompting the user or falling back to a cancel action where appropriate.
- ITEM_FULLY_SHIPPED
- New message clarifies that the error triggers when the full quantity of the item is allocated to one or more shipments, including draft shipments where the item has been picked. The name references an allocation check rather than a physical shipment.
- Action: Treat this as an allocation block rather than strictly a delivered shipment. If your workflow requires changing quantities, ensure you unallocate from shipments where appropriate (for example, cancel or adjust draft shipments) before attempting mutations that would otherwise conflict.
- ITEM_PRESENT_ON_DRAFT_SHIPMENT_WITH_ZERO_QUANTITY
- New message states plainly that the line item appears on a draft shipment with quantity 0.
- Action: Investigate the draft shipment to determine why quantity is zero and whether it should be updated to a positive quantity or removed. This error helps diagnose mismatched expectations between transfer line items and shipments.
Developer workflow recommendations
- If your app surfaces mutation errors to users, present the updated message verbatim when useful; it often gives the next step.
- Keep existing error-code checks in place. Use the message for logging and guidance, but do not rely on exact message text for programmatic control (the message could change in future).
- Add retry logic where appropriate, especially around concurrent operations that might trigger allocation-related errors.
Migration and compatibility: adopting the change with minimal risk
The new webhook fields and clarified mutation behavior are additive and non-breaking, but proper adoption requires attention to webhook API versions and defensive handling of absent keys.
Step-by-step migration checklist:
- Confirm which webhook subscriptions your app has and which API version they use.
- If a subscription is on an older API version, update or recreate it to target a version that includes the origin/destination fields.
- Update webhook parsing logic to handle origin.id and destination.id when present, and to fall back when absent.
- Example: if the keys are absent, only fetch the transfer when needed.
- Review existing inventoryTransferSetItems and inventoryTransferRemoveItems calls:
- Ensure SetItems calls treat the mutation as an upsert operating only on supplied items.
- For READY_TO_SHIP or IN_PROGRESS transfers, compute processableQuantity adjustments rather than assuming you set total quantities.
- Ensure RemoveItems calls account for preserved allocations and the no-op behavior when transferLineItemIds is empty.
- Add monitoring and logging:
- Log webhook payload differences for a period to measure how many transfers omit origin/destination.
- Capture userErrors returned from transfer mutations and correlate them with operations to streamline user guidance.
- Test edge cases:
- Supplier-fulfilled transfers or non-Location transfers that omit the keys.
- Concurrent operations that create shipments and mutate transfers simultaneously.
- Removing items that are partially or fully allocated to shipments.
- Update documentation and internal runbooks:
- Document the expected behavior for each transfer status and the recommended steps when encountering specific userErrors.
Compatibility considerations
- Because error codes are unchanged, code paths that act on codes will continue to behave. However, if your app previously parsed the English error message to infer next steps, switch to a code-based approach or verify against the new messages.
- Some shops will continue to produce payloads without origin/destination if they operate older webhook subscriptions or use transfer types that do not map to Locations. Prepare for mixed environments.
Real-world scenarios and worked examples
Scenario 1 — Third-party logistics (3PL) service routing A 3PL receives inventory_transfers/ready_to_ship webhooks from multiple merchants. Before this update, the 3PL fetched each transfer to decide which warehouse to pick from and which to deliver to.
- Before: webhook -> fetch transfer -> examine transfer.origin/destination -> route work order.
- After: webhook contains origin.id and destination.id -> route work order immediately.
Benefit: significant reduction in latency and fewer API calls per transfer, especially at scale. The 3PL can keep a local mapping of Shopify Location GIDs to their internal warehouse IDs and kick off pick tasks immediately.
Scenario 2 — ERP reconciliation with partial shipments An ERP receives a remove_items webhook for a transfer in READY_TO_SHIP. It uses inventoryTransferRemoveItems to remove unallocated quantities. Some of the transfer’s items were already allocated to draft shipments.
Behavior:
- RemoveItems preserves allocated quantities and removes only the unallocated portion.
- The ERP receives a webhook showing the new transfer line items (with smaller quantity) and must reconcile its purchase order and allocated shipments accordingly.
- The returned reserved quantity gets added back to available inventory at the origin location, so the ERP should listen for inventory level updates or query inventory levels after the mutation to update on-hand values.
Scenario 3 — Supplier-fulfilled transfers without Location endpoints A merchant uses supplier-fulfilled transfers where the supplier is not represented as a Shopify Location. For these transfers origin.id/destination.id are omitted.
Approach:
- The consuming application must fall back to a transfer fetch if it needs origin/destination details for special processing.
- Alternatively, the application can route supplier-fulfilled transfers via default supplier workflows without attempting to map to internal warehouse codes.
Scenario 4 — Preventing accidental emptying of READY_TO_SHIP transfer A merchant’s UI attempted to remove all line items from a READY_TO_SHIP transfer, expecting the transfer to become empty. The mutation returns READY_TO_SHIP_TRANSFER_REQUIRES_AT_LEAST_ONE_ITEM.
Correct resolution:
- If the merchant intends to cancel the transfer, call the cancel mutation.
- If they intended to remove specific items and leave at least one, ensure the UI enforces that at least one line item remains before issuing RemoveItems.
These scenarios illustrate how the updated payloads and clarifications reduce ambiguity and how developers should adapt to maintain correctness.
Testing and validation strategies
Comprehensive testing reduces the risk of inventory inconsistencies when you change webhook handling or mutate transfers.
- Local/Dev Store Testing
- Create a test shop or development store to subscribe to the unstable API version that already includes the change. Trigger transfers across multiple locations and observe webhook payloads and mutation behaviors.
- Include supplier-fulfilled transfer variants to verify omission behavior.
- Automated integration tests
- Write tests that simulate receiving each webhook topic with both included and omitted origin/destination fields. Verify your code handles both cases gracefully.
- Simulate concurrent operations: create a shipment and perform SetItems or RemoveItems concurrently to see whether your application handles userErrors and retries.
- Mutation contracts
- Create tests that assert SetItems upsert semantics. For a given transfer, verify that only passed items change and that preserved shipped quantities are not overwritten.
- Test RemoveItems behavior against partially allocated items to confirm allocations remain intact.
- Error handling tests
- Inject error scenarios to ensure that your system surfaces meaningful messages to operators and implements recommended fallbacks (e.g., cancel transfer vs. leave an item).
- Monitoring and observability
- Add metrics for webhook processing failures, number of fallback transfer fetches due to omitted fields, and inventory level changes after RemoveItems events. Track trends during and after rollout.
- Staged rollout
- For applications in production, rollout webhook subscription version changes in a staged manner — perhaps per merchant or per region — to limit blast radius and monitor behavior.
Edge cases and concurrency concerns
Edge cases:
- Draft shipments with zero quantities: the update to ITEM_PRESENT_ON_DRAFT_SHIPMENT_WITH_ZERO_QUANTITY helps diagnose when a line item appears as zero on a draft shipment. Check draft shipment contents before mutating transfers that involve those items.
- Transfers whose source/destination are external systems: expect origin/destination omitted; use supplementary logic or transfer fetch.
- Quantity zero on non-DRAFT transfers: attempting to set quantity: 0 on READY_TO_SHIP or IN_PROGRESS raises INVALID_QUANTITY.
Concurrency concerns:
- Race between shipment creation and RemoveItems: If a shipment is created and picks items while an app calls RemoveItems for the same transfer, the RemoveItems action will preserve allocations. Your code should handle failed mutations gracefully and reconcile after retries.
- Simultaneous SetItems calls: Because SetItems is an upsert limited to passed items, two parallel calls that affect overlapping line items must be coordinated to avoid conflicting changes. Use optimistic locking patterns where feasible, and check returned userErrors.
Mitigation strategies:
- Apply optimistic retries for transient errors and re-fetch transfer state when receiving allocation-related userErrors.
- Serialize operations against a given transfer when business-critical operations require strict sequencing. Use a queue keyed with the transfer ID.
- For high-throughput consumers, use idempotency tokens to prevent duplicate processing of webhook payloads and to make SetItems/RemoveItems calls resilient.
Best practices for robust webhook consumers
Design webhook consumers to be resilient, efficient, and observant of API versioning and payload variance.
- Validate payload schema but remain permissive
- Accept additional fields and tolerate missing optional keys like origin.id/destination.id.
- Log warnings when expected keys are absent for monitoring.
- Favor the GID identity
- Store full GIDs and use them as canonical keys in mapping tables.
- Avoid brittle parsing into integers when not necessary.
- Minimize additional API calls
- Use origin.id/destination.id when present to avoid fetching the transfer.
- Only fetch the transfer when the payload lacks needed information or when you must read non-sent fields.
- Handle userErrors intelligently
- React to specific error codes but use the richer error messages to provide operators and users with concrete next steps.
- For READY_TO_SHIP_TRANSFER_REQUIRES_AT_LEAST_ONE_ITEM, surface a cancel option instead of attempting to remove all items.
- Conserve idempotency and auditability
- Log webhook IDs and processing results. Use idempotency keys for outbound mutations to avoid duplicate effects during retries.
- Reconcile inventory asynchronously
- Because RemoveItems on READY_TO_SHIP returns reserved inventory to origin, consumer systems should reconcile on-hand and reserved inventory in an eventual-consistent manner via inventory level events or polling.
- Secure webhook endpoints
- Authenticate and verify webhook signatures. Maintain replay protection and rate limits on your consumer endpoints.
- User-facing messaging
- When your app surfaces errors to merchant users, present actionable next steps derived from the new messages: cancel transfers that need to be emptied, check draft shipments for zero-quantity items, or unallocate items from shipments before attempting certain mutations.
Operational impact for merchants and apps
For merchants:
- Inventory and fulfillment visibility improves because partner apps and integrators can more quickly respond when transfers move between concrete locations.
- Merchant-facing UIs that allow users to remove items from transfers should prevent attempts to empty READY_TO_SHIP transfers and offer a cancel action.
For apps and partners:
- Apps that previously fetched transfers to determine origin/destination can eliminate those calls, reducing API usage and latency.
- Logistics and warehouse partners can integrate faster routing and more immediate fulfillment triggers.
- ERPs and reconciliation services must account for preserved allocations and the return of reserved inventory when items are removed from READY_TO_SHIP transfers.
Cost/benefit trade-offs:
- Reduced API call overhead and faster processing improve operational throughput, but apps must still handle cases without location IDs.
- The clarified mutation behaviors reduce ambiguity and support safer inventory operations, but they introduce the need to consider preserved allocations in business logic.
Implementation checklist and sample code patterns
Checklist for implementing support:
- Verify webhook subscription API versions and update as needed.
- Update webhook parsing to read origin.id and destination.id, with safe fallbacks.
- Audit SetItems and RemoveItems usage and update logic to match clarified behaviors.
- Add monitoring for missing keys and for userErrors returned by transfer mutations.
- Extend integration tests for the new semantics and edge cases.
Sample webhook processing flow (conceptual pseudocode):
function handleInventoryTransferWebhook(payload) {
const transferId = payload.transferId
const originGid = payload.origin && payload.origin.id
const destinationGid = payload.destination && payload.destination.id
if (originGid && destinationGid) {
routeToWarehouse(originGid, destinationGid, transferId)
} else {
// Only fetch transfer when necessary
const transfer = fetchTransfer(transferId)
routeToWarehouse(transfer.origin.id, transfer.destination.id, transferId)
}
}
Sample SetItems call accounting for preserved shipped quantity:
function setProcessableQuantity(transferId, inventoryItemId, desiredTotal) {
const transfer = fetchTransfer(transferId)
const lineItem = transfer.lineItems.find(li => li.inventoryItemId === inventoryItemId)
const alreadyShipped = lineItem.shippedQuantity || 0
const processableDesired = Math.max(0, desiredTotal - alreadyShipped)
// For non-DRAFT transfers, ensure processableDesired > 0
if (transfer.status !== 'DRAFT' && processableDesired === 0) {
throw new Error('Cannot set processable quantity to 0 on non-DRAFT transfers')
}
callInventoryTransferSetItems(transferId, [{ inventoryItemId, quantity: processableDesired }])
}
Sample RemoveItems handling preserving allocations:
function removeUnallocatedItems(transferId, transferLineItemIds) {
const response = callInventoryTransferRemoveItems(transferId, transferLineItemIds)
const updatedTransfer = response.inventoryTransfer
// updatedTransfer's line items will contain any allocated quantities that were preserved
reconcileAllocations(updatedTransfer)
}
Monitoring and observability recommendations
To operate reliably with the changes, enhance monitoring in the following areas:
- Webhook payload variance:
- Track how often origin.id or destination.id are present vs. absent per merchant to identify shops using supplier transfers or older subscription versions.
- API call reductions:
- Measure the reduction in transfer fetch calls and the latency improvement from routing actions triggered directly from webhook payloads.
- Mutation userErrors:
- Log occurrences of READY_TO_SHIP_TRANSFER_REQUIRES_AT_LEAST_ONE_ITEM, ITEM_FULLY_SHIPPED, and ITEM_PRESENT_ON_DRAFT_SHIPMENT_WITH_ZERO_QUANTITY. Correlate these with user actions or concurrent operations.
- Inventory reconciliation:
- Monitor inventory adjustments after RemoveItems operations to ensure your systems reflect returned reserved quantities.
- Error spikes during migration:
- When updating webhook subscription versions across merchants, watch for spikes in errors or fallback transfer fetches that indicate unhandled cases.
Instrumenting these metrics will make it easier to detect regressions and tune retry and fallback strategies.
Recommendations and next steps for teams
- Update webhook subscriptions: ensure subscriptions are on an API version that includes origin.id and destination.id to benefit from immediate location visibility.
- Harden webhook handlers: implement safe fallbacks for omitted fields and avoid extra transfer fetches whenever possible.
- Review mutation use: ensure your SetItems and RemoveItems calls align with the clarified semantics, especially for READY_TO_SHIP and IN_PROGRESS transfers.
- Add tests and observability: build integration tests for allocation-preserving behaviors and add metrics around webhook variance and mutation userErrors.
- Update user interfaces: if merchants interact with transfers through your app, show clear guidance when a transfer cannot be emptied (suggest cancel) and when items are preserved due to allocations.
Implementing these changes and recommendations will reduce API usage, accelerate routing actions, and improve the clarity of inventory transfer operations across your systems.
FAQ
Q: Which webhook topics include the new origin.id and destination.id fields? A: The new fields appear in payloads for inventory_transfers/add_items, inventory_transfers/update_item_quantities, inventory_transfers/remove_items, inventory_transfers/ready_to_ship, inventory_transfers/cancel, and inventory_transfers/complete—provided the webhook subscription uses an API version that includes the change.
Q: Are origin.id and destination.id always present in transfer webhooks? A: No. They are present only for transfers whose source and destination are Shopify Location resources and only for subscriptions on an API version that includes the change. If the transfer’s source or destination is not a Location (for example, supplier-fulfilled transfers), the keys are omitted entirely. If the subscription is on an earlier API version, the keys are also omitted.
Q: Do I still need to fetch the transfer after receiving a webhook? A: Only when the webhook payload omits origin.id or destination.id and your business logic requires endpoint details, or when you need fields not included in the webhook payload. When origin.id and destination.id are present, you can proceed without an additional transfer fetch.
Q: What does it mean that inventoryTransferSetItems is an upsert? A: Upsert means the mutation affects only the inventory items you include in the call. Line items on the transfer that you do not reference remain unchanged. For READY_TO_SHIP or IN_PROGRESS transfers, the quantity you pass replaces only the processableQuantity (the portion that can still be shipped or picked), preserving already-shipped or picked quantities.
Q: Can I set quantity to 0 for line items using inventoryTransferSetItems? A: You can set quantity: 0 only on DRAFT transfers; this leaves a zero-quantity line item on the transfer. On READY_TO_SHIP or IN_PROGRESS transfers, setting quantity: 0 will return an INVALID_QUANTITY error. To remove a line item entirely, use inventoryTransferRemoveItems.
Q: What happens when I call inventoryTransferRemoveItems on an item that’s partially allocated to a shipment? A: The mutation preserves any portion of the line item that is allocated to a shipment; the unallocated portion is removed. On READY_TO_SHIP transfers, the removed reserved quantity is returned to available inventory at the origin location.
Q: I’m seeing READY_TO_SHIP_TRANSFER_REQUIRES_AT_LEAST_ONE_ITEM. What should I do? A: This error means you attempted to remove every line item from a READY_TO_SHIP transfer. You cannot empty a READY_TO_SHIP transfer by removing items; instead, cancel the transfer if your intention is to abort it.
Q: The ITEM_FULLY_SHIPPED error looks like shipment-delivery, but items haven’t left the warehouse. Why did this occur? A: The error refers to allocation checks. It triggers when the full quantity of the item has been allocated to one or more shipments — including draft shipments where items may have been picked. Investigate shipment allocations to see where the item is reserved, and unallocate if changes are necessary.
Q: Have error codes changed? A: No. Only the textual messages were clarified to make them more actionable. Existing handling that relies on error codes continues to work.
Q: How should I test these changes in my environment? A: Use a development or test store and create webhook subscriptions on the API version that includes the change. Write integration tests that simulate webhooks with and without origin/destination fields, exercise SetItems and RemoveItems across DRAFT, READY_TO_SHIP, and IN_PROGRESS statuses, and simulate concurrent manipulations to verify error handling.
Q: Will including origin.id and destination.id reduce my API usage? A: Yes. When those fields are present, you no longer need to fetch the transfer solely to determine origin and destination, which reduces Admin API calls and speeds up downstream actions.
Q: Are there any security implications for processing the new fields? A: The same webhook verification and signature validation best practices apply. Treat the GID values as identifiers only; do not assume any privileged information. If you must fetch additional location details, ensure your app has the necessary access scope.
Q: Where can I find more details about the inventory transfer webhook and mutations? A: Consult Shopify’s Inventory transfer webhook reference and the GraphQL Admin API documentation for inventory transfer mutations. Review the API versioning documentation to understand webhook subscription versioning and migration pathways.