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

typeFires whenParameters
vehicle_approachingA tracked bus is due at the stop within minutes. Once per bus per day.atcocode, minutes (1–30, default 5), line (optional)
disruptionA 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 2xx within 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_approaching alert with minutes: 5 arrives between about 4½ and 5 minutes before the bus.