Skip to content

WebSocket API

SnapDog publishes real-time state changes to connected clients over a WebSocket interface. This allows user interfaces to sync instantly without polling HTTP endpoints.


The WebSocket server listens on the same port as the REST API (5555 by default) at the /ws path:

ws://<device-ip>:5555/ws

If Bearer token authentication is enabled in snapdog.toml (via [http].api_keys), you must pass the token as a query parameter:

ws://<device-ip>:5555/ws?token=<your-configured-api-key>

Connections with invalid or missing tokens are immediately rejected with a 401 Unauthorized HTTP handshake response.

  • Heartbeat: The server sends standard WebSocket ping frames periodically and responds to client pings automatically.
  • State bootstrap: Fetch the initial zone/client state through the REST API, then apply WebSocket events as deltas. This avoids losing state when a client reconnects after being offline.
  • Shutdown: When the server is shutting down, clients may receive a normal close frame with code 1001 (Going Away).

All WebSocket messages are JSON objects containing a "type" string that identifies the event.

Event Purpose
zone_changed Full zone playback, source, metadata, playlist navigation, and zone volume update.
zone_volume_changed Lightweight zone volume/mute update.
zone_progress Periodic position and optional buffer progress update.
client_state_changed Client connection, volume, mute, zone assignment, or SnapDog capability update.
zone_presence_changed Presence detection and auto-off timer update.
zone_eq_changed Zone equalizer configuration update.
playback_error Decoder, stream, network, cache, or unsupported audio failure for a zone.

Fired whenever a zone’s playback state, active source, track metadata, playlist position, navigation availability, volume, or mute state changes.

{
"type": "zone_changed",
"zone": 1,
"playback": "playing",
"source": "subsonic_playlist",
"shuffle": false,
"repeat": "off",
"title": "Acoustic Song",
"artist": "The Band",
"album": "Unplugged",
"album_artist": "The Band",
"genre": "Folk",
"year": 2026,
"track_number": 3,
"disc_number": 1,
"duration_ms": 240000,
"position_ms": 12400,
"seekable": true,
"cover_url": "/api/v1/zones/1/cover",
"bitrate_kbps": 320,
"content_type": "audio/flac",
"track_index": 2,
"track_count": 12,
"playlist": 1,
"playlist_name": "Acoustic Evening",
"playlist_total": 4,
"can_playlist_next": true,
"can_playlist_prev": false,
"can_next": true,
"can_prev": true,
"volume": 75,
"muted": false
}

When playback is "playing" or new track metadata arrives, clients should clear any previously displayed playback_error for that zone.

Fired when a zone’s master volume or mute state is altered.

{
"type": "zone_volume_changed",
"zone": 1,
"volume": 80,
"muted": false
}

Fired periodically while playback is active. buffered_ms is included when the stream supports buffer progress reporting.

{
"type": "zone_progress",
"zone": 1,
"position_ms": 42150,
"duration_ms": 240000,
"buffered_ms": 90000
}

Fired when a Snapcast/SnapDog client changes connection state, volume, mute state, assigned zone, or SnapDog capability.

{
"type": "client_state_changed",
"client": 3,
"volume": 55,
"muted": false,
"connected": true,
"zone": 1,
"is_snapdog": true
}

Fired when presence detection, presence-triggered playback enablement, or the auto-off timer state changes.

{
"type": "zone_presence_changed",
"zone": 1,
"presence": true,
"enabled": true,
"timer_active": false
}

Fired when a zone equalizer is enabled, disabled, edited, or assigned a preset.

{
"type": "zone_eq_changed",
"zone": 1,
"enabled": true,
"bands": [
{
"freq": 1000.0,
"gain": 2.5,
"q": 1.0,
"type": "peaking"
}
],
"preset": "vocals"
}

Fired when a zone fails during streaming or decoding. Examples include DNS failures, HTTP timeouts, unsupported HLS encryption, invalid audio containers, unreadable cached files, or decoder probe failures.

Field Type Required Description
type string yes Always playback_error.
zone number yes 1-based zone index that encountered the failure.
message string yes Primary user-facing error message.
details string | null no Optional technical diagnostic detail. The field may be omitted or null.
recoverable boolean yes Whether the server considers the error recoverable without explicit user action.
{
"type": "playback_error",
"zone": 1,
"message": "Failed to probe audio format",
"details": "invalid data found in stream",
"recoverable": false
}

When a playback_error arrives, clients should mark the zone as stopped/idle, clear active track artwork/metadata for that zone, and display the message. The optional details field is intended for expandable technical diagnostics. Clear the displayed error when the user dismisses it, when a later zone_changed event reports "playback": "playing", or when new track metadata is received for the zone.