New: the AI firewall watches every module and explains each incident in plain words. See how it works

Webhook event reference

Every IoT webhook event – devices, connections, alarms, commands, firmware, provisioning, rules and flows – with when it is sent and an example payload.

This page lists the IoT events a webhook can subscribe to and the data each one carries. The envelope around data, the headers and the signature are described in Webhooks. The list of events offered to your organisation is also available from GET /api/webhooks/events.

General rules:

  • Payloads carry ids and safe fields only. A device credential, token, password, certificate or download link is never sent.
  • Times are ISO 8601 text in UTC, except alarm times, which are milliseconds since the epoch.
  • Events can arrive out of order and, rarely, twice. Deduplicate on X-Webhook-Delivery and order by the times in the payload.
  • The examples show only the envelope’s event and data; the full envelope also has webhookId, tenantId and createdAt.

Devices

EventSent when
device.createdA device was added: by hand, from an import, by provisioning or claiming, or as a gateway’s child
device.updatedThe name, label, type, profile, gateway flag, additional info or status changed
device.deletedA device was deleted (by hand, by a release, or by undoing an import)
device.credentials_rotatedThe credential was rotated, replaced, renewed (X.509) or revoked
device.connectedA device came online and stayed online for the hold time
device.disconnectedA device went offline and stayed offline for the hold time
{ "event": "device.created", "data": {
  "deviceId": "6f1c…", "name": "pump-station-04", "serial": null, "profileId": "a1b2…",
  "status": "ACTIVE", "isGateway": false, "createdAt": "2026-11-09T08:00:00.000Z" } }

A gateway’s child also carries gatewayDeviceId.

{ "event": "device.updated", "data": {
  "deviceId": "6f1c…", "name": "pump-station-04", "serial": null, "profileId": "a1b2…",
  "status": "DISABLED", "isGateway": false, "changedFields": ["status"], "previousStatus": "ACTIVE",
  "updatedAt": "2026-11-09T08:10:00.000Z" } }

changedFields names fields only (sorted: additionalInfo, isGateway, label, name, profileId, status, type), never their values. previousStatus is present only when the status changed. An edit that changes none of these fields sends nothing.

{ "event": "device.deleted", "data": {
  "deviceId": "6f1c…", "name": "pump-station-04", "serial": null, "profileId": "a1b2…",
  "credentialRevoked": true, "deletedAt": "2026-11-09T08:20:00.000Z" } }
{ "event": "device.credentials_rotated", "data": {
  "deviceId": "6f1c…", "name": "pump-station-04", "serial": null, "profileId": "a1b2…",
  "change": "rotated", "credentialType": "ACCESS_TOKEN", "previousType": "ACCESS_TOKEN",
  "changedAt": "2026-11-09T08:30:00.000Z" } }

change is rotated, replaced, renewed or revoked; after revoked, credentialType is null.

{ "event": "device.connected", "data": {
  "deviceId": "6f1c…", "name": "router-17", "profileId": "a1b2…", "transport": "mqtts",
  "connectedAt": "2026-11-03T10:00:00.000Z", "remoteIpPrefix": "203.0.113.0/24" } }
{ "event": "device.disconnected", "data": {
  "deviceId": "6f1c…", "name": "router-17", "profileId": "a1b2…", "reason": "timeout",
  "disconnectedAt": "2026-11-03T12:00:00.000Z", "lastSeenAt": "2026-11-03T11:59:40.000Z", "onlineSeconds": 7200 } }

remoteIpPrefix is the /24 of an IPv4 address or the /48 of an IPv6 address, never the full address. reason is clean (the device disconnected), timeout (its keep-alive or uplink window ran out), kicked (the server closed it, for example after a credential change) or quota (it hit a limit).

Hold time and volume. A connection change is sent only after it held for the hold time: Devices > settings > Connection webhooks: hold time (seconds), default 30, 5 to 3,600. A device that drops and comes back within it sends nothing. At most 600 connection events and 600 device change events are sent per organisation per minute by default; the rest of that minute is dropped and your owners and admins get one notice. The devices stay readable through the API.

Alarms

EventSent when
alarm.createdAn alarm opened (a profile rule, a flow block or an operator). Not sent when an open alarm is triggered again.
alarm.acknowledgedAn operator or a flow acknowledged an open alarm. It stays open.
alarm.clearedAn alarm was resolved: by its rule or flow, by an operator, or because its device was deleted
{ "event": "alarm.created", "data": {
  "alarmId": "9a7e…", "type": "high_temperature", "severity": "CRITICAL",
  "originator": { "type": "DEVICE", "id": "6f1c…", "name": "cold-room-2" },
  "deviceId": "6f1c…", "profileId": "a1b2…", "url": "/admin/iot/alarms/9a7e…",
  "title": "Temperature too high", "startedAt": 1762678800000, "openedAt": 1762678800000,
  "details": { "temperature": 9.4 }, "backfilled": false, "metadata": { "topic": null } } }
{ "event": "alarm.acknowledged", "data": {
  "alarmId": "9a7e…", "type": "high_temperature", "severity": "CRITICAL", "originator": { "…": "…" },
  "deviceId": "6f1c…", "profileId": "a1b2…", "url": "/admin/iot/alarms/9a7e…",
  "ackBy": { "userId": "u-55…" }, "ackAt": 1762679100000 } }
{ "event": "alarm.cleared", "data": {
  "alarmId": "9a7e…", "type": "high_temperature", "severity": "CRITICAL", "originator": { "…": "…" },
  "deviceId": "6f1c…", "profileId": "a1b2…", "url": "/admin/iot/alarms/9a7e…",
  "clearedBy": "rule", "reason": "clear", "clearedAt": 1762680000000, "durationSeconds": 1200,
  "metadata": { "topic": null } } }
  • url is the alarm’s page in the app; put your host and /tenant/<tenantId> in front of it.
  • backfilled: true marks an alarm opened by late readings (for example after a gateway’s buffer was replayed). Nobody is notified for those.
  • clearedBy is "rule" or { "userId": "…" }; reason is clear, manual or originator_deleted. metadata is null when a person cleared it.
  • A change of severity of an open alarm sends no webhook. Alarm webhooks are not throttled; your endpoint’s rate limit still applies.

