Skip to content

Order lifecycle

An order’s status is one of a small, stable set. Each change sends a webhook and increases the order’s version.

On demand
  1. placed
  2. awaiting_goods
  3. searching
  4. driver_assigned
  5. picked_up
  6. delivered

Can end early: cancelledfailed

Window
  1. placed
  2. scheduled
  3. picked_up
  4. delivered

Can end early: cancelledfailed

Status Type Meaning Webhook
placed both Booked. Transport is being arranged. order.placed
awaiting_goods on demand Waiting for your goods. The driver search has not started. order.placed
searching on demand Goods released; Delivr is finding a driver. order.searching
scheduled window Booked on its window’s route. order.scheduled
driver_assigned on demand A driver accepted and is on the way to the store. order.driver_assigned
picked_up both The driver has the goods. order.picked_up
delivered both Handed over. proofOfDelivery has deliveredAt and, when recorded, receiverName. order.delivered
cancelled both Cancelled, with a reason. order.cancelled
failed both Could not be delivered, with a reason. order.failed

placed and awaiting_goods both send order.placed: that is the moment to create the delivery in your system. An on-demand order usually shows awaiting_goods from the start, while it waits for the goods. You are billed for delivered orders and for cancellation fees, not for placing.

cancelled and failed orders carry a reason:

Reason Ends as Meaning
no_driver failed No driver accepted the on-demand order in time. Your fallback may follow.
goods_not_ready failed The goods were never reported ready, and the ready deadline and its grace passed. Your fallback may follow.
no_capacity failed The transport could not be booked, for example the window filled up.
undeliverable failed The transport partner could not complete the delivery.
cancelled_by_merchant cancelled You cancelled.
cancelled_by_delivr cancelled Delivr’s operators cancelled.
other either Anything else.

New reasons may be added. Treat an unknown one like other.

Some webhooks are not status changes:

Event When
order.ready_at_changed You reported when the goods will be ready (readyAt).
order.ready_overdue The goods are due (readyBy) and not reported ready.
order.window_changed Booked on another window than quoted (previousWindow), after onWindowFull: reschedule.
order.awaiting_recipient A failed order waits for the recipient to choose a window, until choiceDeadline.
order.successor_created A failed order got a successor order (successorId).
order.fallback_cancelled No successor follows (fallbackReason: expired or no_window).
order.held An assisted checkout order is held for a pending sale.

See Event types for the payloads.

  • GET /v1/merchant/orders/{order} takes Delivr’s id or the DD- reference. It is the source of truth.
  • GET /v1/merchant/orders is a change feed. Page through nextCursor to see every change once, and filter by updatedSince, status or externalRef. A nightly reconciliation with updatedSince catches anything a webhook missed.
  • version increases with every change. Keep the highest you have seen, and ignore older data.

Webhooks can be missed, for example while your endpoint was down for longer than the retries last. Run a job, nightly or hourly, that reads every order changed since its last run and applies it with the same version check as your webhook handler.

updatedSince counts from when Delivr recorded a change, and nextCursor is absent on the last page, so keep the time each run started and overlap the next run a little:

const startedAt = new Date();
const since = new Date(lastRunStartedAt.getTime() - 10 * 60_000); // overlap: applying twice is harmless
let cursor;
do {
const url = new URL("/v1/merchant/orders", process.env.DELIVR_API_URL);
url.searchParams.set("updatedSince", since.toISOString());
url.searchParams.set("limit", "100");
if (cursor) url.searchParams.set("cursor", cursor);
const res = await fetch(url, { headers: { Authorization: `Bearer ${process.env.DELIVR_API_KEY}` } });
if (!res.ok) throw new Error(`Delivr answered ${res.status}`);
const page = await res.json();
for (const order of page.items) await applyIfNewer(order); // update only when order.version is higher
cursor = page.nextCursor;
} while (cursor);
await saveLastRunStartedAt(startedAt);

Held assisted checkout orders are not listed until their sale completes; read them by id.

Field Meaning
createdAt, placedAt When the order was created and placed.
readyBy On demand. When the goods are due.
readyAt On demand. When the goods are (or are assumed to be) ready.
window Window orders. The delivery window.
confirmedAt A driver accepted (on demand), or the transport was booked (window).
updatedAt The last change.