Events and the event feed
Everything that happens to an application’s granted devices (an alert activating, a device leaving a geofence, an automation firing) is written to the organization’s event journal. An application reads it in two ways: by polling the feed, or by receiving each event as a signed webhook. Both carry the same envelope.
The envelope
Section titled “The envelope”Each event has a stable id, a schema_version of integration-event.v1, a type, occurred_at (when the change happened), recorded_at (when it was written) and data, a snapshot taken when the event was written. Apply data as the authoritative state; don’t merge it with older values.
Reading the feed
Section titled “Reading the feed”- Call
GET events/checkpointto get a cursor at the current head of the journal. - Read the current state you care about (for example the active alerts) through the API.
- Call
GET events?cursor=…&limit=…(limit1–100) and apply each event in order. Each page returnsdata,next_cursorandhas_more. - Store
next_cursortogether with your processed results, in the same transaction, even when the page was empty. Don’t advance it after a failed request.
Things to rely on:
- Order is commit order, not event time. Apply events in the order the feed returns them.
- Delivery is at least once. The same
idcan arrive more than once; record processed IDs and skip repeats. - Cursors survive token rotation. Changing the application’s grants invalidates them: the feed answers
409, and you rebuild from a fresh checkpoint. - The feed is filtered by device. Events for devices the application isn’t granted never appear.
Event types
Section titled “Event types”alert.activated.v1, alert.cleared.v1, alert.acknowledged.v1
An alert was activated, cleared or acknowledged; data is the alert snapshot.
Field in data | Type | Description |
|---|---|---|
uuid | string (uuid) | |
episode | integer | |
condition | "active" | "cleared" | |
activated_ | string (date-time) or null | |
cleared_ | string (date-time) or null | |
acknowledged_ | string (date-time) or null | |
workflow_ | string (uuid) | |
workflow_ | string | |
device_ | string (uuid) | |
execution_ | string or null | Why the alert's workflow stopped evaluating, for example binding_. |
geofence.entered.v1, geofence.exited.v1, geofence.wandering_started.v1, geofence.wandering_resolved.v1
A device entered or left a geofence (entered, exited), or its geofence rule activated or cleared (wandering_, wandering_).
Field in data | Type | Description |
|---|---|---|
uuid | string (uuid) | The geofence event's own UUID. |
geofence_ | string (uuid) | |
geofence_ | string | |
assignment_ | string (uuid) | |
device_ | string (uuid) | |
type | "entered" | "exited" | "wandering_ | |
source_ | string (uuid) or null | The telemetry event that caused the transition, when there was one. |
occurred_ | string (date-time) |
automation.triggered.v1
A workflow's action.event.v1 node fired.
Field in data | Type | Description |
|---|---|---|
workflow_ | string (uuid) | |
action_ | string | The action's identity, stable across retries. |
name | string | The event name configured on the node. |
node_ | string |
Webhooks
Section titled “Webhooks”Instead of polling, an application can register one HTTPS endpoint. Each event is delivered to it in journal order, one at a time.
Signed delivery of one integration event: POST to your endpoint.
Sent to the application's webhook endpoint (HTTPS on port 443, one per application, which must grant events:read and alerts:read) for each event on its granted devices, in journal order, one delivery at a time and at most one send per second per endpoint. The body is the same envelope the feed returns.
Signature. X-Iot-Signature is v1= followed by the hex HMAC-SHA256 of X-Iot-Timestamp + "." + X-Iot-Delivery-Id + "." + raw body, keyed with the endpoint's signing secret. Verify it over the raw bytes before parsing, reject a timestamp more than 300 seconds from your clock, and deduplicate by event id before returning 2xx: a valid signature can be replayed.
Retries. A 2xx response confirms delivery. Timeouts (3 s to connect, 5 s to transfer), network errors, 408, 425, 429 and 5xx are retried after 10 s, 30 s, 2 min and 10 min, up to five attempts within 24 hours; any other status, including a redirect, fails the delivery at once. A delivery that is still being retried holds back newer ones for that endpoint. Saving the endpoint rotates the secret, cancels pending deliveries and starts at the current head of the journal; recover gaps through the feed. Revoking the application or changing its grants disables the endpoint.
| Header | Format | Description |
|---|---|---|
X-Iot-Delivery-Id | string (uuid) | The delivery's UUID, the same on every attempt. |
X-Iot-Timestamp | string | Unix seconds when this attempt was signed. |
X-Iot-Signature | string | v1=<hex HMAC-SHA256>. |
{ "id": "0b1c2d3e-4f5a-4b6c-8d7e-9f0a1b2c3d4e", "schema_version": "integration-event.v1", "type": "geofence.exited.v1", "occurred_at": "2026-10-12T08:10:00+00:00", "recorded_at": "2026-10-12T08:10:01+00:00", "data": { "uuid": "1a2b3c4d-5e6f-4a7b-8c9d-0e1f2a3b4c5d", "geofence_uuid": "1e2d3c4b-5a69-4788-9a6b-5c4d3e2f1a0b", "geofence_name": "North paddock", "assignment_uuid": "4c5d6e7f-8a9b-4c0d-9e1f-2a3b4c5d6e7f", "device_uuid": "3f8a1c2e-5b6d-4e7f-8a9b-0c1d2e3f4a5b", "type": "exited", "source_event_uuid": "5d6e7f8a-9b0c-4d1e-8f2a-3b4c5d6e7f8a", "occurred_at": "2026-10-12T08:09:58Z" }}Verify the signature on the raw request body, before parsing it:
- Read the
X-Iot-Timestamp,X-Iot-Delivery-IdandX-Iot-Signatureheaders. - Reject the request if the timestamp is more than 300 seconds from your clock.
- Compute HMAC-SHA256 over
timestamp + "." + deliveryId + "." + rawBody, keyed with your endpoint’s signing secret, and hex-encode it. - Compare it with the value after
v1=using a constant-time comparison. - Deduplicate by the event’s
idbefore returning a 2xx response: a valid signature can be replayed.