Event: Dispatch

delivery_provider.dispatch — the order is placed and paid for. Create the delivery.

Event: Dispatch

event_type: delivery_provider.dispatch

The order exists and the customer has committed. This is the only event that creates anything, and the only one where a wrong answer strands a real order.

Request

{
  "event_id": "b71e3c05-2a4f-4c11-9e83-15c9a2f0d477",
  "event_type": "delivery_provider.dispatch",
  "event_timestamp": 1785062700,
  "user_id": 12,
  "data": {
    "brand_id": null,
    "brand_guid": null,
    "external_store_id": "PZ-0031",
    "provider_store_id": "STORE-8871",
    "order_guid": "6f1c2b18-3f4d-4f0e-9a6f-2f3b1a9c4d55",
    "order_id": 884213,
    "track_delivery": true,
    "provider_quote_id": "prv_qt_abc123",
    "pickup": {
      "name": "Pizza Place Hillcrest",
      "address": "10 Old Main Road, Hillcrest, 3610",
      "latitude": "-29.778100",
      "longitude": "30.762200",
      "phone_number": "+27317650000",
      "ready_at": null,
      "deadline_at": null
    },
    "dropoff": {
      "address": "25 Dovehouse Road, Hillcrest 3650",
      "latitude": "-29.777900",
      "longitude": "30.788900",
      "phone_number": "+27825550100",
      "ready_at": null,
      "deadline_at": null
    },
    "manifest": {
      "total_value_cents": 10590,
      "sub_total_cents": 9590
    },
    "customer": {
      "first_name": "Jane",
      "last_name": "Roe",
      "phone": "+27825550100",
      "email": "[email protected]"
    },
    "order_number": "1234",
    "order_items": [
      {
        "quantity": 1,
        "name": "Large Margherita",
        "unit_price_cents": 8990,
        "total_price_cents": 8990
      },
      {
        "quantity": 2,
        "name": "Garlic Roll",
        "unit_price_cents": 300,
        "total_price_cents": 600
      }
    ],
    "payment": {
      "paid": true,
      "payment_type": "online",
      "net_amount_paid": "105.90",
      "amount_due": "0.0",
      "driver_tip": "0.0"
    },
    "delivery_fee": "45.0",
    "discount_total": "0.0",
    "delivery_instructions": "Leave at the gate, dog in the yard",
    "placed_at": "2026-08-07T14:05:00+02:00",
    "requested_for_time": null
  }
}

Identity and routing

FieldTypeNotes
order_guidUUIDYUMBI Gateway's order reference. Store it — it goes in the URL of every status update you push, and it is the idempotency key for this event.
order_idintegerThe same order as a number, for platforms that cannot key on a UUID. Prefer order_guid unless yours cannot.
provider_store_idstring | nullYour store identifier.
external_store_idstringYUMBI Gateway's store identifier.
brand_id, brand_guidnullInternal to YUMBI Gateway. Always null for you.
provider_quote_idstring | nullThe quote to honour. Absent when the channel dispatched without quoting.
track_deliverybooleanThe channel wants live driver-position updates for this delivery. See below.

Addresses

pickup and dropoff use the same shape as on Quote Request. At dispatch both sets of coordinates come from YUMBI Gateway's own records, so both serialise as decimal strings.

dropoff.name is not sent at dispatch — the customer's name is in the customer block instead. dropoff.deadline_at carries requested_for_time when the order is scheduled.

The order

FieldTypeNotes
order_numberstring | nullThe human-readable order number. Show this to the driver — it is what the store's staff will call out.
order_itemsarrayTop-level lines only.
manifest.total_value_centsinteger | nullGross the customer owes, in cents.
manifest.sub_total_centsinteger | nullValue of the goods being carried, in cents — before delivery fee and other charges.
delivery_feedecimal string | nullWhat the customer is charged for delivery.
discount_totaldecimal string | nullTotal discounts applied to the order.
delivery_instructionsstring | nullThe customer's free-text note.
placed_atISO 8601 | nullWhen the order was placed.
requested_for_timeISO 8601 | nullScheduled delivery time. null means ASAP.

Providers differ on which value they want, which is why both are sent: declare the goods with sub_total_cents, and use total_value_cents where you record what the delivery is worth.

