Skip to content

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.

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": {}
}

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
}
]
}

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": {}
}

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": {}
}

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
}

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
}

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
}
]
}

Every event is an envelope { id, type, tenantId, createdAt, data }; id is monotonic, so a stream resumes from it. The kinds:

KindWhen
device.onlineA device came online (the broker saw it, or it reported after being marked offline).
device.offlineA device went offline (its last will fired, or the heartbeat window passed).
device.telemetryA device published readings. High volume; subscribe deliberately.
command.resultA device acknowledged a command, with its status.
thing.stateA thing’s state changed: after a command, a refresh, or a push from its connector.
notification.raisedA notification was raised in the tenant.
submission.stateOne of the developer’s connector versions changed state in review.
webhook.pingA 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
}

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.

Terminal window
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.

Last updated