Skip to navigation

Receiving

View as Markdown

Overview

A Warehouse Receiving Order (WRO) is how ShipBob manages inbound shipments into their fulfillment centers. Some other solutions call this an “ASN” (Advanced Ship Notice).

This guide will walk you through building an integration to send inventory to ShipBob and sync it back to your system.

For near real-time receiving updates, ShipBob webhooks are the preferred option. Use them to react to WRO lifecycle events as they happen, and keep polling as a fallback.


How It Works

Here’s the flow of creating and tracking a Warehouse Receiving Order:


Step 1: Check if Products Exist in ShipBob

Before creating a WRO, every product needs an inventory_id from ShipBob.

Check if a product exists:

GET https://api.shipbob.com/2026-07/product?sku=light-roast

If the product doesn’t exist, create it first:

POST https://api.shipbob.com/2026-07/product
{
"name": "Light Roast Coffee",
"type_id": 1,
"variants": [
{
"name": "Light Roast Coffee",
"sku": "light-roast",
"packaging_requirement_id": 1,
"packaging_material_type_id": 1,
"barcode": "06547321109",
"customs": {
"country_code_of_origin": "US",
"hs_tariff_code": "6103.22.00",
"value": "10",
"description": "coffee"
}
}
]
}
Save the inventory_id when you create products in your system. This way, you won’t need to look them up every time.

Step 2: Create a WRO

Once you have your inventory_id values, create the WRO:

POST https://api.shipbob.com/2026-07/receiving

Example request (sending packages):

{
"fulfillment_center": {
"id": 10
},
"package_type": "Package",
"box_packaging_type": "EverythingInOneBox",
"expected_arrival_date": "2025-01-15",
"purchase_order_number": "PO-2026-001",
"boxes": [
{
"tracking_number": "1Z999AA10123456784",
"box_items": [
{
"inventory_id": 12345678, // get the inventory_id by making request to GET Products /2026-07/product?sku=light-roast
"quantity": 50
}
]
}
]
}

What you’ll get back:

{
"id": 987654,
"status": "Awaiting",
"package_type": "Package",
"box_packaging_type": "EverythingInOneBox",
"expected_arrival_date": "2025-01-15T00:00:00+00:00",
"purchase_order_number": "PO-2026-001",
"box_labels_uri": "https://api.shipbob.com/2026-07/receiving/987654/labels"
// ... additional fields
}
Use the box_labels_uri from the response in the next step if you are fetching box labels.

Package types:

  • Package - For boxes (max 50 boxes per request)
  • Pallet - For palletized freight shipments (each pallet is a separate box)
  • FloorLoadedContainer - For container shipments (entire container as one box)

Creating WROs with Lot Items

If you’re sending lot-tracked items (products with expiration dates or batch numbers), you must include lot_date and lot_number for each item in your WRO.

Example request with lot items:

POST https://api.shipbob.com/2026-07/receiving
{
"fulfillment_center": { "id": 8 },
"package_type": "Package",
"box_packaging_type": "EverythingInOneBox",
"expected_arrival_date": "2025-12-20T00:00:00Z",
"purchase_order_number": "PO-LOT-001",
"boxes": [
{
"tracking_number": "1Z999AA10123456784",
"box_items": [
{
"inventory_id": 12345678,
"quantity": 50,
"lot_number": "2222",
"lot_date": "2025-06-15T00:00:00Z"
}
]
}
]
}

Key fields for lot items:

FieldRequiredDescription
lot_numberYesYour lot/batch identifier (e.g., “LOT-2222”, “BATCH-A1”)
lot_dateYesThe lot expiration date or manufacture date in ISO 8601 format
inventory_idYesThe ShipBob inventory ID for the product
quantityYesNumber of units in this lot

Both lot_number and lot_date are required for lot-tracked items. If you omit either field, the WRO creation will fail.