Commands

EventSent when
rpc.completedA command ended SUCCESSFUL (answered, or a one-way command was delivered)
rpc.failedA command ended FAILED, TIMEOUT (outcome unknown) or EXPIRED (never reached the device)
{ "event": "rpc.failed", "data": {
  "rpcId": "c0de…", "deviceId": "6f1c…", "method": "pump.start", "status": "TIMEOUT",
  "error": null, "source": "api", "completedAt": "2026-11-09T09:00:15.000Z" } }

The answer itself is not in the payload: read it with GET /api/iot/rpc/{rpcId}. A cancelled command sends nothing.

Firmware

EventSent when
ota.update.initiatedA device was given a firmware package to install
ota.update.succeededA device runs the target version
ota.update.failedThe update failed, timed out or rolled back
ota.rollout.wave_completedA wave passed its gates
ota.rollout.pausedA gate failed or a person paused the rollout
ota.rollout.resumedA person resumed it
ota.rollout.completedThe last wave passed its gates
ota.rollout.abortedA person aborted it, its assignment was removed or replaced, or its package was revoked
{ "event": "ota.update.failed", "data": {
  "deviceId": "6f1c…", "packageId": "f00d…", "title": "router-fw", "version": "2.5.0", "fromVersion": "2.4.1",
  "state": "FAILED", "attempts": 1, "at": "2026-11-09T10:00:00.000Z", "stage": "verify", "error": "…" } }

stage is download, verify, update or rollback; error is the device’s code, or a timeout.

{ "event": "ota.rollout.paused", "data": {
  "rolloutId": "r-12…", "assignmentId": "as-3…", "packageId": "f00d…", "status": "PAUSED", "wave": 2, "waves": 3,
  "at": "2026-11-09T11:00:00.000Z", "reason": "failure_gate", "failures": 4, "failurePct": 6.7, "successPct": 88.3 } }

Rollout payloads carry rolloutId, assignmentId, packageId, status, wave (1-based), waves and at, plus: updated, failures, successPct (wave completed); reason and the counts (paused; reason is failure_gate, success_gate, no_result (no device finished) or manual); pausedReason (resumed); updated (completed); reason (aborted).

Provisioning

EventSent when
iot.device.claimedA licensed device was claimed into your organisation
iot.device.releasedA device went back to the factory pool: kind OWNER (you released it), FORCED (platform support) or DEVICE (from the device’s own page)
{ "event": "iot.device.claimed", "data": {
  "deviceId": "6f1c…", "serial": "RT-0017", "model": "…", "claimedAt": "2026-11-09T07:00:00.000Z", "claimedByUserId": "u-55…" } }
{ "event": "iot.device.released", "data": {
  "deviceId": "6f1c…", "serial": "RT-0017", "model": "…", "kind": "OWNER", "releasedAt": "2026-11-09T07:30:00.000Z" } }

Rules

EventSent when
rules.mode_changedA place was put in Manual, went back to Auto, or its Manual ended on its own
rules.needs_attentionA rule was switched off because a device or the place it names is gone
{ "event": "rules.mode_changed", "data": {
  "placeType": "asset", "placeId": "77aa…", "placeName": "Greenhouse 2", "mode": "manual", "change": "manual",
  "actorId": "u-55…", "until": 1762700000000, "reason": "Harvest" } }

change is manual, auto or expired; actorId is null when a Manual ended by itself. A switch back to Auto also carries applyNow and rearmed.

{ "event": "rules.needs_attention", "data": {
  "ruleId": "ru-9…", "name": "Close vents at night", "reason": "…",
  "home": { "type": "asset", "id": "77aa…" } } }

Flows

automation.message is sent only by a flow’s Webhook block, and only to the one endpoint the block names (which must subscribe to “Automation message”). It carries topic, payload, originator, flowId, flowVersion, nodeId, actor and the metadata keys the block lists, with X-Idempotency-Key. See Action blocks.

Other events

  • esim.operation.succeeded and esim.operation.failed belong to eSIM management, which is in development.
  • API key events (api_key.created, api_key.updated, api_key.rotated, api_key.deleted, api_key.expired) and organisation events (members, invitations, subscription) are offered too. Their payloads are not covered on this page.