Skip to content

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:

EventMeaning
OBJECT_UPDATEDA device reported property values — the live data. Route it to your state via the service CRN.
META_STATUS_CHANGEConnectivity changed: payload carries status and lastMessage.
META_SET / META_DELETEDevice metadata was replaced / cleared.
OBJECT_UPDATE_REQUESTA 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

SymptomCause / fix
Websocket closes immediatelyThe ticket was already used or older than 30 seconds. Fetch a new one per connect.
Connected, but no eventsExpected while no device reports. Confirm the account has read access to the devices.
Property ids don't match between REST and socketREST uses hex strings ("0x0800"), socket payloads decimal numbers (2048). Convert once at the edge.