Developers who connect their own software to Routewren and want to know what happens to a location ping
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.
| 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
- The key is valid and not revoked.
- The key has the scope
pings:write. - The body is valid JSON and every ping has its fields.
- 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.
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.
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 |