Make sure the product is already created in ShipBob and you have the inventory_id before creating the WRO. You can check if a product is lot-tracked by looking at the product details in the ShipBob dashboard or via the GET Product API.

Multiple lots in one box:

You can include multiple lots of the same product or different products in a single box:

{
"boxes": [
{
"tracking_number": "1Z999AA10123456784",
"box_items": [
{
"inventory_id": 12345678,
"quantity": 50,
"lot_number": "LOT-2222",
"lot_date": "2025-06-15T00:00:00Z"
},
{
"inventory_id": 12345678,
"quantity": 30,
"lot_number": "LOT-3333",
"lot_date": "2025-08-20T00:00:00Z"
},
{
"inventory_id": 98765432,
"quantity": 100,
"lot_number": "BATCH-A1",
"lot_date": "2026-01-10T00:00:00Z"
}
]
}
]
}

Step 3: Generate Box Labels (Optional)

Print box labels to help the warehouse identify your shipment:

GET https://api.shipbob.com/2026-07/receiving/{wro_id}/labels

This returns a PDF with labels for each box/pallet. Print and attach them to your shipment before sending.


ShipBob can fire webhooks for WRO lifecycle events, so you can process inbound updates as they happen instead of waiting for the polling loop.

Recommended WRO topics:

  • wro.created
  • wro.updated
  • wro.completed
  • wro.box.arrived
  • wro.box.scanned
  • wro.box.stowed

Typical handling workflow:

  1. Subscribe to WRO topics using Create Webhook Subscription.
  2. Validate webhook signatures.
  3. Update your WRO state machine when wro.updated arrives.
  4. Trigger inventory reconciliation after wro.completed and/or final wro.box.stowed events.
  5. Return 2XX quickly, then process asynchronously.
Use webhooks for real-time orchestration and polling only as a fallback if webhook delivery is delayed or unavailable.

Step 5: Poll for Completed WROs (Fallback)

Set up a recurring job (every 15-30 minutes) to check for completed WROs that need to be synced.

Check for completed WROs:

GET https://api.shipbob.com/2026-07/receiving?Statuses=Completed&ExternalSync=false

This returns all WROs that are:

  • Completed - The warehouse has finished processing them
  • Not synced - You haven’t updated your system yet (ExternalSync=false)

Here’s what to do with each completed WRO:

  1. Get the inventory quantities from the WRO
  2. Update your system with the new inventory levels
  3. Mark the WRO as synced so you don’t process it again

Example code:

// Run this every 15-30 minutes
async function checkForCompletedWROs() {
// 1. Get completed WROs that haven't been synced
const response = await fetch(
'https://api.shipbob.com/2026-07/receiving?Statuses=Completed&ExternalSync=false',
{
headers: { 'Authorization': 'Bearer YOUR_ACCESS_TOKEN' }
}
);
const wros = await response.json();
// 2. Process each WRO
for (const wro of wros) {
// Get the inventory quantities
wro.boxes.forEach(box => {
box.inventory.forEach(item => {
// Update your system with item.stowed_quantity
console.log(`Product ${item.inventory_id}: +${item.stowed_quantity} units`);
});
});
// 3. Mark as synced
await fetch(
`https://api.shipbob.com/2026-07/receiving:setExternalSync`,
{
method: 'POST',
headers: { 'Authorization': 'Bearer YOUR_ACCESS_TOKEN' },
body: JSON.stringify({ "ids": [987654], "is_external_sync": true })
}
);
}
}

Step 6: Mark WRO as Synced

After updating your inventory system, mark the WRO as synced to prevent duplicate processing:

POST https://api.shipbob.com/2026-07/receiving:setExternalSync
{
"ids": [987654],
"is_external_sync": true
}

This endpoint accepts an array of WRO IDs, allowing you to mark multiple WROs as synced in a single request.


Advanced: Track Individual Boxes

Want to see details for each box? Use this endpoint:

