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.
1. Connection & Security
The WebSocket server listens on the same port as the REST API (5555 by default) at the /ws path:
ws://<device-ip>:5555/wsAuthentication
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.
Connection Lifecycle
- 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).
2. Event Frames
All WebSocket messages are JSON objects containing a "type" string that identifies the event.
Event Summary
| 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. |
zone_changed
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.
zone_volume_changed
Fired when a zone’s master volume or mute state is altered.
{ "type": "zone_volume_changed", "zone": 1, "volume": 80, "muted": false}zone_progress
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}client_state_changed
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}zone_presence_changed
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}zone_eq_changed
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"}playback_error
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.
{ "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.