Skip to content

WebSockets

FreeSDN exposes a persistent WebSocket connection at /api/v1/ws that delivers every platform event in real time: device state changes, alert firings, scan progress, agent heartbeats, camera detections, VoIP call events, automation completions, and more. The same transport carries commands to connected agents. This page covers the two WS endpoints, the authentication handshake, the message protocol, subscription filtering, and the security model the server enforces on each connection.

MethodPathPurposeAuth
WS/api/v1/wsReal-time event stream and agent-command channelJWT (see below)
GET/api/v1/ws/statsReturns the count of currently active connectionsauthenticated HTTP

The stats endpoint is a plain HTTP GET - use it for dashboards or health checks when you want to know how many clients are connected without opening a socket yourself.

Use any standards-compliant WebSocket client. The URL scheme is wss:// in production and ws:// in development:

wss://your-domain/api/v1/ws

The server performs two checks during connection setup:

  1. Origin validation - for browser connections the Origin header must match CORS_ORIGINS or be same-origin. Connections that fail this check are closed immediately with code 1008 before accept() is called. Agent connections (non-browser) are not subject to the Origin check.
  2. Connection caps - the server enforces MAX_WS_PER_USER=25 concurrent connections per user account and MAX_WS_GLOBAL=5000 across all tenants. Exceeding either cap closes the new connection with code 1013 (Try Again Later). When the connection authenticates via cookie or query-string token the cap is enforced before accept(). When the connection authenticates via the auth-message frame, accept() must be called first (so the server can receive the frame); if the cap is exceeded after the auth message arrives the already-upgraded socket is closed with 1013.

You must authenticate within 10 seconds of the connection being accepted. Three mechanisms are supported, in order of preference:

Send a JSON frame immediately after the socket opens:

{"type": "auth", "token": "<JWT access token>"}

The server validates the token with the same verify_token logic used by the REST API. On success it sends a connected frame listing all available event types. On failure it closes the connection.

If the browser already has the freesdn_access httpOnly cookie set (from a prior /api/v1/auth/login call), the cookie is read during the upgrade handshake automatically. No extra frame is needed.

wss://your-domain/api/v1/ws?token=<JWT>

The authenticated principal attached to the connection holds the user’s user_id, org_id, role, permission list, and token_version. The token_version is checked at connection time and re-validated every five minutes (see Session revalidation below).

All messages are JSON objects with a type field that identifies the message kind.

typeWhen sentKey fields
connectedImmediately after successful authavailable_events (array of all EventType values)
eventWhen a subscribed event firesevent object (see Event shape)
subscribedAfter a successful subscribe requestsubscriptions (confirmed patterns)
unsubscribedAfter an unsubscribe requestsubscriptions (removed patterns)
subscription_deniedOn a subscribe request for a pattern the caller lacks permission forpatterns (denied list)
filters_setAfter a successful set_filters requestsite_ids
pongIn response to a pingtimestamp (ISO 8601 server time)
session_revokedWhen the server detects the session is no longer valid-
errorProtocol or validation errormessage
{
"type": "event",
"event": {
"event_type": "device.state_changed",
"category": "devices",
"priority": "normal",
"organization_id": "<uuid>",
"payload": {
"site_id": "<uuid or null>",
"..."
}
}
}

Payloads are sanitized before delivery: the server strips any field whose name appears in the _SENSITIVE_PAYLOAD_KEYS set - password, hashed_password, secret, api_key, api_secret, client_secret, token, access_token, refresh_token, private_key, ssh_key, mfa_secret, mfa_backup_codes, credentials, cookie, session_token, and encryption_key - from every outgoing payload, regardless of nesting depth.

Send these frames after authentication. Frames arrive at a rate limiter capped at five messages per second; exceeding that closes the connection with code 1008.

{
"type": "subscribe",
"subscriptions": [
"device.*",
"alert.fired",
"discovery.*"
]
}

Patterns may use wildcards. Each pattern must be 200 characters or fewer. The server checks every requested pattern against the permission map (see Subscription permissions) and responds with a subscribed frame for allowed patterns and a subscription_denied frame for any that are denied. You can hold up to MAX_SUBSCRIPTIONS_PER_CONN=200 active subscriptions per connection.

{"type": "unsubscribe", "subscriptions": ["alert.fired"]}

Narrow the event stream to one or more sites. The server validates each site_id against your organisation membership and your UserSiteAccess grants - any site you cannot read is rejected silently.

{"type": "set_filters", "site_ids": ["<uuid>", "<uuid>"]}
{"type": "ping"}

The server responds with {"type": "pong", "timestamp": "<ISO 8601 datetime>"}. Use this to keep the connection alive through idle-timeout proxies and to measure round-trip latency.

Not every role can subscribe to every event stream. The server maps subscription patterns to permission requirements in app/core/ws_rbac.py. The general principle mirrors the REST permission model:

Event prefixRequired permissionNotes
device.*device:readDevice state changes, adoption events
alert.*alert:readAlert fired/resolved
sla.*analytics:readSLA status updates
controller.*controller:readController sync events
discovery.*device:readScan progress and discovered-device events
vpn.*vpn:readVPN tunnel state
pbx.*device:readPBX sync progress and call-state events
camera.*device:readCamera health and detection events
nvr.*device:readNVR-level events (reboot, recording status)
audit.*audit:readAudit log entries
security.*audit:readSecurity event stream
settings.*settings:readPlatform settings changes
user.*user:readUser account events
admin.*super_admin onlyAdministrative events
system.*super_admin onlyInternal system events

The viewer role carries device:read, alert:read, controller:read, vpn:read, analytics:read, and audit:read by default, giving read-only users a broad real-time view that includes device state, alerts, controller sync events, VPN tunnel state, SLA updates, audit log entries, and security events. Only user.* (requires user:read), settings.* (requires settings:read), admin.*, and system.* streams are out of reach for viewers. Unknown prefixes (for example network.*, cameras.*, voip.*, firewall.*, agent.*, automation.*) are denied by default - the server sends a subscription_denied frame and the subscription is silently rejected.

  • Authentication - obtain a JWT access token to use in the auth-message frame
  • Authentication - obtain a short-lived JWT access token via POST /api/v1/auth/login for use in non-interactive WS clients (API key WS support is not yet available)
  • Using the API - CSRF, rate limits, and error shapes that also apply to WebSocket clients
  • Agents - agent registration, task dispatch, and the REST endpoints that complement the WS channel