The Webhook Contract

The envelope every event shares, how to route on it, and what YUMBI Gateway does with your response. ---

The Webhook Contract

All four events arrive at your endpoint as an HTTP POST with the same envelope. Only data changes shape between them.

The envelope

{
  "event_id": "3f1a5d80-6f5f-4b6a-9d1f-0d6d0b7a1c22",
  "event_type": "delivery_provider.dispatch",
  "event_timestamp": 1785062400,
  "user_id": 12,
  "data": { }
}
FieldTypeNotes
event_idUUIDFresh per HTTP attempt, including retries of the same logical event. Useful for correlating your logs with ours; it is not a deduplication key — see below.
event_typestringOne of the four dotted values.
event_timestampintegerUnix seconds. Covered by the HMAC signature.
user_idintegerYour delivery provider id inside YUMBI Gateway. Constant for your integration.
dataobjectThe event payload.

Content-Type is always application/json.

Routing

Route on event_type. Do not route on the URL: all four events can be registered against the same URL, and a single handler is the simplest way to serve them.

switch (body.event_type) {
    case "delivery_provider.quote_request":        return quote(body.data);
    case "delivery_provider.dispatch":            return dispatch(body.data);
    case "delivery_provider.cancellation_request":return cancel(body.data);
    case "delivery_provider.status_request":      return status(body.data);
    default:
        throw new BadRequestException(`Unsupported event_type: ${body.event_type}`);
}

An event_type you do not recognise should be a 400. Silently returning 200 makes a misconfiguration look like a working integration.

You may register different URLs per event if you prefer — each event type is configured independently on our side — but the path is part of the signed string, so each URL signs against its own path.

Identifying the store

Three fields identify where the food is collected from. They are on every event except status_request.

FieldUse it
provider_store_idFirst choice. Your own identifier for the store, which you gave us during onboarding and we store per store.
external_store_idFallback. YUMBI Gateway's store identifier.
brand_id, brand_guidInternal to YUMBI Gateway. Always null for you — ignore them.

If provider_store_id is null for a store you expect to serve, that store's configuration is incomplete on our side — fail the event with a 422 and a clear message rather than guessing. That is a configuration error nothing will fix by retrying, and a distinct status code lets our operators see it.

Responding

Reply synchronously. The response body is the result — there is no separate callback for quote, dispatch or cancellation.

SituationStatusBody
Handled200The response documented for that event
Declining the job (quote or dispatch)any non-2xxA reason, in any JSON shape
Our configuration is wrong (no store id, unknown store)422A message naming what is missing
Signature invalid, missing or stale401
Unrecognised event_type400
Broken on your side5xxA message

YUMBI Gateway treats any non-2xx as "this did not happen". On a quote that means delivery is unavailable; on a dispatch it means no delivery exists. Both are safe outcomes. What is not safe is a 200 that does not mean what it says — see the warning below.

Whatever you return in a non-2xx body is passed through to the ordering channel as provider_response and stored against the order, so put something diagnosable there.

❗️

Never return 2xx unless the thing actually happened

A 2xx on dispatch tells YUMBI Gateway a delivery exists at your end and an assignment is recorded against the order. A 2xx with no provider_delivery_id leaves an order live with nothing behind it and no driver coming — the worst failure mode in the whole integration. If you cannot create the delivery, return a non-2xx.

Timeouts

Connection5 seconds
Response30 seconds

Quote and dispatch are synchronous with a customer waiting at checkout, so treat a couple of seconds as the target and anything over ten as a problem. A response we never receive is indistinguishable from a failure: on dispatch, that means we may retry and you may end up holding two deliveries unless you are idempotent on order_guid.

If your own upstream is slow, fail fast with a 5xx rather than holding the connection. A refused quote costs one unavailable delivery option; a stalled one holds up the customer's checkout.

Retries and idempotency

Dispatch is retried on transient failures — a 5xx, a timeout, or a connection error. Make it idempotent on order_guid: a repeat dispatch for an order you already hold must return the existing provider_delivery_id rather than creating a second delivery. Two couriers arriving for one order is the failure this prevents.

event_id changes on every attempt, so it cannot serve as the idempotency key. Use order_guid (or order_id, if your platform needs a numeric reference — both identify the same order).

Quote is not retried; a failed quote simply means no price. Cancellation may be retried, so make it idempotent too — a cancellation of something already cancelled should be a 200.

YUMBI Gateway's side is also defended: an order that already has a delivery in flight never reaches your endpoint a second time. The dispatch webhook is only sent when no active assignment exists for that order.

Logging on our side

Every webhook we send you is recorded with the request body, your response status and body, and the order it belongs to, and is visible against the order in the Gateway. The one exception is a successful status_request, which fires on a recurring interval and would flood the log — failures are still recorded.

If you are debugging a delivery with us, quote the order_guid and we can show you exactly what we sent and what came back.


Did this page help you?