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.


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/ws

Authentication

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

EventPurpose
zone_changedFull zone playback, source, metadata, playlist navigation, and zone volume update.
zone_volume_changedLightweight zone volume/mute update.
zone_progressPeriodic position and optional buffer progress update.
client_state_changedClient connection, volume, mute, zone assignment, or SnapDog capability update.
zone_presence_changedPresence detection and auto-off timer update.
zone_eq_changedZone equalizer configuration update.
playback_errorDecoder, 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.

Next Steps