Skip to main content

Webhooks

A webhook subscription sends events to your HTTPS endpoint as they happen: a device registered, a trip completed, an alert raised, a geofence entered, a stop completed, a fuel purchase flagged, a payment read from your gateway. The event catalogue lists every event you can subscribe to, with its schema version; GET /v1/webhooks/events returns the same list with each event's JSON Schema and a sample. Manage subscriptions in Developer Studio or with the webhooks API (webhooks:read, webhooks:write).

Subscribe​

curl -X POST "https://api.zaitrack.app/v1/webhooks" \
-H "Authorization: Bearer zk_test_…" \
-H "Content-Type: application/json" \
-d '{
"url": "https://hooks.example.com/zaitrack",
"description": "Dispatch board",
"events": ["zaitrack.geofence.*", "zaitrack.alert.raised"],
"filters": {"severityAtLeast": "high"}
}'
  • url: https, at most 2,048 characters, no credentials in it. Private, loopback and link-local addresses are refused at the address each delivery actually connects to, and redirects are not followed.
  • events: 1 to 50 patterns, each matching at least one event of the catalogue: an exact name (zaitrack.alert.raised.v1), a name without its version, a prefix wildcard (zaitrack.alert.*) or *.
  • filters (optional):
    • severityAtLeast: info, low, medium, high or critical; events with a lower data.severity are dropped.
    • vehicleIds: up to 500 UUIDs; events about other vehicles are dropped.
    • divisionIds: up to 50 division UUIDs; only events about vehicles of those divisions, or of the divisions below them, are kept. An event that names no vehicle is dropped.
    • predicate: a JSON Logic expression over the envelope (event_name, region, data), at most 4 KiB, with the operators var, ==, !=, <, <=, >, >=, in, and, or and !. Example: {"in": [{"var": "data.severity"}, ["high", "critical"]]}.
  • retryPolicy (optional): maxAttempts 1 to 24 (default 12), timeoutS 1 to 60 (default 10) and backoff exponential (default), linear or fixed.
  • secret (optional): 24 to 128 characters. Left out, the service generates one (whsec_…). The response carries it once.

An organisation keeps at most 50 subscriptions.

A delivery​

Each delivery is an HTTP POST with a JSON body:

{
"schema_version": "1.0",
"event_id": "…",
"event_name": "zaitrack.geofence.entered.v1",
"tenant_id": "…",
"region": "gcc",
"occurred_at": "2026-10-28T06:12:04.512Z",
"produced_at": "2026-10-28T06:12:05.031Z",
"trace_id": "…",
"data": { … }
}

The envelope keys are snake_case, unlike the REST API. data is described by the event's JSON Schema, with times in RFC 3339. A body over 256 KiB is not delivered.

HeaderValue
X-Zaitrack-Signaturet=<unix seconds>,v1=<hex HMAC-SHA256>, two v1 during a secret's rotation: see Verify a delivery
X-Zaitrack-Event-Idthe event's id; the same on every attempt and replay
X-Zaitrack-Event-Namefor example zaitrack.alert.raised.v1
X-Zaitrack-Delivery-Idthis delivery (one event to one subscription)
X-Zaitrack-Attempt1 for the first attempt, then 2, 3, …
X-Zaitrack-Subscription-Id, X-Zaitrack-Tenant-Id, X-Zaitrack-Regionwhere it came from
X-Zaitrack-Schema-Version1.0
X-Zaitrack-Replaytrue on a replay
X-Zaitrack-Testtrue on a test send
X-Zaitrack-Trace-Idthe trace, when the event has one

An alert's events say it in English (title, description) and language-neutral (code, params), to show in your users' language: see Alert wording.

Answer with any 2xx within the subscription's timeout. Deduplicate on X-Zaitrack-Event-Id: an event can arrive more than once.

Test a subscription​

POST /v1/webhooks/{id}:test sends one delivery to the endpoint now and answers with it and its attempt.

  • With no body, it sends zaitrack.webhook.test.v1, whose data.message says the endpoint is reachable.
  • With {"event": "zaitrack.trip.completed.v1"}, it sends that event with the catalogue's sample as its data, so you can test the handler for a real event. Any event of the catalogue can be named.

Either way the body carries "test": true and the request X-Zaitrack-Test: true, so your handler can tell a test from a real event. A test does not count towards the subscription's failures.

Rotate the secret​

POST /v1/webhooks/{id}/secrets:rotate gives the subscription a new secret, shown once in the response. For an overlap the old secret keeps signing beside the new one: each delivery carries two v1 signatures, so an endpoint still holding the old secret keeps verifying while you deploy the new one.

curl -X POST "https://api.zaitrack.app/v1/webhooks/$ID/secrets:rotate" \
-H "Authorization: Bearer zk_test_…" \
-H "Content-Type: application/json" \
-d '{"overlapHours": 48}'

overlapHours is 0 to 168 and 24 when left out; 0 stops the old secret at once.

Retries​

A delivery that gets no 2xx (an error status, a timeout, a refused connection) is retried. The wait after attempt n:

BackoffWait
exponential30 s × 2^(n−1): 30 s, 1 min, 2 min, 4 min, …
linearn × 5 min
fixed5 min

No wait exceeds 60 minutes. After maxAttempts the delivery is dead-lettered (status dlq). An endpoint that answers 410 Gone is dead-lettered at once and its subscription is disabled. After 25 dead letters in a row the subscription is disabled too (status: disabled, with disabledReason); a disabled subscription receives nothing until you set it back to active with PATCH /v1/webhooks/{id}. paused stops deliveries on purpose.

Deliveries and replay​

  • GET /v1/webhooks/{id}/deliveries lists a subscription's deliveries, newest first, with their status: queued, sending, retrying, delivered or dlq.
  • GET /v1/webhooks/{id}/deliveries/{deliveryId} gives the payload and every attempt: its status code, latency, error, the request headers sent, your endpoint's response headers and the first 1 KiB of its answer.
  • POST /v1/webhooks/{id}/deliveries/{deliveryId}:replay sends it again now, with X-Zaitrack-Replay: true. A replay does not use up the retry budget of a delivery still being retried, and is refused (409) while the delivery is being sent.

Dead letters​

GET /v1/webhooks/dead-letters lists the deliveries that ran out of attempts across every subscription of your organisation, newest first (limit, cursor). Once your endpoint is fixed, replay them in one call:

curl -X POST "https://api.zaitrack.app/v1/webhooks/dead-letters:replay" \
-H "Authorization: Bearer zk_test_…" \
-H "Content-Type: application/json" \
-d '{"deliveryIds": ["…", "…"]}'

Up to 100 deliveries a call. Each is sent before the answer, which gives, per delivery, the delivery and its attempt, or the error that stopped it.

Deliveries are kept for 30 days.