Skip to content

Fulfilment Lifecycle

Last updated October 20, 2026

Everything the API does exists to serve one loop. Read this section before the endpoint reference — it explains why the endpoints are shaped the way they are.


The flow, step by step

1. An order becomes actionable. As soon as you can see an order, the fulfilment_order.created webhook fires with the full order payload: shipping address, line items, and the shipment and parcels Omnishelf has created for it. Every order always carries at least one shipment with one parcel, even before a label or tracking number exists.

If you are not using webhooks, poll GET /v1/fulfilment-orders instead — it returns awaiting_shipment and processing orders by default.

2. Fetch the labels for its parcels. Call GET /v1/fulfilment-orders/{order_id}/labels for everything on the order in one request, or drop to shipment or parcel level. Labels come back as base64-encoded PDFs alongside the parcel_id and tracking_number they belong to, so the right label always lands on the right box.

3. Pack and hand the boxes to the carrier. No API involvement. Omnishelf learns nothing until a scan happens or you tell it.

4. Submit tracking numbers back to Omnishelf. If Omnishelf did not supply the labels — you bought your own postage, for instance — post the tracking numbers to POST /v1/fulfilment-orders/{order_id}/tracking. Omnishelf creates the shipment and parcel records, writes through to the fulfilment system, and recomputes order status itself.

5. Poll tracking status until delivered. GET /v1/orders/{order_id}/tracking returns the order rollup plus every shipment and parcel with its carrier status. Poll on a sensible interval — the 100 requests/minute limit is per key, not per order.


What Omnishelf does and does not do for you

The single most common integration mistake is assuming you control order status. You do not, and this is deliberate. Order status is derived from the tracking status of the parcels on the order — Omnishelf recomputes it automatically each time a parcel's carrier tracking status changes, and rolls the parcel statuses up into a single order status (see How statuses roll up).

Omnishelf does Omnishelf does not
Compute order status from the tracking status of each parcel, every time a carrier update is received. Accept a status value from you. There is no endpoint that sets order status directly.
Create shipment and parcel records when you submit tracking at order level. Move an order to shipped just because you submitted a tracking number. The order stays in processing until carrier tracking confirms movement.
Move an order to shipped only when the parcel tracking status shows a real carrier scan (in_transit or later). Treat pending or info_received as a carrier scan. Those are label-created placeholders, not actual carrier movement.
Push your tracking submission through to the fulfilment system before writing anything locally. Save a partial result. If the push fails, nothing is written and you can safely retry.

Webhooks start the work; polling finishes it

The dependable pattern is: react to fulfilment_order.created to begin fulfilment, then poll tracking for the orders you have in flight until each reaches delivered, exception, or cancelled. Do not wait for a completion event — there isn't one.

See Webhooks — What's not sent for the full list of transitions that emit no event.