Skip to main content

WebSocket Integration Guide

Build real-time dashboards and monitoring tools with the Haltless WebSocket API. This guide provides complete implementation examples for connecting, authenticating, handling messages, and reconnecting gracefully.

Connection overview

Client Haltless
│ │
│──── WebSocket connect ────────>│
│ │
│──── Send JWT token ──────────>│ (first message)
│ │
│<──── heartbeat ───────────────│ (periodic)
│<──── sensor_update ───────────│
│<──── alert ───────────────────│
│<──── machine_status ──────────│

The WebSocket streams all real-time events for your tenant: sensor readings, alerts, and machine status changes.

Quick start

JavaScript / TypeScript

const WS_URL = "wss://api.haltless.io/api/v1/ws/dashboard";

function connect(accessToken: string) {
const ws = new WebSocket(WS_URL);

ws.onopen = () => {
// Authenticate by sending the JWT as the first message
ws.send(accessToken);
console.log("Connected and authenticated");
};

ws.onmessage = (event) => {
const data = JSON.parse(event.data);

switch (data.type) {
case "heartbeat":
// Connection is alive
break;
case "sensor_update":
console.log(
`${data.machine_id}: ${data.metric_name} = ${data.value} ${data.unit}`
);
break;
case "alert":
console.log(
`[${data.severity.toUpperCase()}] ${data.message}`
);
break;
case "machine_status":
console.log(
`Machine ${data.machine_id}: ${data.old_status}${data.new_status}`
);
break;
}
};

ws.onclose = (event) => {
console.log(`Disconnected: code=${event.code}`);
};

ws.onerror = (error) => {
console.error("WebSocket error:", error);
};

return ws;
}

Python

import asyncio
import json
import websockets

WS_URL = "wss://api.haltless.io/api/v1/ws/dashboard"

async def connect(access_token: str):
async with websockets.connect(WS_URL) as ws:
# Authenticate
await ws.send(access_token)
print("Connected and authenticated")

async for raw_message in ws:
data = json.loads(raw_message)

if data["type"] == "sensor_update":
print(f"{data['machine_id']}: {data['metric_name']} = {data['value']}")
elif data["type"] == "alert":
print(f"[{data['severity']}] {data['message']}")
elif data["type"] == "machine_status":
print(f"Machine {data['machine_id']}: {data['old_status']}{data['new_status']}")

asyncio.run(connect("your-jwt-token"))

Authentication

The JWT token must be sent as the first text message after the WebSocket connection opens. This keeps the token out of URL query strings and server access logs.

Do not pass the token as a query parameter:

# ❌ Don't do this , token appears in server logs
wss://api.haltless.io/api/v1/ws/dashboard?token=eyJhbG...

# ✅ Do this , send token as first message after connect
ws.send(accessToken);

Authentication timeout

If the token is not sent within the timeout window, the server closes the connection with code 4002. Send the token immediately in your onopen handler.

Close codes

CodeMeaningAction
4001Invalid or expired tokenRefresh the JWT and reconnect
4002Authentication timeoutSend token faster in onopen
4003Too many pending connectionsBack off and retry

Message types

Heartbeat

Sent periodically by the server to keep the connection alive.

{
"type": "heartbeat",
"server_time": "2026-04-04T10:30:00Z"
}

Track heartbeats to detect stale connections. If you miss several consecutive heartbeats, proactively reconnect.

Sensor update

A new sensor reading was ingested (from an edge agent or direct ingestion).

{
"type": "sensor_update",
"machine_id": "uuid",
"metric_name": "temperature",
"value": 73.2,
"unit": "celsius",
"timestamp": "2026-04-04T10:30:05Z"
}

Alert triggered

An alert rule's threshold was crossed.

{
"type": "alert",
"alert_id": "uuid",
"machine_id": "uuid",
"severity": "critical",
"message": "Temperature exceeded threshold",
"metric_value": 87.3,
"threshold_value": 85.0,
"created_at": "2026-04-04T10:30:05Z"
}

Machine status change

A machine's health status changed (e.g., from healthy to warning).

{
"type": "machine_status",
"machine_id": "uuid",
"old_status": "healthy",
"new_status": "warning",
"timestamp": "2026-04-04T10:30:05Z"
}

Reconnection with exponential backoff

Network interruptions are inevitable. Implement automatic reconnection with backoff:

TypeScript

