WebSocket
Stream live sensor readings and alerts over a single WebSocket connection. See the WebSocket Integration guide for a full client example.
Endpoint
wss://api.haltless.io/api/v1/ws/dashboard
Authentication
Connect first, then send your Bearer JWT access token as the first text message. Sending the token in the message body (rather than the URL) keeps it out of server logs and browser history. The server waits 4 seconds for it; if it does not arrive in time, the connection is closed with 4008. A token in the URL is ignored.
const ws = new WebSocket("wss://api.haltless.io/api/v1/ws/dashboard");
ws.onopen = () => {
ws.send(accessToken); // first message must be your JWT
};
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
switch (msg.type) {
case "new_reading": /* … */ break;
case "new_alert": /* … */ break;
case "heartbeat": /* keepalive */ break;
default: /* ignore types you don't know */ break;
}
};
After the token, the connection is one-way: the server ignores anything else the client sends.
What you receive
The stream covers your company, narrowed to what your user may see:
- Only machines within your data scope (the machine groups your role or user is limited to, if any).
new_alertonly if your role can open the Alerts page.
The server re-checks the session every 30 seconds. If your token has expired, or the session was ended elsewhere (sign-out, password change, a deactivated account, a suspended company), the connection is closed with 4001. Changes to your permissions or data scope apply to an open connection without reconnecting.
Close codes
| Code | Meaning | What to do |
|---|---|---|
4001 | Invalid or expired token, or the session was ended | Refresh the JWT, then reconnect |
4008 | Authentication timed out (token not sent within 4 seconds) | Send the token immediately in onopen |
4013 | Server busy | Reconnect after a short, randomized delay |
4029 | Too many connections: too many unauthenticated connections from your address, or your company's connection limit was reached | Back off and retry; share one connection across your app |
Event types
Every message is JSON with a type field. Inspect type first, then read the fields for that event.
new_reading
A new sensor reading was stored for a machine.
{
"type": "new_reading",
"machine_id": "...",
"metric_name": "temperature",
"value": 73.2,
"unit": "celsius",
"timestamp": "2026-08-05T10:30:05Z"
}
Readings that were already more than 2 minutes old when they arrived, such as a backlog an Edge Agent sends after an outage, are not streamed one by one: each batch sends only the newest of them for each machine and metric. Readings of tags not yet assigned to a machine are not streamed.
new_alert
An alert was raised.
{
"type": "new_alert",
"alert_id": "...",
"machine_id": "...",
"severity": "critical",
"message": "Temperature exceeded threshold"
}
heartbeat
Sent every 30 seconds to keep the connection alive and confirm the session is still valid.
{
"type": "heartbeat",
"server_time": "2026-08-05T10:30:00Z"
}
The stream does not carry machine status changes. Every machine's current status is on GET /api/v1/fleet/status (see Fleet); re-read it when a new_alert arrives, or on a timer.
Best practices
- Reconnect with exponential backoff on disconnect.
- Watch for heartbeats; if several are missed, reconnect.
- Refresh your JWT before it expires (access tokens last 30 minutes) and reconnect with the new token.
- Branch on the
typefield before reading event-specific fields, and ignore types you don't recognize.