Order lifecycle
How a real order moves from created to completed, when you can still cancel it, and how to rehearse all of it with a test order.
How a real order moves
Every order you create starts in one of two states.
NEWis the normal start. The order sits in an acceptance window of about two hours from creation. During that window you can still cancel it withPOST /v1/orders/{orderId}/cancel: the merchant is refunded and you receiveorder.canceled. When the window closes, the order is accepted automatically and becomesACCEPTED. It cannot be canceled through the API after that.PROCESSINGis a rare start for an order that requires additional review. It moves toACCEPTEDonce the review is done, and the API cannot cancel it.
From ACCEPTED a warehouse takes over: shipments move the order to
IN_PROGRESS and then COMPLETED, and every step arrives as a webhook.
A hold (onHold) can land at any point, and it doesn't change the status.
Read onHoldReasons to see what has to change before the order can proceed.
If you might need to cancel, do it early. The window is measured from when the
order was created, and a cancel that arrives late gets
order-not-cancelable.
Rehearse it with a test order
A real order moves when a warehouse packs it, a carrier scans it or a payment
clears — none of which you can trigger from an API call. So a test order moves
because you say so: POST /v1/test/orders/{orderId}/transitions puts it in the
next state and publishes the event a real transition would have published.
Only test_ orders can be transitioned. A real order id answers 404, because
its lifecycle is not yours to drive.
A test order starts where a real one would: if a real order would require
additional review, the test order starts in PROCESSING, and cancelling it
gets the same 409 it would in production.
The states
NEW ──process──▶ PROCESSING ──accept──▶ ACCEPTED ──ship──▶ IN_PROGRESS ──complete──▶ COMPLETED
│
returnShipment ──▶ RETURNED
│
└─ hold ⇄ releaseHold cancel ──▶ CANCELED| Transition | What it does | Event |
|---|---|---|
process | Status to PROCESSING, as if the order had needed review. A real order only ever starts in PROCESSING, so rehearse your handling of it here | — |
accept | Status to ACCEPTED | — |
hold | Blocks the order. reasons defaults to ["out_of_stock"] | order.onHold |
releaseHold | Clears the block | order.onHoldReleased |
ship | Creates a shipment over the lines in lineIds, or everything unshipped | order.shipmentCreated |
returnShipment | Brings a parcel back. shipmentId defaults to the most recent one still out | order.shipmentReturned |
complete | Status to COMPLETED. Refused while a line has no shipment | order.completed |
cancel | Status to CANCELED from any unsettled state, as Supliful canceling would. To rehearse your own cancel, call POST /v1/orders/{orderId}/cancel instead: it applies the real NEW-only rule | order.canceled |
order.created arrives when you create the order, before any of this.
You do not have to walk the chain. Jump straight to the transition you want to exercise — only the rules below are enforced.
The rules are the ones the real system enforces, and the sandbox says no for the same reasons: a held order will not ship, an order with an unshipped line will not complete, and a settled order takes nothing further except a parcel coming back — which is exactly when returns happen.
Try it
Create a throwaway order against this connection and push it through. Every
event is delivered to your endpoints for real: signed, retried and recorded in
your delivery log, with testMode: true in the payload so nothing downstream
acts on it.
Sent straight to the API from your browser and never stored by this page.
By hand
# 1. Create it. The id comes back prefixed `test_`.
curl -X POST $API/v1/orders -H "Authorization: Bearer $KEY" \
-H 'content-type: application/json' \
-d '{"testMode":true,"externalOrderId":"demo-1",
"shippingAddress":{"firstName":"Jane","lastName":"Doe","address1":"1 Main St",
"city":"Austin","stateCode":"TX","zip":"78701","countryCode":"US"},
"lines":[{"productId":"<from GET /v1/products>","quantity":1}]}'
# 2. Move it. Repeat with the transitions you want to exercise.
curl -X POST $API/v1/test/orders/test_…/transitions -H "Authorization: Bearer $KEY" \
-H 'content-type: application/json' -d '{"transition":"ship"}'
# 3. Read it back. This is what your consumer should reconcile against.
curl $API/v1/orders/test_… -H "Authorization: Bearer $KEY"What it will not teach you
- Timing. Real orders sit in a state for hours or days, and a
NEWorder is accepted by itself after about two hours. A test order staysNEWuntil you move it. - Money. A test order is never charged and never fulfilled, so nothing about pricing, invoicing or stock is exercised.
- Expiry. The order is erased two hours after it was created, and a
transition does not extend that. A
404after two hours is the design, not a bug.