Events
What a device and the platform exchange on the broker. The broker is internal and never published: your code receives these messages through webhooks or the event stream on developers.alyt.com, with the same shapes. Every message here is validated by the bridge with the schema shown; anything else is dropped.
Devices speak MQTT on alyt/{tenantId}/{propertyId}/{deviceId}/{channel}. A device’s credential reaches only its own subtree, which is what makes one tenant’s devices invisible to another’s. The machine-readable form of this page is the AsyncAPI 3.0 document at /asyncapi.json, also served live at GET /api/v1/asyncapi.json.
Your code receives these two ways, both through developers.alyt.com, both carrying the same envelope: webhooks and the stream. What arrives is the platform’s own event, not the raw MQTT message: the sections after the channels list every kind and its payload.
status
Section titled “status”The device’s current state, nested by capability id, plus firmware and radio facts. On this channel a device publishes, the platform receives.
Address: alyt/{tenantId}/{propertyId}/{deviceId}/status
Example:
{ "firmware": "1.4.2", "rssi": -58, "light": { "power": true, "brightness": 60 }}Schema
{ "type": "object", "additionalProperties": {}}telemetry
Section titled “telemetry”Readings, one per metric, stored as time series. On this channel a device publishes, the platform receives.
Address: alyt/{tenantId}/{propertyId}/{deviceId}/telemetry
Example:
{ "metrics": { "temperature": 21.4, "humidity": 48 }}Schema
{ "anyOf": [ { "type": "object", "properties": { "metric": { "type": "string", "minLength": 1, "maxLength": 64, "description": "The metric's name, stable per device model: `temperature`, `rssi`, `energy_wh`." }, "value": { "type": [ "number", "null" ] }, "payload": { "type": [ "object", "null" ], "additionalProperties": {} } }, "required": [ "metric" ], "additionalProperties": false }, { "type": "object", "properties": { "metrics": { "type": "object", "additionalProperties": { "type": "number" }, "description": "Several metrics read at once, each a finite number." } }, "required": [ "metrics" ], "additionalProperties": false } ]}events
Section titled “events”Something that happened once: a button, a motion, a door. Counted today, stored when notification-service consumes it. On this channel a device publishes, the platform receives.
Address: alyt/{tenantId}/{propertyId}/{deviceId}/events
Example:
{ "event": "button", "button": 1, "action": "single"}Schema
{ "type": "object", "additionalProperties": {}}alerts
Section titled “alerts”A condition the household should hear about, raised by the device itself. On this channel a device publishes, the platform receives.
Address: alyt/{tenantId}/{propertyId}/{deviceId}/alerts
Example:
{ "alert": "water_leak", "severity": "high"}Schema
{ "type": "object", "additionalProperties": {}}commands
Section titled “commands”A command from the platform, one per pending command-history row. On this channel the platform publishes, a device receives.
Address: alyt/{tenantId}/{propertyId}/{deviceId}/commands
Example:
{ "commandId": "1042", "command": "set", "payload": { "capability": "light", "key": "power", "value": true }}Schema
{ "type": "object", "properties": { "commandId": { "type": "string", "pattern": "^\\d+$", "description": "The command's id as a decimal string; echoed back in the response." }, "command": { "type": "string", "minLength": 1, "maxLength": 64, "description": "The verb the device understands: `set`, `reboot`, `set_volume`, `identify`." }, "payload": { "type": [ "object", "null" ], "additionalProperties": {} } }, "required": [ "commandId", "command", "payload" ], "additionalProperties": false}response
Section titled “response”The device’s answer to a command, by id. On this channel a device publishes, the platform receives.
Address: alyt/{tenantId}/{propertyId}/{deviceId}/response
Example:
{ "commandId": "1042", "command": "set", "status": "ok"}Schema
{ "type": "object", "properties": { "commandId": { "type": "string", "pattern": "^\\d+$" }, "command": { "type": "string", "minLength": 1, "maxLength": 64 }, "status": { "type": "string", "minLength": 1, "maxLength": 32 } }, "additionalProperties": false}availability
Section titled “availability”Online or offline, retained, set by the broker’s last will when the device drops. Retained. On this channel a device publishes, the platform receives.
Address: alyt/{tenantId}/{propertyId}/{deviceId}/availability
Example:
"online"Schema
{ "anyOf": [ { "type": "string", "enum": [ "online", "offline" ] }, { "type": "object", "properties": { "status": { "type": "string", "enum": [ "online", "offline" ] } }, "required": [ "status" ], "additionalProperties": false } ]}What your code receives
Section titled “What your code receives”Every event is an envelope { id, type, tenantId, createdAt, data }; id is monotonic, so a stream resumes from it. The kinds:
| Kind | When |
|---|---|
device.online | A device came online (the broker saw it, or it reported after being marked offline). |
device.offline | A device went offline (its last will fired, or the heartbeat window passed). |
device.telemetry | A device published readings. High volume; subscribe deliberately. |
command.result | A device acknowledged a command, with its status. |
thing.state | A thing’s state changed: after a command, a refresh, or a push from its connector. |
notification.raised | A notification was raised in the tenant. |
submission.state | One of the developer’s connector versions changed state in review. |
webhook.ping | A test delivery the developer asked for from the dashboard. |
device.online payload
{ "type": "object", "properties": { "deviceId": { "type": "string", "format": "uuid" }, "name": { "type": "string" }, "at": { "type": "string", "format": "date-time" } }, "required": [ "deviceId", "name", "at" ], "additionalProperties": false}device.offline payload
{ "type": "object", "properties": { "deviceId": { "type": "string", "format": "uuid" }, "name": { "type": "string" }, "at": { "type": "string", "format": "date-time" }, "reason": { "type": "string", "enum": [ "last-will", "heartbeat" ] } }, "required": [ "deviceId", "name", "at", "reason" ], "additionalProperties": false}device.telemetry payload
{ "type": "object", "properties": { "deviceId": { "type": "string", "format": "uuid" }, "metrics": { "type": "object", "additionalProperties": { "type": "number" } }, "at": { "type": "string", "format": "date-time" } }, "required": [ "deviceId", "metrics", "at" ], "additionalProperties": false}command.result payload
{ "type": "object", "properties": { "deviceId": { "type": "string", "format": "uuid" }, "commandId": { "type": "string" }, "command": { "type": "string" }, "status": { "type": "string" }, "at": { "type": "string", "format": "date-time" } }, "required": [ "deviceId", "commandId", "command", "status", "at" ], "additionalProperties": false}thing.state payload
{ "type": "object", "properties": { "endpointId": { "type": "string", "format": "uuid" }, "integrationId": { "type": "string", "format": "uuid" }, "kind": { "type": "string" }, "state": { "type": "object", "additionalProperties": { "type": "object", "additionalProperties": {} } }, "at": { "type": "string", "format": "date-time" } }, "required": [ "endpointId", "integrationId", "kind", "state", "at" ], "additionalProperties": false}notification.raised payload
{ "type": "object", "properties": { "id": { "type": "string", "format": "uuid" }, "kind": { "type": "string" }, "title": { "type": "string" }, "body": { "type": "string" }, "deviceId": { "type": [ "string", "null" ], "format": "uuid" }, "at": { "type": "string", "format": "date-time" } }, "required": [ "id", "kind", "title", "body", "deviceId", "at" ], "additionalProperties": false}submission.state payload
{ "type": "object", "properties": { "versionId": { "type": "string", "format": "uuid" }, "slug": { "type": "string" }, "semver": { "type": "string" }, "state": { "type": "string" }, "at": { "type": "string", "format": "date-time" } }, "required": [ "versionId", "slug", "semver", "state", "at" ], "additionalProperties": false}webhook.ping payload
{ "type": "object", "properties": { "endpointId": { "type": "string", "format": "uuid" }, "at": { "type": "string", "format": "date-time" } }, "required": [ "endpointId", "at" ], "additionalProperties": false}Webhooks
Section titled “Webhooks”Register an endpoint on the dashboard or with POST /developers/webhooks { url, events }; the answer carries the signing secret once. Each delivery is a POST of the envelope with X-ALYT-Event, X-ALYT-Delivery and X-ALYT-Signature: t=<seconds>,v1=<hex>, where v1 is HMAC-SHA256 with your secret over <t>.<body>. Verify it and refuse a t older than five minutes:
import { createHmac, timingSafeEqual } from "node:crypto";
export function verify(secret: string, body: string, header: string): boolean { const { t, v1 } = Object.fromEntries(header.split(",").map((p) => p.split("="))); if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false; const expected = createHmac("sha256", secret).update(`${t}.${body}`).digest(); const given = Buffer.from(v1, "hex"); return given.length === expected.length && timingSafeEqual(given, expected);}A 2xx within ten seconds is delivered; anything else is retried at 30 s, 1 min, 2 min, doubling to an hour, twenty times over a day. Fifty consecutive failures disable the endpoint and you are mailed; re-enable it from the dashboard. Every delivery, its attempts and its response code are on the dashboard.
The stream
Section titled “The stream”curl -N "https://developers.alyt.com/api/v1/developers/events/stream?events=device.online,device.offline" \ -H "Authorization: Bearer $ALYT_KEY"Server-sent events: event: <kind>, id: <id>, data: <envelope>, a comment every 15 seconds. Resume with after=<id> or the Last-Event-ID header. The key’s scope is events:read; a key hears its tenants, a session its sandbox.