WebSockets
Composer ships an embedded WebSocket server that pushes real-time events to subscribed clients and accepts a small set of command messages. This page covers the connection model, the message envelope, the catalogue of message types, and the subscription channels.
Note — not available on Core licences
The WebSocket server is disabled at startup under a Vindral Composer Core (free) licence, regardless of the
EnableWebSocketssetting. No listener is opened on the configured port and clients will fail to connect. See Activating a license for licence-tier details.
Connection
| Property | Value |
|---|---|
| URL | ws://{host}:{port}/ |
| Default host | localhost (configurable; see the Web API tab in Settings) |
| Default port | 8081 |
| Protocol | RFC 6455 WebSocket; UTF-8 JSON text frames |
| TLS | Not provided directly — terminate at a reverse proxy if needed |
The server is off by default. Enable it via the EnableWebSockets setting (see the WebSockets section of the settings.xml reference). Restart Composer for the change to take effect — the WebSocket server is started during application startup.
On a successful handshake the server immediately sends a Welcome message containing the connection's assigned Guid. Clients should keep that GUID and quote it back in subsequent commands so the server can route replies.
Authentication
The WebSocket server does not perform API-key authentication itself. Treat its port as trusted: bind to localhost for local-only access, or restrict the listening interface to a private network and front it with a TLS-terminating proxy (nginx, Caddy, etc.) that handles auth.
Use the HTTP API's WebSocket management endpoints to enumerate or disconnect connected clients on demand — those are protected by the same apikey mechanism as the rest of the HTTP API.
Message envelope
Every frame sent in either direction is a JSON object with this shape:
{
"Type": "PropertyChanged",
"Content": "<payload — usually a JSON string or human-readable string>",
"Guid": "<connection / receiver guid>",
"ObjectId": "<related object guid, or all-zero if not applicable>",
"DateTime": "2026-04-29T13:42:18.0000000Z"
}
Type is one of the values in the table below (case-sensitive). Content is the actual payload — a plain string for system messages, a JSON-encoded inner document for PropertyChanged / MultiplePropertiesChanged / ComponentLogUpdate, etc. Guid is the connection's identity (server-assigned in the Welcome message) and ObjectId references a Composer object when relevant.
Message types
Defined in WebSocketServer.Enums.SocketMessageType.
| Type | Direction | Description |
|---|---|---|
Welcome |
server → client | First message after handshake; carries the server-assigned connection GUID. |
System |
both | Generic system messages — server heartbeats, ping/pong, ack strings. |
Subscribe |
client → server | Subscribe to one of the channels listed in the next section. |
Unsubscribe |
client → server | Cancel a subscription. Content is the channel name (or All). |
PropertyChanged |
server → client | One property on one object changed. Content is a JSON document with object id, property name, new value. |
MultiplePropertiesChanged |
server → client | Several properties changed in the same tick — coalesced for efficiency. |
SetPropertyValueByObjectId |
client → server | Request a property value change. Content is a JSON document with the target object id, property name, and value. |
ExecuteCommandByObjectId |
client → server | Invoke a Command property on a target object. |
AudioMixerSummary |
server → client | Periodic audio-mixer levels snapshot for clients subscribed to AudioMixer. |
LogMessage |
server → client | Log entry for clients subscribed to LogFile. |
ThumbnailUpdate |
server → client | New input thumbnail for clients subscribed to thumbnail pushes. |
ComponentLogUpdate |
server → client | Component log entry for clients subscribed to ComponentLogs. |
SystemInformationUpdate |
server → client | Periodic CPU / memory / GPU stats for clients subscribed to SystemInformation. |
Error |
server → client | Server-side error in response to a malformed or rejected client message. |
Setting a property value
Send a SetPropertyValueByObjectId message to change a property on a component. Content is a JSON-encoded string whose decoded form names the target object, the property, and the new value:
{
"ObjectId": "<target object guid>",
"PropertyName": "Mute",
"Value": "true"
}
The server stamps the incoming frame with the connection's own Guid, so clients don't need to set it. There is no reply on success; a rejected or malformed request comes back as an Error message. Read the applied value back from the live AudioMixerSummary (for audio properties) or via the HTTP API's /api/getproperty.
Most properties take a scalar Value — a number, string, enum name, or true/false.
Collection properties
A handful of properties hold a collection rather than a scalar, so their Value is a structured string rather than a plain number/string. The encoding is property-specific. The collection properties writable over the WebSocket today are:
| Property | Value format |
|---|---|
PreFaderSendConfigurations |
JSON array of send-slot objects (the same shape the HTTP API's /api/setproperty accepts and /api/getproperty returns) — worked example below. |
AudioChannelOutputMappings |
Comma-separated {audioStripId}:{gain}:{channelMapping} triples, e.g. b2e5afc4-…:0:OneTwo,d4ea516f-…:-6:ThreeFour, where channelMapping is an AudioChannelMapping enum name. |
Read the current value back from the live AudioMixerSummary (it serialises each audio property in the same string form), or — for PreFaderSendConfigurations — via the HTTP API's /api/getproperty.
PreFaderSendConfigurations is an input's pre-fader audio sends; each array element is one send slot:
| Field | Meaning |
|---|---|
Active |
Whether this slot routes audio. |
TargetAudioStripId |
GUID of the destination audio channel strip (all-zero when inactive). |
InputAudioSendType |
0 = pre-fader (tapped before the source fader), 1 = post-fader. |
TargetCh01..TargetCh08 |
Per-source-channel routing: 0 = none, 1..8 = destination channel. |
The four send slots are fixed per input; to clear the sends, send four slots with "Active": false rather than an empty array.
Because the collection value is itself a JSON string, it ends up JSON-encoded twice — once as Value, and again inside the Content document. Building the message with JSON.stringify keeps the nesting correct:
// Route the input's audio pre-fader to two audio strips (slots 3-4 left inactive).
const sends = [
{ Active: true, TargetAudioStripId: "b2e5afc4-d29f-4f72-b87b-c42045e73054",
InputAudioSendType: 0, TargetCh01: 1, TargetCh02: 2, TargetCh03: 0, TargetCh04: 0,
TargetCh05: 0, TargetCh06: 0, TargetCh07: 0, TargetCh08: 0 },
{ Active: true, TargetAudioStripId: "d4ea516f-ab03-4d4c-b24d-bacbf92d7bd8",
InputAudioSendType: 0, TargetCh01: 1, TargetCh02: 2, TargetCh03: 0, TargetCh04: 0,
TargetCh05: 0, TargetCh06: 0, TargetCh07: 0, TargetCh08: 0 },
{ Active: false, TargetAudioStripId: "00000000-0000-0000-0000-000000000000",
InputAudioSendType: 0, TargetCh01: 0, TargetCh02: 0, TargetCh03: 0, TargetCh04: 0,
TargetCh05: 0, TargetCh06: 0, TargetCh07: 0, TargetCh08: 0 },
{ Active: false, TargetAudioStripId: "00000000-0000-0000-0000-000000000000",
InputAudioSendType: 0, TargetCh01: 0, TargetCh02: 0, TargetCh03: 0, TargetCh04: 0,
TargetCh05: 0, TargetCh06: 0, TargetCh07: 0, TargetCh08: 0 }
];
ws.send(JSON.stringify({
Type: "SetPropertyValueByObjectId",
Content: JSON.stringify({
ObjectId: inputGuid, // the input that owns the sends
PropertyName: "PreFaderSendConfigurations",
Value: JSON.stringify(sends) // the collection value is itself a JSON string
})
}));
The decoded Content for the first slot above looks like this (the remaining slots follow the same shape):
{
"ObjectId": "06b44dd8-01eb-4040-9239-8e3e0012a1e0",
"PropertyName": "PreFaderSendConfigurations",
"Value": "[{\"Active\":true,\"TargetAudioStripId\":\"b2e5afc4-d29f-4f72-b87b-c42045e73054\",\"InputAudioSendType\":0,\"TargetCh01\":1,\"TargetCh02\":2,\"TargetCh03\":0,\"TargetCh04\":0,\"TargetCh05\":0,\"TargetCh06\":0,\"TargetCh07\":0,\"TargetCh08\":0}, ...]"
}
Subscription channels
Send a Subscribe message with Content set to one of these values to start receiving the matching push type. Channel names are case-insensitive on subscribe; Unsubscribe accepts the same names plus the wildcard All.
| Channel | Pushed message types | Notes |
|---|---|---|
AudioMixer |
AudioMixerSummary |
Throttled to the rate set by WebSocketThrottledPropertiesFrequency. |
LogFile |
LogMessage |
Streams every log entry written by Composer's logger. Volume can be high under debug logging — consider scoping. |
Components |
PropertyChanged for non-audio [ApiProperty] properties |
Every API-exposed property of an input/operator/target that isn't an audio-mixer value — run state, source URL, errors, and so on. Audio-level properties stream on AudioMixer instead. |
Switcher |
PropertyChanged, pre-filtered to switchers |
A Switcher's important properties — PGM / Preview source, blend / fade-to-black progress, transition durations — from any origin. Same data as the switcher SSE topic; the animated progress values are throttled to ~10 Hz. |
ComponentLogs |
ComponentLogUpdate |
Per-component log entries (the same lines you'd see in the Desktop log panel for that component). |
SystemInformation |
SystemInformationUpdate |
Periodic snapshot — CPU, RAM, GPU, frame budget, etc. |
| Thumbnails | ThumbnailUpdate |
Pushed implicitly when an input registers for thumbnail updates; not a separate Subscribe channel today. |
The set of properties the Components and AudioMixer channels deliver is defined by attributes, not a fixed list: a property is broadcast when it carries [ApiProperty]. It streams on AudioMixer when it is also marked [AudioProperty] (audio-mixer levels) and on Components otherwise. Each component's reference page in the Inputs, Operators, and Targets manuals badges its [ApiProperty] properties with the channel they stream on, so those per-component property tables are the authoritative list of what each subscription delivers.
An
Unsubscribemessage withContent: "All"cancels every subscription on the connection.
Lifecycle events
The Runtime API broadcasts coarse lifecycle events (project start/stop/reload, runtime exit, etc.) to all WebSocket clients via the RuntimeEvent message type. The full event list lives on the Runtime API WebSocket Events page.
Configuration
All WebSocket-related settings live under the WebSockets section of settings.xml:
EnableWebSockets— master on/off switch.WebSocketsHostNameandWebSocketsPort— listening interface and port.WebSocketsMaxConnectedSubscribers— connection cap (default 1).WebSocketsMaxIncomingMessageQueueLength/WebSocketsMaxOutgoingMessageQueueLength— back-pressure tuning.WebSocketThrottledPropertiesFrequency— how often coalesced pushes (audio mixer, property changes) emit.WebSocketMsgAudioDecimals/WebSocketMsgAudioRmsDecimals— precision of audio level values to keep payloads small.
Quick start (browser JavaScript)
const ws = new WebSocket('ws://localhost:8081/');
ws.addEventListener('open', () => {
// Subscribe to system info pushes.
ws.send(JSON.stringify({ Type: 'Subscribe', Content: 'SystemInformation' }));
});
ws.addEventListener('message', (event) => {
const msg = JSON.parse(event.data);
switch (msg.Type) {
case 'Welcome':
console.log('Connected as', msg.Guid);
break;
case 'SystemInformationUpdate':
console.log('System info:', JSON.parse(msg.Content));
break;
case 'Error':
console.warn('Server error:', msg.Content);
break;
}
});
Related documentation
- HTTP API › WebSocket Management — list and disconnect connected clients via HTTP.
- Runtime API › WebSocket Events — full catalogue of
RuntimeEventbroadcasts. - Script Engine › WebSockets — sending WebSocket messages from a project script.
- Settings — the Web API tab covers WebSocket-related options through the UI; the settings.xml reference lists every WebSocket-section property.