Delivery API
Tracking
Every delivery carries an unguessable token. Hand it to your customer as a link and they watch the rider approach — no account, no login, no app. Or consume the same feed yourself and draw the pin in your own interface.
The link
Creating a delivery returns a trackingToken. Append it to the tracking path and you have a page you can text, email or show as a QR code.
https://dispatch.gumza.ke/track/<trackingToken>If your customers ordered through a Gumza WhatsApp storefront, they already have a link — it is sent into the chat thread automatically as the order moves, alongside each status update, and it points at https://gumza.ke/track/<token> with the order's own token.
Reading it yourself
Both endpoints are public — the token in the URL is the authorisation, so no API key is sent and none should be.
- GET
/api/v1/track/orders/:tokenCurrent status, both addresses, last known rider location - GET
/api/v1/track/orders/:token/streamServer-sent events: rider position as it changes
{
"success": true,
"data": {
"status": "IN_TRANSIT",
"pickup": { "address": "Shop 4, Westlands, Nairobi", "lat": -1.2672, "lng": 36.8065 },
"dropoff": { "address": "Kileleshwa, Nairobi", "lat": -1.2833, "lng": 36.7833 },
"updatedAt": "2026-10-12T09:11:40.220Z",
"rider": { "location": { "lat": -1.2744, "lng": 36.7961, "bearing": 112,
"updatedAt": "2026-10-12T09:11:38.004Z" } }
}
}rider is null until someone is assigned, and rider.location is null until that rider's device has reported a position. Handle both — a map that throws before the first ping is the most common bug here.
The live stream
The stream endpoint is Server-Sent Events, not a WebSocket, so a plain EventSource works with no library and reconnects on its own.
const source = new EventSource(
`https://api.dispatch.gumza.ke/api/v1/track/orders/${token}/stream`
);
source.onmessage = (event) => {
const { status, location } = JSON.parse(event.data);
marker.setLatLng([location.lat, location.lng]);
label.textContent = status;
};
// EventSource retries by itself; close it when the view goes away.
source.onerror = () => { /* surface a "reconnecting" hint, do not close */ };
window.addEventListener('beforeunload', () => source.close());- Each frame is
{ status, location }, wherelocationis{ lat, lng, bearing?, updatedAt }— the same shape as the read endpoint. - A message is pushed only when the position actually changes — no duplicate frames.
- A comment line is sent every 15 seconds to keep the connection alive through proxies and mobile networks.
EventSourceignores it; you will not see it inonmessage. - Before a rider is assigned there is nothing to stream. The connection stays open and quiet, so render the status from the read endpoint rather than waiting for a frame.
Position in your own backend
If you want rider position server-side rather than in a browser, subscribe to the dispatch.location_updated webhook — see Webhooks. It is throttled to at most one event every 20 seconds per rider, which is plenty for a map and gentle on your endpoint.
One wrinkle worth knowing before you write the mapping code: that webhook names its coordinates latitude and longitude with a timestamp, while the tracking read and stream above use lat, lng and updatedAt. Same numbers, different keys.