Appearance
Delivery lifecycle
Every delivery moves through a normalized status model. Your integration should key off status on API responses and eventType on webhooks.
Status definitions
| Status | Terminal? | Description |
|---|---|---|
accepted | No | Created; dispatch in progress |
dispatch_failed | No* | No driver assigned — may recover or expire |
scheduled | No | Waiting for scheduled window |
reassigning | No | Being re-dispatched to another driver |
driver_assigned | No | Driver assigned |
driver_enroute_to_pickup | No | Driver en route to pickup |
driver_at_pickup | No | Driver at pickup location |
pickup_complete | No | Items picked up |
driver_enroute_to_dropoff | No | Driver en route to customer |
driver_at_dropoff | No | Driver at customer location |
delivered | Yes | Successfully completed |
failed | Yes | Delivery failed |
return_in_progress | No | Returning to store |
returned | Yes | Returned to store |
cancelled | Yes | Cancelled by client or operations |
expired | Yes | Timed out without completion |
* dispatch_failed becomes terminal only if followed by expired or cancelled.
Why a delivery ended that way
status tells you what happened; statusReasonCode tells you why, in a form you can branch on. It accompanies failed, cancelled, dispatch_failed, expired, and return_in_progress. The prose statusReason is for humans and may change without notice.
The codes worth handling explicitly:
| Group | Codes |
|---|---|
| Recipient | recipient_unavailable, recipient_refused, recipient_not_at_cq |
| Address | address_not_found, address_inaccessible, unsupported_destination_type |
| Installation access | denied_at_gate, credential_expired, credential_not_valid_for_installation, gate_closed, fpcon_elevated, escort_unavailable |
| Verification | verification_failed, age_verification_failed, photography_prohibited_no_substitute |
| Package | package_damaged, package_not_ready |
| Dispatch | no_carrier_available, outside_service_area, quote_expired |
| Cancellation attribution | cancelled_by_merchant, cancelled_by_recipient, cancelled_by_carrier, cancelled_by_onpoint |
| External | weather_or_road_closure |
Treat the access codes differently from recipient_unavailable. A driver turned away at a gate is not a customer who missed the door: the package never reached the destination, the recipient did nothing wrong, and the fix is credential or scheduling work rather than a redelivery attempt. Most delivery APIs collapse both into one "delivery attempt failed" bucket, which makes the failure rate uninterpretable and gives support nothing to route on. Retrying a denied_at_gate order unchanged will simply fail again.
Cancellation attribution matters for the same reason — cancelled_by_carrier is an OnPoint problem, cancelled_by_recipient is not, and only one of them should count against your fulfillment metrics.
Typical happy path
accepted
→ driver_assigned
→ driver_enroute_to_pickup
→ driver_at_pickup
→ pickup_complete
→ driver_enroute_to_dropoff
→ driver_at_dropoff
→ deliveredOnPoint guarantees that a delivery reaching delivered has passed through the intermediate driver milestones in order. If an upstream carrier omits a milestone, OnPoint infers it before emitting webhooks.
Special cases
Reassigning
reassigning means the delivery is still live. A new driver_assigned follows. Do not mark the order cancelled or create a replacement delivery unless your business rules require it.
Dispatch failed
The delivery record exists. You may:
- Wait for automatic retry (if configured for your account)
- Call
PATCH /deliveries/{deliveryId}to update and trigger re-dispatch - Cancel via
POST /deliveries/{deliveryId}/cancel
Returns
When capabilities.returns is true, failed or refused drop-offs may enter:
failed or driver_at_dropoff
→ return_in_progress
→ returnedUpdate eligibility
| Lifecycle stage | Updatable fields |
|---|---|
Before pickup_complete | All create fields (pickup, dropoff, windows, package, verification, handling, tip, metadata) |
After pickup_complete, before delivered | dropoff.instructions, dropoff.contactPhone, tipAmountCents |
After delivered | None — returns 409 |
Verification can be tightened but never loosened. Removing a check that cargo attributes or destination policy require returns 422 — you cannot drop the age check off an alcohol order mid-flight.
Cancellation eligibility
Cancellations are accepted until the delivery reaches a terminal state. Cancellation fees (when supported) appear in pricing.cancellationFeeCents on the response.
Polling vs webhooks
| Use case | Recommendation |
|---|---|
| Live order tracking UI | Webhooks + occasional GET for reconciliation |
| Checkout serviceability | POST /serviceability |
| Checkout quote | POST /quotes |
| Recovery after a webhook outage | GET /events?delivered=false&since= |
| Recovery after your own outage | GET /deliveries?from={lastSeenTimestamp} |
| Look up one order by your own ID | GET /deliveries?clientOrderReference={ref} |
| Nightly reconciliation | GET /deliveries?from=&to= |
Prefer GET /events?delivered=false for webhook gaps. It returns exactly the events your endpoint never received, in order, with payloads identical to the originals — so you replay them through the same handler instead of diffing delivery state and inferring what you missed.