WebSocket API
In addition to the REST APIs LedFx has a WebSocket API for streaming realtime data. The primary use for this is for things like effect visualizations in the frontend.
Will document this further once it is more well defined. The general structure will be event registration based.
The documentation on websockets need a LOT of legacy implementation. New sections are added as new events are commited, but all the pre-existing behaviour is currently not documented
Websocket client UIDs
This functionality is intended to support notification between clients of connect / disconnect and sync actions
sequenceDiagram
participant Client
participant ClientEndpoint as ClientEndpoint (/api/clients)
participant WebSocketMgr as WebsocketConnection
participant EventSystem as Event System
Note over Client,EventSystem: WebSocket Connection Flow
Client->>WebSocketMgr: Establish WebSocket connection
WebSocketMgr->>WebSocketMgr: Extract client IP from request.remote
WebSocketMgr->>WebSocketMgr: Generate UUID for client
WebSocketMgr->>WebSocketMgr: Store UUID to IP mapping thread-safe with map_lock
WebSocketMgr-->>Client: Send JSON {event_type: client_id, client_id: UUID}
WebSocketMgr->>EventSystem: Fire ClientConnectedEvent(UUID, IP)
Note over Client,EventSystem: Client List Retrieval
Client->>ClientEndpoint: GET /api/clients
ClientEndpoint->>WebSocketMgr: get_all_clients()
WebSocketMgr->>WebSocketMgr: Return copy of ip_uid_map thread-safe
WebSocketMgr-->>ClientEndpoint: UUID to IP mapping dictionary
ClientEndpoint-->>Client: HTTP 200 with result client_list
Note over Client,EventSystem: WebSocket Disconnection Flow
Client->>WebSocketMgr: Close WebSocket connection
WebSocketMgr->>WebSocketMgr: Remove UUID from ip_uid_map thread-safe with map_lock
WebSocketMgr->>EventSystem: Fire ClientDisconnectedEvent(UUID, IP)
On opening a websocket connection the client will be assigned a UID stored along with the client IP address. noting that mulitple client can exist on one IP address. Multiple browser tabs for example.
The assigned UID will be returned to the client via an event on the websocket of the following structure
{
"event_type": "client_id",
"client_id": "e59d112e-3652-41e5-acb1-94538b4cb27c"
}
A client can store its own id to enable filtering out of events generated by itself.
Client Events
The following events are available for a client to subscribe to via its websocket
client_connected
Generated when a new client is connected to the backend by its own websocket
{
"event_type": "client_connected",
"client_id": "e59d112e-3652-41e5-acb1-94538b4cb27c",
"client_ip": "1.2.3.4"
}
client_disconnected
Generated when an existing client disconnects its websocket to the backend.
{
"event_type": "client_disconnected",
"client_id": "e59d112e-3652-41e5-acb1-94538b4cb27c",
"client_ip": "1.2.3.4"
}
client_broadcast
Generated when a client broadcasts a message to other clients. See Client Management (Client Metadata & Broadcasting) below for details on the new client broadcasting system.
Note: The legacy
client_syncevent has been replaced by the more secure and flexibleclient_broadcastevent. See Client Sync Migration Guide for migration instructions.
client rest api
The following rest api calls support client tracking
/api/clients
GET
As of this version, GET /api/clients returns full client metadata objects instead of simple IP strings.
Returns metadata for all active websocket clients
{
"823f78cd-24fa-4cd4-908f-979249350dea": {
"ip": "127.0.0.1",
"name": "Living Room Display",
"type": "visualiser",
"device_id": "device-123",
"connected_at": 1708272000.123
},
"34361601-1416-428d-9b89-37c82281222d": {
"ip": "127.0.0.1",
"name": "Controller App",
"type": "controller",
"device_id": null,
"connected_at": 1708272010.789
},
"8743a845-40ba-4427-8ae6-361b2be6fac6": {
"ip": "1.2.3.4",
"name": "Client-8743a845",
"type": "unknown",
"device_id": null,
"connected_at": 1708272020.456
}
}
Metadata Fields:
ip: Client IP addressname: Client-provided name (or auto-generatedClient-{uuid[:8]})type: Client type - one of:controller,visualiser,mobile,display,api,unknowndevice_id: Optional device identifier provided by clientconnected_at: Unix timestamp when client connected
📘 For Client Management: Client metadata management, broadcasting, and related functionality has been moved to its own comprehensive documentation. See:
WebSocket Client API - Full API specification
WebSocket Client Examples - Practical implementation examples
Client Sync Migration Guide - Migration from legacy sync system
Client Management (Client Metadata & Broadcasting)
📘 Quick Start: See WebSocket Client Examples Guide for practical implementation examples.
🔄 Migrating from Legacy? If your code uses the old
POST /api/clientssync action, see Client Sync Migration Guide.
LedFx provides a client management system that allows WebSocket clients to:
Set persistent metadata (name, type, device ID)
Update their display name
Broadcast messages to other connected clients
Receive notifications when client list changes
Client Metadata
Clients can provide metadata about themselves to enable better identification and targeted communication.
set_client_info WebSocket Message
Set client metadata when first connecting. This is typically sent once after receiving the client_id event.
Client → Server:
{
"id": 1,
"type": "set_client_info",
"data": {
"name": "Living Room Display",
"type": "visualiser",
"device_id": "device-123"
}
}
Fields:
name(optional): Display name for this client. If not provided or conflicts with existing name, will auto-generate or append countertype(optional): Client type - one of:controller,visualiser,mobile,display,api,unknown. Defaults tounknown.device_id(optional): Optional device identifier string
Server → Client Response:
{
"id": 1,
"event_type": "client_info_updated",
"client_id": "e59d112e-3652-41e5-acb1-94538b4cb27c",
"name": "Living Room Display",
"type": "visualiser",
"name_conflict": false
}
Response Fields:
client_id: UUID of this clientname: Final assigned name (may differ from requested if conflict occurred)type: Final assigned typename_conflict:trueif name was modified due to conflict,falseotherwise
Name Conflict Handling: If the requested name is already taken, the server will append a counter:
First conflict:
"Display"→"Display (2)"Second conflict:
"Display (2)"→"Display (3)"And so on…
update_client_info WebSocket Message
Update client name and/or type while connected.
Client → Server:
{
"id": 2,
"type": "update_client_info",
"data": {
"name": "Bedroom Display",
"type": "visualiser"
}
}
Fields:
name(optional): New display name for this client. Must be unique across connected clients.type(optional): New client type - one of:controller,visualiser,mobile,display,api,unknown. Invalid types default tounknown.
Server → Client Success:
{
"id": 2,
"event_type": "client_info_updated",
"client_id": "e59d112e-3652-41e5-acb1-94538b4cb27c",
"name": "Bedroom Display",
"type": "visualiser"
}
Server → Client Error (Name Taken):
{
"id": 2,
"success": false,
"error": {
"message": "Name 'Bedroom Display' is already taken by another client"
}
}
clients_updated Event
Generated when client list or metadata changes (connect, disconnect, name update).
{
"event_type": "clients_updated"
}
Usage:
Subscribe to this event to be notified when to refetch the client list via GET /api/clients.
Client-to-Client Broadcasting
Clients can broadcast messages to other connected clients through the server. The server ensures sender identity is authenticated and cannot be spoofed.
broadcast WebSocket Message
Broadcast a message to other clients based on target specification.
Client → Server:
{
"id": 3,
"type": "broadcast",
"data": {
"broadcast_type": "scene_sync",
"target": {
"mode": "type",
"value": "visualiser"
},
"payload": {
"scene_id": "party-mode",
"action": "activate"
}
}
}
Required Fields:
broadcast_type: Type of broadcast - one of:visualiser_control,scene_sync,color_palette,customtarget: Object specifying recipients (see Target Modes below)payload: Dictionary with custom data (max 2048 bytes when JSON-encoded)
Target Modes:
Broadcast to All:
{
"mode": "all"
}
Broadcast to Client Type:
{
"mode": "type",
"value": "visualiser"
}
Broadcast to Specific Names:
{
"mode": "names",
"names": ["Living Room Display", "Bedroom Display"]
}
Broadcast to Specific UUIDs:
{
"mode": "uuids",
"uuids": ["823f78cd-24fa-4cd4-908f-979249350dea", "34361601-1416-428d-9b89-37c82281222d"]
}
Server → Client Success:
{
"id": 3,
"event_type": "broadcast_sent",
"broadcast_id": "b-e7a3f2d1-4cd4-45e1-9b89-37c82281222d",
"targets_matched": 2,
"target_uuids": [
"823f78cd-24fa-4cd4-908f-979249350dea",
"34361601-1416-428d-9b89-37c82281222d"
]
}
Server → Client Error (No Targets):
{
"id": 3,
"success": false,
"error": {
"message": "No clients matched target specification: {\"mode\": \"type\", \"value\": \"nonexistent\"}"
}
}
Server → Client Error (Payload Too Large):
{
"id": 3,
"success": false,
"error": {
"message": "Payload size (2500 bytes) exceeds maximum (2048 bytes)"
}
}
client_broadcast Event
Generated when a client broadcasts a message.
Important - Client-Side Filtering Required:
This event is sent to ALL clients subscribed to client_broadcast, regardless of the broadcast target specification. Clients MUST check if their UUID is in the target_uuids array before processing the broadcast. This is a client-side filtering pattern - the server does not selectively send to specific WebSocket connections.
{
"event_type": "client_broadcast",
"broadcast_id": "b-e7a3f2d1-4cd4-45e1-9b89-37c82281222d",
"broadcast_type": "scene_sync",
"sender_uuid": "8743a845-40ba-4427-8ae6-361b2be6fac6",
"sender_name": "Controller App",
"sender_type": "controller",
"target_uuids": [
"823f78cd-24fa-4cd4-908f-979249350dea",
"34361601-1416-428d-9b89-37c82281222d"
],
"payload": {
"scene_id": "party-mode",
"action": "activate"
}
}
Event Fields:
broadcast_id: Server-generated unique ID for this broadcastbroadcast_type: Type of broadcastsender_uuid: UUID of the client that sent the broadcast (server-authenticated)sender_name: Display name of sender (server-derived)sender_type: Type of sender client (server-derived)target_uuids: List of UUIDs that matched the target specification (clients MUST check if their UUID is in this list)payload: Custom data from sender
Critical: target_uuids is a filter list, not a delivery restriction. All subscribers receive this event.
Important Security Note:
All sender identity fields (sender_uuid, sender_name, sender_type) are derived by the server from the authenticated WebSocket connection and cannot be spoofed by the client.
Usage Example:
// Subscribe to broadcasts
websocket.send(JSON.stringify({
id: 1,
type: "subscribe_event",
event_type: "client_broadcast"
}));
// Handle incoming broadcasts
websocket.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.event_type === "client_broadcast") {
// REQUIRED: Check if this broadcast is intended for us
// All subscribers receive this event - we must filter client-side
if (!data.target_uuids.includes(myClientId)) {
return; // Not for us - ignore
}
// Optional: Filter out our own broadcasts
if (data.sender_uuid === myClientId) {
return; // We sent this - ignore
}
// Now process the broadcast
handleBroadcast(data.broadcast_type, data.payload);
}
};
Example Client Flow
Connect and Register:
// Receive client ID
{"event_type": "client_id", "client_id": "uuid..."}
// Set metadata
{"id": 1, "type": "set_client_info", "data": {"name": "My App", "type": "controller"}}
// Receive confirmation
{"id": 1, "event_type": "client_info_updated", ...}
Subscribe to Events:
// Subscribe to client list changes
{"id": 2, "type": "subscribe_event", "event_type": "clients_updated"}
// Subscribe to broadcasts
{"id": 3, "type": "subscribe_event", "event_type": "client_broadcast"}
Send Broadcast:
// Broadcast to all visualisers
{
"id": 4,
"type": "broadcast",
"data": {
"broadcast_type": "color_palette",
"target": {"mode": "type", "value": "visualiser"},
"payload": {"colors": ["#ff0000", "#00ff00", "#0000ff"]}
}
}
System Events
System events are loosely related events emitted when changes are made that effect the system as a whole. e.g. global pause, system configuration changes, audio input device changes, etc.
global_pause
The global_pause event is emitted when the global paused state is changed. This state can be changed via the Web UI, the REST API, or via a command line argument.
Payload Example:
{
"event_type": "global_pause",
"paused": true
}
Fields:
event_type: Always"global_pause".paused: Current paused state.
base_config_update
The base_config_update event is emitted when the system configuration is updated.
Payload Example:
{
"event_type": "base_config_update",
"config": {
// TODO
}
}
Fields:
event_type: Always"base_config_update".config: The updated system configuration.
audio_input_device_changed
The audio_input_device_changed event is emitted when the an audio reactive effect is enabled and the configured audio device is changed.
Payload Example:
{
"event_type": "audio_input_device_changed",
"audio_input_device_name": "ALSA: default"
}
Fields:
event_type: Always"audio_input_device_changed".audio_input_device_name: The friendly name of the selected audio device.
audio_device_list_changed
The audio_device_list_changed event is emitted when the system audio device list changes (devices are added or removed). LedFx uses the audio-hotplug library to automatically detect device changes and refresh the internal audio device list.
Payload Example:
{
"event_type": "audio_device_list_changed"
}
Fields:
event_type: Always"audio_device_list_changed".
Usage:
When this event is received, the frontend should re-fetch the device list via /api/audio/devices to update the UI. The backend has already refreshed its internal device cache, so the API will return the current device list.
colors_updated
The colors_updated event is emitted when user-defined colors or gradients are added, modified, or deleted via the /api/colors endpoint.
Payload Example:
{
"event_type": "colors_updated"
}
Fields:
event_type: Always"colors_updated".
Usage:
Subscribe to this event to be notified when the colors or gradients collection changes so the frontend can refetch the updated list via GET /api/colors. This ensures the UI color pickers and gradient selectors stay synchronized with the backend.
Virtuals Events
Virtuals events allow a client to be notified of changes to a virtual’s active state or configuration.
virtual_pause
The virtual_pause event is emitted when the active state of a virtual changes.
Payload Example:
{
"event_type": "virtual_pause",
"virtual_id": "my_virtual_id",
"paused": true
}
Fields:
event_type: Always"virtual_pause".virtual_id: Identifier of the virtual entity.paused: Current paused state of virtual entity.
virtual_update
The virtual_update event is emitted when a virtual’s pixels are updated.
Payload Example:
{
"event_type": "virtual_update",
"virtual_id": "my_virtual_id",
"pixels": [
// Array of pixel state
]
}
Fields:
event_type: Always"virtual_update".virtual_id: Identifier of the virtual entity.pixels: Array of current pixel state.
virtual_config_update
The virtual_config_update event is emitted when a virtual’s configuration is updated.
Payload Example:
{
"event_type": "virtual_config_update",
"virtual_id": "my_virtual_id",
"config" : {
// TODO
}
}
Fields:
event_type: Always"virtual_config_update".virtual_id: Identifier of the virtual entity.config: Configuration of the virtual entity.
Effect Events
Effect events allow a client to be notified of changes to the active effect and it’s configuration.
effect_set
The effect_set event is emitted when a new effect is set on a virtual. This event may be emitted when changing certain effect configurations when the update triggers a transition.
Payload Example:
{
"event_type": "effect_set",
"effect_name": "Energy 2",
"effect_id": "energy2-1",
"virtual_id": "my_virtual_id",
"effect_type": "energy2",
"active": true,
"streaming": false
}
Fields:
event_type: Always"effect_set".effect_name: Friendly name of the effect.effect_id: Identifier of the effect instance (includes instance suffix if multiple instances exist).virtual_id: Identifier of the virtual entity.effect_type: Type of the effect without the instance suffix.active: Whether the virtual is currently active/playing.streaming: Whether the virtual is currently streaming.
effect_updated
The effect_updated event is emitted when a virtual’s active effect’s configuration changes.
Payload Example:
{
"event_type": "effect_updated",
"effect_id": "effect_id",
"virtual_id": "my_virtual_id"
}
Fields:
event_type: Always"effect_updated".effect_id: Identifier of the effect.virtual_id: Identifier of the virtual entity.
effect_cleared
The effect_cleared event is emitted when an effect is removed from a virtual.
Payload Example:
{
"event_type": "effect_cleared",
"virtual_id": "my_virtual_id"
}
Fields:
event_type: Always"effect_cleared".virtual_id: Identifier of the virtual entity.
Diagnostic Events
Diagnostic events are intended to allow improved visibility of performance and development diagnostic via live front end rather than log analysis.
virtual_diag is generated by any virtual running an effect that support advanced / diag and is set to true. At time of writing that is only and all 2d matrix effects. Once established and tested it will be added to all 1d effects as well.
The example given in the following sequence diagram for general_diag is a trivial use case added to noise2d. It will be removed in due course when a better more relevant example is available.
sequenceDiagram
participant V as Virtual Device
participant E as Effect (with LogSec)
participant E2 as Effect (noise2d)
participant LS as LogSec Monitor
participant ES as Events System
participant WS as WebSocket Connection
participant FE as Frontend Client
Note over V,FE: Virtual Diag Event Flow
V->>+E: Execute effect in thread
E->>+LS: log_sec() - Start frame timing
LS->>LS: Track FPS, render times
loop Every Frame
E->>LS: Render frame
LS->>LS: Measure render time
LS->>LS: Update min/max/total times
end
LS->>LS: Check if second boundary crossed
alt Second boundary crossed & diag enabled
LS->>ES: fire_event(VirtualDiagEvent)
Note right of LS: Contains virtual_id fps r_avg r_min r_max cycle sleep phy
ES->>WS: notify_websocket(event)
WS->>FE: send_event(virtual_diag)
Note right of FE: Real-time performance metrics
end
Note over V,FE: General Diag Event Flow
V->>+E2: Execute noise2d effect
alt Test mode enabled
E2->>E2: draw_test() - Draw diagnostic pattern
E2->>ES: fire_event(GeneralDiagEvent)
Note right of E2: Contains debug message for Noise2d width x height
ES->>WS: notify_websocket(event)
WS->>FE: send_event(general_diag)
Note right of FE: Diagnostic message for debugging
end
Note over V,FE: WebSocket Subscription Setup
FE->>WS: subscribe_event("virtual_diag")
WS->>ES: add_listener(notify_websocket, "virtual_diag")
FE->>WS: subscribe_event("general_diag")
WS->>ES: add_listener(notify_websocket, "general_diag")
Note over V,FE: Event Distribution System
Note over V,FE: Event Distribution - fire_event calls all registered listeners via loop.call_soon_threadsafe
virtual_diag
The virtual_diag WebSocket event is emitted when a virtual’s diagnostics are updated. It provides real-time performance and timing metrics in units of seconds for a specific virtual entity on a 1 second period
Payload Example:
{
"event_type": "virtual_diag",
"virtual_id": "my_virtual_id",
"fps": 48,
"r_avg": 0.017432,
"r_min": 0.008123,
"r_max": 0.022345,
"cycle": 16.67,
"sleep": 0.014232,
"phy":
{
"fps": 55,
"ver": "0.14.4",
"n": 1024,
"name": "32x32",
"rssi": -45,
"qual": 100
}
}
Fields:
event_type: Always"virtual_diag".virtual_id: Identifier of the virtual entity.fps: Frames per second being rendered.r_avg: Average render time.r_min: Minimum render time.r_max: Maximum render time.cycle: Cycle time for the virtual’s update loop.sleep: Sleep time between cycles.phy: A dictionary containing physical device information:fps: Frames per second reported by the physical device.ver: Firmware version of the device.n: Number of LEDs or pixels in the device.name: Name or identifier of the device.rssi: Signal strength (RSSI) of the device’s connection.qual: Connection quality percentage.
Usage: Subscribe to this event to monitor the performance and timing of virtual devices for diagnostics and optimization.
If a virtual is mapped directly to a device and that device is WLED, then an attempt to read the WLED info and extra key details will be made once per second asynchronously via the /json/info api call and returned on phy, unavailable values will be None.
general_diag
The general_diag WebSocket event is emitted to provide arbitrary diagnostic messages, typically for debugging or informational purposes.
Payload Example:
{
"event_type": "general_diag",
"debug": "Diagnostic message here",
"scroll": false
}
Fields:
event_type: Always"general_diag".debug: The diagnostic message string.scroll: Boolean indicating if the message should be scrolled in the UI oe replaced in each cycle.
Usage: Listen for this event to receive general diagnostic messages from the system, which may be useful for debugging or displaying system status in the frontend. If a monospaced font is used then back end can attempt table live updates with scroll set to false which is default if not explicitly set.