Skip to content
OkadaDrop

Developers

Tracking a delivery

Two ways to show a customer where their parcel is: the status it has reached, or the rider moving on a map. Pick one per product — the first is a webhook you already receive, the second is one open endpoint you poll.


Two levels

Status only

No map, no coordinates, no polling loop. You already have this: every status change arrives at your webhook, and GET /partner/deliveries/{id} answers the same question on demand. Render the five states as a progress line and you have told the customer everything that matters about where their parcel is — accepted, picked up, in transit, delivered. Most integrations should stop here. It costs nothing to run and there is nothing to keep in sync.

Best for: Order pages, confirmation emails, anything that is read once or twice.

The rider's live position

Coordinates you can plot on your own map. GET /track/{delivery_id}/public returns the route, the per-drop ETAs and, while the trip is in flight, the rider's current latitude and longitude. It needs no API key — the delivery id is the credential — so you can call it straight from the customer's browser and draw the dot with whatever map library you already use.

Best for: A live tracking page a customer sits and watches while they wait.

Status only

Nothing new to call. The webhook you already receive carries the status, and the delivery endpoint answers on demand if you cannot be called back. Map the five forward states onto a progress line and you are done — that is what most customers actually read.

# The webhook already told you this:
# { "event": "delivery.in_transit", "status": "in_transit", … }

# Ask on demand when you cannot be called back:
curl https://api.okadadrop.com/api/v1/partner/deliveries/8f3b2c1e-… \
  -H "Authorization: Bearer $SOMA_API_KEY"

The rider’s live position

One open endpoint carries everything a map needs: both ends of the route, each drop on a multi-drop trip, the arrival estimate, and the rider’s current coordinates while they are riding.

# No key. The delivery id is the credential, so this is safe
# to call from your customer's browser.
curl https://api.okadadrop.com/api/v1/track/8f3b2c1e-…/public
200 response
{
  "reference": "SOMA-8F3B2C1E",
  "status": "in_transit",
  "package_size": "small",

  "pickup_address": "Osu, Accra",
  "pickup_lat": 5.5560, "pickup_lng": -0.1820,
  "dropoff_address": "East Legon, Accra",
  "dropoff_lat": 5.6360, "dropoff_lng": -0.1530,
  "recipient_name": "Ama M.",

  "eta_min": 12,

  "rider": {
    "name": "Kwame A.",
    "vehicle": "Honda CG125",
    "rating": 4.8,
    "lat": 5.5900,
    "lng": -0.1700
  },

  "stops": [],
  "events": [
    { "status": "pending",   "lat": null,   "lng": null,    "created_at": "…" },
    { "status": "accepted",  "lat": 5.5561, "lng": -0.1822, "created_at": "…" },
    { "status": "picked_up", "lat": 5.5560, "lng": -0.1820, "created_at": "…" }
  ]
}

What comes back

Fields on the public tracking response
referenceTypestringNotesThe short human reference — SOMA-8F3B2C1E. What a customer quotes at you on the phone.
statusTypestringNotesThe same seven values the webhook sends.
pickup_lat / pickup_lngTypenumberNotesWhere the parcel was collected. Always present.
dropoff_lat / dropoff_lngTypenumberNotesThe final destination. Always present.
eta_minTypenumber | nullNotesMinutes to the final drop, traffic-aware. Null when the trip is not live.
rider.name / vehicle / ratingTypestring | nullNotesEnough to recognise the person at the door: a first name and an initial, the bike, a rating.
rider.lat / rider.lngTypenumber | nullNotesThe live position. Null until a rider is on the job, and again after the drop — see below.
stops[]TypearrayNotesEach earlier drop on a multi-drop trip, with its own coordinates, status and cumulative eta_min.
events[]TypearrayNotesThe timeline: every status change with a timestamp, and the coordinate it happened at.

Polling it

const TERMINAL = ["delivered", "cancelled", "expired"];

// Poll while it moves, stop when it lands. An interval left running on a
// finished delivery is the usual way integrations burn their rate limit.
export function track(deliveryId, onUpdate) {
  let timer;

  const tick = async () => {
    const res = await fetch(
      `https://api.okadadrop.com/api/v1/track/${deliveryId}/public`,
    );
    if (!res.ok) return; // 404 = wrong id; anything else, try again next tick
    const data = await res.json();

    onUpdate(data);
    if (!TERMINAL.includes(data.status)) timer = setTimeout(tick, 6000);
  };

  tick();
  return () => clearTimeout(timer);
}

Putting it on a map

The payload is coordinates and nothing else, so it fits whatever map you already run. Two rules carry most of the correctness: draw the route from the fields that are always present, and treat the rider’s dot as something that may be missing at any moment.

Plotting the rider
// Any map library works — this is Google Maps, but the payload is
// only coordinates, so Leaflet, Mapbox or MapLibre are the same few lines.
const map = new google.maps.Map(el, { center: pickup, zoom: 13 });
const rider = new google.maps.Marker({ map, icon: RIDER_PIN });

track(deliveryId, (data) => {
  // The route is always there.
  drawRoute(map, [
    { lat: data.pickup_lat, lng: data.pickup_lng },
    ...data.stops.map((s) => ({ lat: s.dropoff_lat, lng: s.dropoff_lng })),
    { lat: data.dropoff_lat, lng: data.dropoff_lng },
  ]);

  // The rider is not. Null before one accepts, after the drop, and any
  // time their app stops reporting — so hide the dot, never throw.
  const live = data.rider && data.rider.lat != null;
  rider.setVisible(Boolean(live));
  if (live) rider.setPosition({ lat: data.rider.lat, lng: data.rider.lng });

  setEta(data.eta_min); // null when the trip is not live
});

Getting it right

The rider's position is optional, always

rider.lat and rider.lng are null before a rider accepts, after the parcel is delivered, and any time that rider's app stops reporting — a dead battery, a tunnel, a phone in a pocket with no signal. We would rather show you nothing than a dot parked where the rider was ten minutes ago. Draw the route first and treat the dot as something that may or may not be there; a map that throws when the field is null will break on a perfectly normal delivery.

Poll every 5–10 seconds, and stop

There is no push channel on this endpoint. Poll while the delivery is live and stop the moment it reads delivered, cancelled or expired — a tab left open on a finished delivery is the most common way to burn a rate limit. Faster than five seconds buys you nothing either: the ETA is recomputed at most every 20 seconds behind the endpoint.

The id is the credential

No key, no signature — anyone holding the delivery id can read this. That is what makes it safe to put in your customer's browser, and it is why the response is redacted: no phone numbers, no fare, no handover code, and names cut to a first name and an initial. Treat a delivery id like an unlisted link. If you need the full record, call /partner/deliveries/{id} from your server with your key.

Call it from the browser if you like

The endpoint answers cross-origin requests from any site, so a tracking page can fetch it directly with no proxy of your own in the middle. Doing it server-side works exactly the same way — it is the same URL either way.

Or send them to ours

Every delivery has a page on this site already, with the same map: link your customer to /track?id={delivery_id} and there is nothing to build, host or keep working. Worth taking seriously before you write a tracking page of your own — it is the only option here with no code in it.

The hosted page
https://okadadrop.com/track?id=8f3b2c1e-…
NextErrorsThe error envelope and what each code means.