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_sync event has been replaced by the more secure and flexible client_broadcast event. 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 address

  • name: Client-provided name (or auto-generated Client-{uuid[:8]})

  • type: Client type - one of: controller, visualiser, mobile, display, api, unknown

  • device_id: Optional device identifier provided by client

  • connected_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:

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/clients sync 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 counter

  • type (optional): Client type - one of: controller, visualiser, mobile, display, api, unknown. Defaults to unknown.

  • 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 client

  • name: Final assigned name (may differ from requested if conflict occurred)

  • type: Final assigned type

  • name_conflict: true if name was modified due to conflict, false otherwise

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 to unknown.

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, custom

  • target: Object specifying recipients (see Target Modes below)

  • payload: Dictionary with custom data (max 2048 bytes when JSON-encoded)

Target Modes:

  1. Broadcast to All:

{
  "mode": "all"
}
  1. Broadcast to Client Type:

{
  "mode": "type",
  "value": "visualiser"
}
  1. Broadcast to Specific Names:

{
  "mode": "names",
  "names": ["Living Room Display", "Bedroom Display"]
}
  1. 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 broadcast

  • broadcast_type: Type of broadcast

  • sender_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

  1. 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", ...}
  1. 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"}
  1. 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.