order_items

{ "quantity": 1, "name": "Large Margherita", "unit_price_cents": 8990, "total_price_cents": 8990 }

Top-level lines only. Modifiers and options are stored as children of their parent item and are deliberately not sent — a driver checking a bag wants the products, not the modifier tree. name is the item name as the customer sees it.

Prices are in cents. Use them if you size a manifest by value or show line prices in your courier app; ignore them if you only need names and quantities.

customer

FieldType
first_namestring | null
last_namestring | null
phonestring | null, E.164
emailstring | null

customer.phone and dropoff.phone_number are the same number.

payment

{
  "paid": true,
  "payment_type": "online",
  "net_amount_paid": "105.90",
  "amount_due": "0.0",
  "driver_tip": "0.0"
}
FieldTypeNotes
paidbooleanWhether any payment has been recorded against the order.
payment_typestringSee below.
net_amount_paiddecimal stringWhat the customer has already settled.
amount_duedecimal string | nullWhat the driver must collect on arrival.
driver_tipdecimal string | nullTip already paid to the driver, included in net_amount_paid.

payment_type is one of:

ValueMeaning
onlinePaid by card through the channel. Nothing to collect.
voucherSettled by voucher. Nothing to collect.
pay_in_storeThe customer pays at the store.
cash_on_deliveryThe driver collects cash on arrival.
card_on_deliveryThe driver takes card payment on arrival.
unpaidNo payment recorded and no collection type stated.

You will only ever receive collection types you told us your drivers can handle. An order whose collection type you do not support is refused before this webhook is sent — see Payment Methods. Read amount_due rather than inferring from paid: an order can carry a partial payment and still owe money on arrival.

Monetary values in this block are decimal strings ("105.90"), not cents. The cents fields are on manifest and order_items.

Response

Return 200 with:

{
  "provider_delivery_id": "prv_dl_xyz789",
  "delivery_status": "assigned",
  "pin_code": "0431",
  "signature_required": true,
  "pickup_ready_at": "2026-08-07T14:20:00+02:00",
  "dropoff_deadline_at": "2026-08-07T14:50:00+02:00"
}
FieldTypeNotes
provider_delivery_idstringRequired. Your delivery reference. Everything afterwards is keyed on it.
delivery_statusstringA canonical status. Defaults to assigned when omitted.
pin_codestring | nullHandover PIN for the customer to give the driver. Send it as a string — a leading zero is significant ("0431").
signature_requiredbooleanDefaults to false.
pickup_ready_atISO 8601 | nullFalls back to the quote's dropoff_eta.
dropoff_deadline_atISO 8601 | nullFalls back to the quote's dropoff_deadline.

The channel receives provider_delivery_id, pin_code and signature_required directly, and shows the PIN to the customer.

Idempotency

Be idempotent on order_guid. Dispatch is retried on timeouts and 5xx responses, and a repeat must return the existing provider_delivery_id rather than creating a second delivery. Two couriers for one order is the outcome this prevents, and it is expensive for everyone.

YUMBI Gateway also guards its own side: an order that already has a delivery in flight never reaches your endpoint again. The webhook is only sent when no active assignment exists — one that has not reached a terminal status. A delivery that was cancelled or failed leaves the order eligible for a fresh dispatch, which will be a new order_guid-scoped attempt against the same order, so expect to see the same order_guid again after a terminal outcome and treat it as a new delivery.

Live tracking

track_delivery: true asks you for driver-position pings. Honour it by pushing update_type: "location" updates as the driver moves — see Pushing Status Updates. If you cannot supply positions, ignore the flag; nothing breaks, the customer simply has no live map.

Failing

Return a non-2xx. No delivery is recorded and the order stays live, so the channel can re-dispatch or fall back to the store's own driver.

HTTP/1.1 422 Unprocessable Entity

{ "error": "no_drivers_available" }

The channel receives a 422 carrying is_successful: false and your body under provider_response.

❗️

Never answer 2xx without a provider_delivery_id

A 2xx records an assignment against the order. Without a delivery id there is nothing to cancel, nothing to track, and nothing to push status against — the order looks dispatched and no driver is coming. If in doubt, fail.


Did this page help you?