Webhook events
What each delivery carries and how to trust it. For managing subscriptions, see webhook management. Part of the documentation.
Event catalog
| Event | Fires when | Use it for |
|---|---|---|
trip.ended | A segmented trip closes (only trips ending inside the sync window, so a first sync never replays history). | Mileage filing. |
geofence.enter | A linked vehicle enters one of your zones. | Delivery timestamps. |
geofence.exit | A linked vehicle leaves one of your zones. | Visit durations. |
alarm.critical | SOS, collision, or rollover fires. | Opening an incident. |
report.daily | The nightly report e-mail run finishes for the organization. | Confirming the day closed. |
webhook.test | Only from the test button — never from real activity. | Proving the chain. |
Every delivery is a POST of JSON with two headers: X-Filodos-Signature (HMAC-SHA256 hex of the raw body, keyed with the subscription secret) and X-Filodos-Delivery (a unique id per delivery — store it and ignore repeats).
Envelopes
Event pushes (geofence.enter, geofence.exit, alarm.critical) share one shape; attributes carries type detail such as geofenceId:
{"event": "geofence.enter", "delivery_id": "947c…",
"organization": "acme", "device_id": 43,
"unique_id": "359876543210987",
"occurred_at": "2026-10-05T08:00:00+00:00",
"event_type": "geofenceEnter", "traccar_event_id": 101,
"attributes": {"geofenceId": 5}}
trip.ended carries the closed trip instead:
{"event": "trip.ended", "delivery_id": "a41d…",
"organization": "acme", "device_id": 43,
"unique_id": "359876543210987",
"occurred_at": "2026-10-05T06:47:03+00:00",
"trip": {"id": 812, "started_at": "2026-10-05T06:02:11+00:00",
"ended_at": "2026-10-05T06:47:03+00:00", "distance_km": 24.6}}
report.daily carries the send outcome:
{"event": "report.daily", "delivery_id": "77e0…",
"organization": "acme", "period_start": "2026-10-04",
"sent": 3, "failed": 0}
Verify the signature
Reject any delivery whose signature does not match — only then parse the body. Python example:
import hashlib, hmac
def trusted(raw_body: bytes, signature: str, secret: str) -> bool:
expected = hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature)
Compare in constant time (hmac.compare_digest, never ==), over the raw body bytes before JSON parsing. Deliveries are best-effort with a short timeout and no automatic retries yet — keep the read API as the source of truth and webhooks as the trigger.