How the tracking pipeline works
Tracking Pipeline › Developer guide
Developer guide
How the tracking pipeline works

Developers who connect their own software to Routewren and want to know what happens to a location ping

Version 1.0 · 6 Nov 2026 · The big picture · Sending pings · The ingest API · The matcher · Events and webhooks · The live map · Numbers to remember

Feature Tracking Pipeline Version 1.0 Date 6 Nov 2026 Owner Routewren Docs Language English · Original

A van sends its position. A few seconds later a customer's server hears that the van has arrived. This guide follows one location ping through Routewren, part by part, and says what you can rely on at each step.

  • 7 parts in one direction: the app sends, the API checks, the queue keeps order, the matcher decides.
  • At least once is how events are delivered. Your server must ignore a copy.
  • About 3 sec from a ping to the Console map.

1. The big picture

1.1 Seven parts, one direction

A ping enters on the left and an event leaves on the right. Plate 1 shows the parts. The numbers on the plate are used in the rest of this guide.

Plate 1. The tracking pipeline, from the driver app to your server and the live map
Plate 1. The tracking pipeline, from the driver app to your server and the live map
Part What it does What it keeps
1 Driver app Sends location pings in batches Up to 200 pings when there is no signal
2 Ingest API Checks the key, the scope and the ping Nothing
3 Queue Holds good pings in order, per driver The pings until the matcher reads them
4 Matcher Compares the position with the geofence of the next stop State store: last position and current stop
5 Event log Holds every event with its evt_... id The events
6 Dispatcher Calls your webhook, signed, with retries Delivery log, 30 days
7 Live map Pushes the event to the Console over a websocket Nothing

1.2 Three promises

Order. Pings of one driver are handled in the order they arrive. Pings of different drivers have no order.

At least once. An event is delivered one time or more. Use its id to ignore a copy.

Speed. From a ping to the Console map takes about 3 seconds.

2. Sending pings

2.1 One request

The driver app, or your own tracker, sends pings to the API in a JSON body. driver_id is written once, then comes a list of pings.

curl -X POST https://api.example.com/v1/pings \
  -H "Authorization: Bearer <api_key>" \
  -H "Content-Type: application/json" \
  -d '{
    "driver_id": "D-0142",
    "pings": [
      { "lat": <lat>, "lng": <lng>, "speed": 41, "battery": 78,
        "accuracy": 12, "ts": "2026-11-09T10:19:04+08:00" }
    ]
  }'

The key needs the pings:write scope. The API answers 202 Accepted and tells you how many pings it accepted.

2.2 The fields of a ping

Field Meaning Note
driver_id The driver, for example D-0142 Written once for the whole batch
lat, lng The position Use <lat> and <lng> in your own tests
speed How fast the van moves Sent by the app
battery Battery level of the phone Sent by the app
accuracy How exact the position is, in metres Worse than 100 m is ignored for arrived
ts When the position was taken ISO time. Part of the duplicate check

2.3 Batches and no signal

Send up to 50 pings in one call. When the phone has no signal, the driver app keeps up to 200 pings and sends them when the signal comes back. Those late pings are still handled in order (see 3.3).

2.4 What the API answers

Code Meaning
202 Accepted. The body has the number of accepted pings
401 The key is wrong or revoked
403 The key has no pings:write scope
422 A ping is not valid. It is rejected and never queued
429 More than 600 requests a minute for this key. Wait for Retry-After

3. The ingest API

The ingest API is the only door into the pipeline. Everything it rejects stops here.

3.1 What it checks

  1. The key is valid and not revoked.
  2. The key has the scope pings:write.
  3. The body is valid JSON and every ping has its fields.
  4. The key is under 600 requests a minute.

A ping that passes all four goes onto the queue. Plate 1 shows the other path, the dashed one, with the four answers.

3.2 The same ping twice

A phone may send a batch again when it is not sure it arrived. Routewren ignores a ping when it has already seen the same driver_id and ts. You do not need to remove copies yourself.

3.3 Late pings

How late What happens
Up to 10 minutes Accepted and handled in order. Events can still come from it
More than 10 minutes Stored for the history, but it makes no events

4. The matcher

The matcher reads the queue one driver at a time. It is the only part that knows what a stop is.

4.1 State for each driver

For each driver the state store keeps the last position and the current stop. A new ping is compared with the geofence of the current stop. The radius is a depot rule, 150 m by default.

4.2 Arrived, delivered, left

Plate 2 shows how one stop is decided. Two pings in a row inside the radius mean arrived. The driver taps Delivered in the app to finish the stop.

Plate 2. How the matcher decides that a van has arrived, delivered or left
Plate 2. How the matcher decides that a van has arrived, delivered or left

A driver who leaves the radius without tapping Delivered makes no event. The dispatcher sees a flag in the Console: left without delivery. Your server hears nothing about that stop.

4.3 Why two pings

One ping can be a GPS jump, a position a long way from the real one. Two pings in a row inside the radius are much harder to fake by accident.A faster ping interval makes two pings come sooner, so arrived comes sooner too. A ping with an accuracy worse than 100 m is ignored for arrived.

5. Events and webhooks

5.1 An event

The matcher writes an event to the event log. The dispatcher sends it to every endpoint that asked for its type.

{
  "id": "evt_<id>",
  "type": "stop.arrived",
  "depot": "nth-depot-01",
  "driver_id": "D-0142",
  "stop_id": "stop_<id>",
  "occurred_at": "2026-11-09T10:19:08+08:00"
}

The types are stop.arrived, stop.delivered, route.completed and driver.offline.

5.2 Sign and check

Every call has the header Routewren-Signature: t=<timestamp>,v1=<signature>. The signature is the HMAC-SHA256 of <timestamp>.<body>, made with the signing secret of the endpoint. Reject a call when the timestamp is more than 5 minutes old.

import hashlib, hmac, time

def is_valid(header, body, signing_key):
    parts = dict(p.split("=", 1) for p in header.split(","))
    if abs(time.time() - int(parts["t"])) > 300:
        return False                      # older than 5 minutes
    signed = f'{parts["t"]}.{body}'.encode()
    expected = hmac.new(signing_key, signed, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, parts["v1"])

5.3 Retries

Your server must answer with a 2xx code within 5 seconds. If it does not, the dispatcher tries again 6 times. After the last try the endpoint turns Failing and the admin gets an e-mail.

Plate 3. The tries of one event: right away, then six retries
Plate 3. The tries of one event: right away, then six retries

5.4 At least once

Delivery is at least once. The same event can come twice, for example when your answer was slow and the retry crossed it. Keep the id of every event you handled, and skip one you have seen.

6. The live map

The Console map does not poll. It opens a websocket and the event log pushes new events down it. The badge says Live while the websocket is up. From a ping to a moving van on the map takes about 3 seconds.

7. Numbers to remember

What Value
Pings in one call Up to 50
Pings kept on the phone without signal Up to 200
Requests for one key 600 a minute
Duplicate ping Same driver_id and ts, ignored
Late ping that still makes events Up to 10 minutes late
Accuracy that counts for arrived 100 m or better
Arrived 2 pings in a row inside the radius
Your answer to a webhook A 2xx code within 5 seconds
Retries 6: after 1 min, 5 min, 30 min, 2 h, 6 h, 24 h
Signature age Reject when older than 5 minutes
Ping to map About 3 seconds

Change log

Version Date Change By
1.0 2026-11-06 First version Routewren Docs