class HaltlessWebSocket {
private ws: WebSocket | null = null;
private reconnectAttempts = 0;
private maxReconnectDelay = 30_000; // 30 seconds
private accessToken: string;

constructor(accessToken: string) {
this.accessToken = accessToken;
this.connect();
}

private connect() {
this.ws = new WebSocket("wss://api.haltless.io/api/v1/ws/dashboard");

this.ws.onopen = () => {
this.ws!.send(this.accessToken);
this.reconnectAttempts = 0; // Reset on successful connection
console.log("WebSocket connected");
};

this.ws.onmessage = (event) => {
const data = JSON.parse(event.data);
this.handleMessage(data);
};

this.ws.onclose = (event) => {
console.log(`WebSocket closed: ${event.code}`);

if (event.code === 4001) {
// Token expired , refresh before reconnecting
this.refreshTokenAndReconnect();
return;
}

this.scheduleReconnect();
};

this.ws.onerror = () => {
// onerror is always followed by onclose, so reconnection
// is handled there
};
}

private scheduleReconnect() {
const delay = Math.min(
1000 * Math.pow(2, this.reconnectAttempts),
this.maxReconnectDelay
);
console.log(`Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts + 1})`);

setTimeout(() => {
this.reconnectAttempts++;
this.connect();
}, delay);
}

private async refreshTokenAndReconnect() {
// Call your auth refresh endpoint
const response = await fetch("https://api.haltless.io/api/v1/auth/refresh", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ refresh_token: "YOUR_REFRESH_TOKEN" }),
});
const { access_token } = await response.json();
this.accessToken = access_token;
this.connect();
}

private handleMessage(data: any) {
switch (data.type) {
case "heartbeat":
break;
case "sensor_update":
// Update your dashboard charts
break;
case "alert":
// Show alert notification
break;
case "machine_status":
// Update machine status indicators
break;
}
}

disconnect() {
this.ws?.close();
}
}

// Usage
const ws = new HaltlessWebSocket("your-jwt-token");

Python

import asyncio
import json
import websockets

WS_URL = "wss://api.haltless.io/api/v1/ws/dashboard"

async def connect_with_reconnect(access_token: str):
reconnect_attempts = 0
max_delay = 30

while True:
try:
async with websockets.connect(WS_URL) as ws:
await ws.send(access_token)
reconnect_attempts = 0
print("Connected")

async for message in ws:
data = json.loads(message)
handle_message(data)

except (websockets.ConnectionClosed, ConnectionError) as e:
delay = min(2 ** reconnect_attempts, max_delay)
print(f"Disconnected: {e}. Reconnecting in {delay}s...")
await asyncio.sleep(delay)
reconnect_attempts += 1

def handle_message(data: dict):
msg_type = data["type"]
if msg_type == "sensor_update":
print(f"{data['metric_name']}: {data['value']} {data['unit']}")
elif msg_type == "alert":
print(f"[{data['severity']}] {data['message']}")
elif msg_type == "machine_status":
print(f"Status: {data['old_status']}{data['new_status']}")

asyncio.run(connect_with_reconnect("your-jwt-token"))

React integration

Use the WebSocket in a React dashboard with a custom hook:

import { useEffect, useRef, useCallback, useState } from "react";

interface SensorUpdate {
type: "sensor_update";
machine_id: string;
metric_name: string;
value: number;
unit: string;
timestamp: string;
}

interface AlertEvent {
type: "alert";
alert_id: string;
machine_id: string;
severity: string;
message: string;
}

type WsMessage = SensorUpdate | AlertEvent | { type: "heartbeat" | "machine_status"; [key: string]: any };

export function useHaltlessWebSocket(accessToken: string | null) {
const wsRef = useRef<WebSocket | null>(null);
const [lastMessage, setLastMessage] = useState<WsMessage | null>(null);
const [isConnected, setIsConnected] = useState(false);

const connect = useCallback(() => {
if (!accessToken) return;

const ws = new WebSocket("wss://api.haltless.io/api/v1/ws/dashboard");

ws.onopen = () => {
ws.send(accessToken);
setIsConnected(true);
};

ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.type !== "heartbeat") {
setLastMessage(data);
}
};

ws.onclose = () => {
setIsConnected(false);
// Reconnect after 2 seconds
setTimeout(connect, 2000);
};

wsRef.current = ws;
}, [accessToken]);

useEffect(() => {
connect();
return () => wsRef.current?.close();
}, [connect]);

return { lastMessage, isConnected };
}

// Usage in a component
function LiveDashboard({ accessToken }: { accessToken: string }) {
const { lastMessage, isConnected } = useHaltlessWebSocket(accessToken);

useEffect(() => {
if (!lastMessage) return;

if (lastMessage.type === "sensor_update") {
// Update chart data
} else if (lastMessage.type === "alert") {
// Show toast notification
}
}, [lastMessage]);

return (
<div>
<span>{isConnected ? "🟢 Live" : "🔴 Disconnected"}</span>
{/* Your dashboard components */}
</div>
);
}

Performance tips

  1. Filter client-side , The WebSocket sends all tenant events. Filter by machine_id in your message handler to show only relevant data.
  2. Throttle UI updates , Sensor updates can arrive many times per second. Batch updates using requestAnimationFrame or a throttle function to avoid excessive re-renders.
  3. Use a single connection , Open one WebSocket per browser tab, not one per component. Share the connection via React context or a global store.
  4. Monitor heartbeats , Track the last heartbeat timestamp. If it's older than 3× the heartbeat interval, reconnect proactively.

Next steps