Appearance
WebSocket & Events
Every state change in the backend is an event. Over a websocket you receive the ones you are allowed to read — property updates, status changes, metadata edits — and apply them on top of your initial REST snapshot.
Connecting
A websocket upgrade cannot carry a DPoP-bound token, so authentication happens in two steps:
text
GET /api/v1/socket/ticket → { "ticket": "…" } (authenticated, DPoP)
WSS /api/v1/socket?ticket=… (no auth header)The ticket is single-use and expires after 30 seconds — fetch a fresh one for every connect, including every reconnect.
js
import WebSocket from "ws";
const { ticket } = await (await client.fetch(
"GET", "https://<<URL>>/api/v1/socket/ticket", { accessToken })).json();
const ws = new WebSocket(`wss://<<URL>>/api/v1/socket?ticket=${encodeURIComponent(ticket)}`);
ws.on("message", (raw) => {
const event = JSON.parse(raw.toString());
if (event.type === "HEALTHCHECK_PONG") return;
console.log(event.type, event.resourceIdentifier);
});There is no subscribe message. Once connected you receive every event of your tenant that your account may read; the access snapshot taken when the ticket was issued decides what that is — reconnect to pick up access changes.
The event envelope
Each event is one JSON object — type, the CRN of the affected resource, and a type-specific payload:
json
{
"type": "OBJECT_UPDATED",
"resourceIdentifier": "crn#tenant:Ba9mN3pQ.device:A1B2C3D4.service:00000000-0000-2001-8003-006d0099ab53",
"payload": {
"updates": [ { "propId": 2048, "tag": "value", "value": 220, "measuredAt": 1756800000000 } ],
"transmittedAt": 1756800000123,
"measuredAt": 1756800000000
}
}(Abridged — each update also carries the property's type and modifier, in the same shapes as the REST expand fields.)
Events worth handling in a typical application:
| Event | Meaning |
|---|---|
OBJECT_UPDATED | A device reported property values — the live data. Route it to your state via the service CRN. |
META_STATUS_CHANGE | Connectivity changed: payload carries status and lastMessage. |
META_SET / META_DELETE | Device metadata was replaced / cleared. |
OBJECT_UPDATE_REQUEST | A write toward a device was requested — the echo of a property write, yours or anyone's. |
The module reference documents every event a module emits — see the Events section of each module page.
propId is decimal here
In websocket payloads propId is a decimal number (2048), while REST uses hex strings ("0x0800"). Convert once at the edge: `0x${propId.toString(16).padStart(4, "0").toUpperCase()}`. Also check tag: only "value" updates carry a value — the others are "null", "error" and "stream".
Heartbeat and reconnects
Send {"type":"HEALTHCHECK_PING"} periodically; the server replies {"type":"HEALTHCHECK_PONG"}. A connection that stops answering is dead even if the socket looks open — terminate and reconnect with a fresh ticket. The example's src/socket.js implements ping every 30 s, reconnect with exponential backoff.
Updates arrive when devices report them — a quiet fleet means a quiet stream.
Troubleshooting
| Symptom | Cause / fix |
|---|---|
| Websocket closes immediately | The ticket was already used or older than 30 seconds. Fetch a new one per connect. |
| Connected, but no events | Expected while no device reports. Confirm the account has read access to the devices. |
| Property ids don't match between REST and socket | REST uses hex strings ("0x0800"), socket payloads decimal numbers (2048). Convert once at the edge. |