GET https://api.shipbob.com/2026-07/receiving/{wro_id}/boxes

This shows you the status and quantities for each box, which would enable you to do partial receiving.


Understanding the Inventory Quantities

When you sync a completed WRO, you’ll see three quantity fields:

Expected Quantity(what you sent) Received Quantity(what arrived) Stowed Quantity(added to inventory)
FieldWhat it means
expected_quantityWhat you said you were sending
received_quantityWhat actually arrived at the warehouse
stowed_quantityWhat was added to your available inventory
Always use stowed_quantity when updating your system. This may be less than expected if items were damaged or missing.

Example:

{
"inventory_id": 12345678,
"sku": "light-roast",
"expected_quantity": 50, // You sent 50 units
"received_quantity": 48, // Only 48 arrived
"stowed_quantity": 48 // 48 added to inventory
}

Important Fields

package_type - Determines how your shipment is configured. Choose based on your shipping method:

  • Package - Standard boxes shipped via carrier (max 50 per request)
  • Pallet - Palletized freight shipments (each pallet = one box)
  • FloorLoadedContainer - Full container shipments (entire container = one box)

expected_arrival_date - The date your shipment is expected to arrive at the fulfillment center. Must be set to a future date - using today’s date or a past date will result in an error.

purchase_order_number - Your internal PO number or shipment identifier. Use a unique value for each WRO to simplify tracking and reconciliation.

tracking_number - The carrier tracking number for each box or pallet. This enables the warehouse to scan and verify your shipment upon arrival.

is_external_sync - A boolean flag that tracks whether you’ve synced the WRO data back to your system:

  • Default value: false when the WRO is created
  • Query unsynced WROs: Use ExternalSync=false to find WROs that need processing
  • Mark as synced: Set to true after updating your inventory system
  • Purpose: Prevents duplicate processing of the same WRO

To mark one or more WROs as synced, use the batch endpoint:

POST https://api.shipbob.com/2026-07/receiving:setExternalSync
{
"ids": [987654],
"is_external_sync": true
}

WRO Status Flow

A WRO’s status is calculated from its boxes. Every status change starts with something physical happening to a box at the warehouse (scanned, counted, stowed). The WRO follows its least-advanced active box, and cancelled boxes are ignored. The one exception: if some boxes have arrived and others are still Awaiting, the WRO is PartiallyArrived.

Treat webhooks as a trigger, not as the data. On each event, call GET /receiving/{id} and GET /receiving/{id}/boxes for the authoritative current state.

Standard flow

Awaiting → PartiallyArrived → Arrived → Processing → Completed
Awaiting → Arrived (single-box WRO, or all boxes scanned together)
Awaiting → Cancelled (merchant cancels before anything arrives)
Current statusMoves toTrigger
AwaitingPartiallyArrivedAt least one box of a multi-box WRO is scanned in at the dock while at least one box is still awaiting
AwaitingArrivedEvery box is scanned in (a single-box WRO, or all boxes scanned together)
AwaitingCancelledThe merchant cancels the WRO before any box arrives, cancelling all boxes. Awaiting is the only status from which a WRO can be edited or cancelled
PartiallyArrivedArrivedThe last remaining box is scanned in
PartiallyArrivedCompleted (auto-close)Some boxes arrived but the rest never do within a set number of days. The missing (Awaiting) boxes are cancelled and the WRO is set to Completed
ArrivedProcessingEvery active box has started counting or stowing. The WRO stays Arrived until all active boxes are past Arrived, not just one
ProcessingCompletedEvery active box is fully stowed to a shelf. Cancelled boxes are ignored
Incomplete is a reserved status that is not set by any current flow. Do not build logic on it.

Hub flow

Applies when a WRO is received at a hub, then transferred to the destination fulfillment center (FC).

