Tracking & Fulfillment
Once Riverr ships an order, carrier and tracking information becomes available through the API. This guide explains where that data lives, how to poll for it, and how it relates to the shipping options you set when creating the order.
Tracking lives on Shipment, not on the order's own fields
There is no trackingCode field directly on Order or OrderItem.
Fulfillment tracking is exposed through the Shipment type. You can read it two
ways:
getShipment(orderId)— the most recent shipment for one order.Order.shipments— every shipment for an order, returned inline ongetOrder/getOrders(best for split shipments, no extra round-trip).
query GetTracking($orderId: ID!) {
getShipment(orderId: $orderId) {
id
orderId
trackingCode
trackingUrl
carrierName
carrierService
cost
currency
platformShipmentId
shippedAt { _seconds _nanoseconds }
}
}
This is the same carrier + tracking record Riverr pushes back to your
connected storefront when an order ships, so reading it gives you exactly what
your buyer sees. trackingUrl is a ready-to-click carrier link — usually the
cleanest value to surface to a buyer.
null until the order ships — poll for it
getShipment returns null while the order has not shipped yet (no
shipment record exists). This is a normal, successful response — not an error.
Poll the order until you get a non-null Shipment:
{ "data": { "getShipment": null } }
A typical integration polls open orders on an interval (e.g. every few minutes
to hourly, depending on volume) and stops once getShipment returns a non-null
object with a trackingCode.
null from an errorA null payload under data.getShipment means "not shipped yet." If instead
the response contains an errors array, that is a real failure (e.g. the order
isn't yours, or the order record is missing) — see
Errors. Don't treat an error as
"not shipped."
Tracking fields can be null even after shipping
The tracking fields are nullable. A shipment record can exist before a label
is purchased (for example, a manual shipment created for a non-label-buying
enterprise), so trackingCode, carrierName, and carrierService may each be
null on a shipment that otherwise exists. Treat them as optional and keep
polling until trackingCode is populated if you need the number.
| Field | Type | Notes |
|---|---|---|
id | ID! | Shipment record id |
orderId | String! | The order this shipment belongs to |
trackingCode | String | Carrier tracking number. Nullable until a label is assigned. |
trackingUrl | String | Direct carrier tracking link (AfterShip fallback). Nullable. |
carrierName | String | Actual carrier used (e.g. "USPS"). Nullable. |
carrierService | String | Actual service level used. Nullable. |
cost | Float | Shipping cost for this shipment (may be 0 for manual shipments). |
currency | String | ISO currency code for cost. |
platformShipmentId | String | Carrier/platform shipment identifier. |
shippedAt | Timestamp | When the shipment was created/shipped (alias of createdAt). |
createdAt | Timestamp | When the shipment record was created. |
Orders that ship in multiple parcels (split shipments)
A single order can ship in more than one parcel — Riverr writes one shipment
record per partial shipment. getShipment(orderId) returns only the most
recent one. To get every parcel for an order in a single call, select
shipments on the order:
query GetOrderWithTracking($id: ID!) {
getOrder(id: $id) {
id
status
shipped
shipments {
trackingCode
trackingUrl
carrierName
shippedAt { _seconds _nanoseconds }
}
}
}
Listing shipments across orders with getShipments
getShipments returns shipments scoped to the orders you own. Call it with a
shopId (must be one of your shops) or with no argument to span all your shops:
query { getShipments(shopId: "shop_123", limit: 50) { orderId trackingCode trackingUrl carrierName } }
Results are newest-first. limit caps the count; orderIds narrows to specific
orders (only those you own are returned).
The status and carrier arguments on getShipments are accepted but not
yet implemented — they are ignored today. Filter client-side until this notice
is removed. See Shipments.
Order status
Alongside tracking, the order carries a coarse, derived status
("production" | "unsynced" | "shipped" | "cancelled") and finer statusTags,
so you can poll lifecycle without inspecting individual flags. See
Orders for the full status model.
Relation to ShippingOptionsInput
Don't confuse the requested shipping method with the actual carrier that shipped the order:
- At order creation you may pass
shippingOptions(aShippingOptionsInput) oncreateOrder/createOrderFromGtin. These are ShipStation-style codes —carrierCode,serviceCode,packageCode— describing the service you'd like used. They are persisted on the order and echoed back asOrder.shippingOptions. - After the order ships the real carrier is reported on the
Shipmentas human-readablecarrierName/carrierService, plus thetrackingCode.
The codes you send (carrierCode/serviceCode) are ShipStation identifiers and
are a different namespace from the carrierName/carrierService you later
read on the Shipment — match orders to shipments by orderId, not by carrier
string.
Recommended fulfillment loop
- Create the order (optionally with
shippingOptions). - Poll
getShipment(orderId)(orgetOrder { status shipments { ... } }) on an interval. - While
getShipmentisnull(orstatusis notshipped), keep polling. - When a
Shipmentappears, readtrackingCode/trackingUrl/carrierName. IftrackingCodeis stillnull, the label isn't bought yet — keep polling for it. - Surface the tracking to your buyer (it already matches what Riverr pushed to the connected store).