Overview
Products
Reference
Push alerts
Instead of asking every minute whether a bus is coming or something has gone wrong, tell TransCAPI once and it POSTs to your server when it happens. Alerts are webhooks: an https URL of yours receives signed JSON.
Kinds of alert
type | Fires when | Parameters |
|---|---|---|
vehicle_approaching | A tracked bus is due at the stop within minutes. Once per bus per day. | atcocode, minutes (1–30, default 5), line (optional) |
disruption | A disruption newly matches your filters: bus, tram, National Rail or TfL. Ones already in force when you create the alert aren't sent. | filters: any of mode, severity, operator, line, atcocode (a CRS code for stations), lat+lon+radius, planned |
vehicle_approaching needs a bus TransCAPI tracks against its
timetable: wherever the operator reports positions to the Bus Open Data
Service. Buses running without tracking never trigger it.
Create one
curl -X POST "https://api.transcapi.com/v1/alerts.json" \
-H "X-Api-Key: YOUR_API_KEY" -H "Content-Type: application/json" \
-d '{"type": "vehicle_approaching", "atcocode": "370022870", "minutes": 10,
"line": "95", "url": "https://example.com/hooks/transcapi",
"description": "95 to Walkley, Church Street"}'
The response is the alert, with an id and a secret.
The secret is shown only once: keep it, because it's how you
check each delivery came from TransCAPI.
{ "id": "563f0ca2-43ad-4bb2-a9db-7b92fb0bcb92", "type": "vehicle_approaching",
"params": { "atcocode": "370022870", "minutes": 10, "line": "95" },
"url": "https://example.com/hooks/transcapi", "active": true,
"secret": "whsec_..." }
A disruption alert for anything affecting Huddersfield station:
{"type": "disruption", "filters": {"atcocode": "HUD"}, "url": "https://example.com/hooks/transcapi"}
Manage them
GET /v1/alerts.json your alerts, and your plan's limit
GET /v1/alerts/{id}.json one alert and its last 20 deliveries
DELETE /v1/alerts/{id}.json delete it
POST /v1/alerts/{id}/test.json send a test event now
Free keys can have 3 alerts, Standard 50 and Pro 500. Deliveries don't count against your request allowance.
What arrives
POST /hooks/transcapi
Content-Type: application/json
X-TransCAPI-Event: vehicle_approaching
X-TransCAPI-Delivery: f6af5213-34d3-46c4-911f-e8274bb2e555
X-TransCAPI-Signature: sha256=bd2b3189aef38c...
{ "id": "f6af5213-34d3-46c4-911f-e8274bb2e555",
"type": "vehicle_approaching",
"created_at": "2026-09-30T09:05:32+00:00",
"data": {
"alert_id": "563f0ca2-43ad-4bb2-a9db-7b92fb0bcb92",
"atcocode": "370022870", "line": "95", "operator": "First South Yorkshire",
"destination": "Walkley", "trip_id": "VJa5bc05d130...",
"aimed_departure_time": "2026-09-30T09:15:00+00:00",
"expected_departure_time": "2026-09-30T09:15:00+00:00",
"delay_seconds": 0, "minutes_away": 9.5,
"vehicle": { "latitude": 53.381833, "longitude": -1.464979, "vehicle_ref": "FSYO-37487", ... }
} }
A disruption event's data.disruption has the same
shape as an entry from /v1/disruptions.json. The delivery
id is the same on every retry, so use it to ignore repeats.
Check the signature
X-TransCAPI-Signature is sha256= followed by the
hex HMAC-SHA256 of the raw request body, keyed with your alert's secret.
Compute it over the body exactly as received, before parsing:
import hashlib, hmac
def is_from_transcapi(raw_body: bytes, header: str, secret: str) -> bool:
expected = "sha256=" + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, header or "")
Delivery, retries and pausing
- Reply with any
2xxwithin 5 seconds. Anything else, or no answer, is a failure. Redirects aren't followed. - A failed delivery is retried after about 1, 2, 5 and 10 minutes: five tries in all.
- After 20 failures in a row the alert is paused, with the reason in
disabled_reason. A successful test (POST /v1/alerts/{id}/test.json) resumes it. - URLs must be https and must resolve to a public address.
- Alerts are checked every 30 seconds, and disruption alerts every 5
minutes, so a
vehicle_approachingalert withminutes: 5arrives between about 4½ and 5 minutes before the bus.