Awaiting → PartiallyArrivedAtHub → ArrivedAtHub → ProcessingAtHub
→ InternalTransfer → Arrived → Processing → Completed
Current statusMoves toTrigger
AwaitingPartiallyArrivedAtHubSome boxes are scanned in at the hub
AwaitingArrivedAtHubAll boxes are scanned in at the hub together
PartiallyArrivedAtHubArrivedAtHubThe remaining boxes are scanned in at the hub
ArrivedAtHubProcessingAtHubThe hub begins counting boxes and creating stow tags
ProcessingAtHubInternalTransferCounting at the hub is complete and inventory is in transit from the hub to the destination FC
InternalTransferArrivedTransfers reach the destination FC, ahead of stowing
ArrivedProcessing → CompletedThe destination FC stows the inventory. Same triggers as the standard flow

Box statuses

Returned by GET /receiving/{id}/boxes. These are the physical events that drive the WRO statuses above.

Awaiting → Arrived → Counting → Stowing → Completed (standard)
Awaiting → Arrived → Counting → InternalTransfer → Stowing → Completed (hub)
Awaiting → Cancelled
Current statusMoves toTrigger
AwaitingArrivedAn associate scans the box label or tracking number at the dock. Re-scanning an Arrived box is valid and does not change its status
ArrivedCountingThe first units in the box are counted (or a fast-track receive starts)
CountingStowingAll items are counted and stow tags are created (standard flow)
CountingInternalTransferAll items are counted at a hub; the box is waiting to move to the destination FC (hub flow)
InternalTransferStowingInventory reaches the destination FC and stowing begins (hub flow)
StowingCompletedAll inventory from the box has been put away to shelves
AwaitingCancelledThe WRO is cancelled, or the box is auto-closed after never arriving
Some fast-track flows skip Counting.

Webhooks that signal these triggers

TopicFires whenWhat to know
wro.createdA WRO is created (API or dashboard)Once per WRO
wro.box.arrivedA box is scanned in (box Awaiting → Arrived)Can fire more than once per box on re-scan
wro.box.stowedInventory from a box is put awayA box can produce several of these. It does not mean the box is finished; check for box status Completed
wro.completedThe WRO reaches CompletedSent when the last box finishes stowing
wro.updatedThe merchant edits a WRO before arrivalNot sent for warehouse status changes. Don’t use it to track receiving progress

There is no cancellation webhook, and not every WRO status change has its own webhook. Poll GET /receiving/{id} on a schedule to detect cancellations and status changes between events.

Tips for your automation

  • Don’t require every status to appear. Statuses can be skipped or compressed; fast-track receiving can move a WRO from Arrived to Processing within seconds.
  • Expect duplicate and out-of-order events. There is no ordering guarantee between WRO-level and box-level events. Make handlers idempotent, keyed on box ID and box status.
  • Reconcile on a schedule (for example, every 15–30 minutes) to catch cancellations and any missed events.
  • Use status_history[].timestamp for transition times, not webhook receipt time or last_updated_date. Entries can share a timestamp; sort by timestamp, then entry order.
  • Normalize status names. status returns names like PartiallyArrived, while status_history[].status returns labels with spaces, like Processing At Hub.
  • A new webhook subscription can take up to about 15 minutes to start receiving events.

For the complete list of statuses, see the Status Reference documentation.


Common Issues

Set expected_arrival_date to tomorrow or later. Don’t use today’s date or past dates.

Create the product first, then use the inventory_id it returns.

Contact ShipBob support with your WRO ID. The warehouse might be experiencing delays.

Make a request to the GET Fulfillment Center endpoint at /2026-07/fulfillment-center to get a list of fulfillment centers you have access to.


Tips

  • Poll every 15-30 minutes - Receiving can take hours or a few days depending on warehouse volume
  • Use unique PO numbers - Makes tracking and reconciliation easier
  • Always verify inventory_id exists - Prevents WRO creation failures
  • API rate limit - 150 requests per minute
  • Consider time zones - Dates are in UTC; convert appropriately