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.

Method Path Purpose Auth
WS /api/v1/ws Real-time event stream and agent-command channel JWT (see below)
GET /api/v1/ws/stats Returns the count of currently active connections authenticated 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.

type When sent Key fields
connected Immediately after successful auth available_events (array of all EventType values)
event When a subscribed event fires event object (see Event shape)
subscribed After a successful subscribe request subscriptions (confirmed patterns)
unsubscribed After an unsubscribe request subscriptions (removed patterns)
subscription_denied On a subscribe request for a pattern the caller lacks permission for patterns (denied list)
filters_set After a successful set_filters request site_ids
pong In response to a ping timestamp (ISO 8601 server time)
session_revoked When the server detects the session is no longer valid -
error Protocol or validation error message
{
"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 prefix Required permission Notes
device.* device:read Device state changes, adoption events
alert.* alert:read Alert fired/resolved
sla.* analytics:read SLA status updates
controller.* controller:read Controller sync events
discovery.* device:read Scan progress and discovered-device events
vpn.* vpn:read VPN tunnel state
pbx.* device:read PBX sync progress and call-state events
camera.* device:read Camera health and detection events
nvr.* device:read NVR-level events (reboot, recording status)
audit.* audit:read Audit log entries
security.* audit:read Security event stream
settings.* settings:read Platform settings changes
user.* user:read User account events
admin.* super_admin only Administrative events
system.* super_admin only Internal 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

All product names, logos, and brands are property of their respective owners. FreeSDN is an independent project and is not affiliated with or endorsed by the vendors it integrates with. See Trademarks.