Skip to main content

Required Discord Library Capabilities

Before implementing a Lavalink client, ensure your Discord library supports these essential features:
You must be able to send messages via a shard’s gateway connection to initiate voice connections.
You must be able to intercept and handle these Discord events on your shard connection:
  • VOICE_SERVER_UPDATE - Provides connection endpoint and token
  • VOICE_STATE_UPDATE - Provides session ID and channel information

Connection Flow

1

Establish WebSocket Connection

Connect to Lavalink’s WebSocket endpoint at /v4/websocket with required headers:
const ws = new WebSocket('ws://localhost:2333/v4/websocket', {
  headers: {
    'Authorization': 'youshallnotpass',
    'User-Id': '170939974227541168',
    'Client-Name': 'my-client/1.0.0'
  }
});
2

Receive Ready Event

Wait for the ready op to confirm connection and get your session ID:
{
  "op": "ready",
  "resumed": false,
  "sessionId": "xtaug914v9k5032f"
}
Store the sessionId - you’ll need it for REST API calls and resuming.
3

Request Voice Channel Join

Send a voice state update to Discord via your gateway connection:
{
  "op": 4,
  "d": {
    "guild_id": "817327181659111454",
    "channel_id": "817327181659111457",
    "self_mute": false,
    "self_deaf": false
  }
}
4

Intercept Discord Voice Events

Capture the voice server update and voice state update from Discord:Voice Server Update:
{
  "t": "VOICE_SERVER_UPDATE",
  "d": {
    "token": "voice-token-here",
    "guild_id": "817327181659111454",
    "endpoint": "us-east1234.discord.media"
  }
}
Voice State Update:
{
  "t": "VOICE_STATE_UPDATE",
  "d": {
    "guild_id": "817327181659111454",
    "channel_id": "817327181659111457",
    "user_id": "170939974227541168",
    "session_id": "session-id-here"
  }
}
You need: token, endpoint, session_id, and channel_id.
5

Forward Voice Data to Lavalink

Update the Lavalink player with the voice connection details:
const response = await fetch(
  `http://localhost:2333/v4/sessions/${sessionId}/players/${guildId}`,
  {
    method: 'PATCH',
    headers: {
      'Authorization': 'youshallnotpass',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      voice: {
        token: voiceToken,
        endpoint: voiceEndpoint,
        sessionId: voiceSessionId
      }
    })
  }
);
6

Load and Play Tracks

Load a track and start playback:
// Load tracks
const loadResult = await fetch(
  'http://localhost:2333/v4/loadtracks?identifier=ytsearch:never gonna give you up',
  {
    headers: { 'Authorization': 'youshallnotpass' }
  }
);
const data = await loadResult.json();

// Play the first track
await fetch(
  `http://localhost:2333/v4/sessions/${sessionId}/players/${guildId}`,
  {
    method: 'PATCH',
    headers: {
      'Authorization': 'youshallnotpass',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      track: {
        encoded: data.data[0].encoded
      }
    })
  }
);

Resuming Sessions

Resuming allows you to reconnect to Lavalink without interrupting playback.

How Resuming Works

When resuming is disabled:
  • All voice connections close immediately on disconnect
  • All player state is lost
When resuming is enabled:
  • Music continues playing during disconnection
  • Events are queued and replayed upon reconnection
  • You can resume control of existing players

Enable Resuming

1

Configure Session for Resuming

Call the Update Session endpoint after connecting:
await fetch(
  `http://localhost:2333/v4/sessions/${sessionId}`,
  {
    method: 'PATCH',
    headers: {
      'Authorization': 'youshallnotpass',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      resuming: true,
      timeout: 60  // seconds before session expires
    })
  }
);
2

Store Session ID

Persist the session ID so you can resume after a crash or restart:
// Store in memory, database, or file
sessionStorage.set('lavalink-session-id', sessionId);
3

Resume on Reconnect

Include the Session-Id header when reconnecting:
const ws = new WebSocket('ws://localhost:2333/v4/websocket', {
  headers: {
    'Authorization': 'youshallnotpass',
    'User-Id': '170939974227541168',
    'Client-Name': 'my-client/1.0.0',
    'Session-Id': storedSessionId  // Resume previous session
  }
});
4

Check Resume Status

Verify resumption via the response header or ready op:Via WebSocket Response Header:
Session-Resumed: true
Via Ready Op:
{
  "op": "ready",
  "resumed": true,
  "sessionId": "xtaug914v9k5032f"
}
If resumed is true, all queued events will be replayed.

Common Pitfalls

These are the most common mistakes when implementing a Lavalink client. Review this list carefully if you encounter connection or playback issues.
Problem: You must intercept VOICE_SERVER_UPDATE and VOICE_STATE_UPDATE events and send them to Lavalink. Solution: Extract endpoint, token, and session_id from Discord events and forward them via the Update Player endpoint.
// Listen for Discord voice events
discordClient.on('VOICE_SERVER_UPDATE', (data) => {
  updateLavalinkPlayer(data.guild_id, {
    voice: {
      token: data.token,
      endpoint: data.endpoint,
      sessionId: currentSessionId  // from VOICE_STATE_UPDATE
    }
  });
});

2. Not Joining a Voice Channel Before Playing

Problem: Attempting to play audio without first establishing a voice connection. Solution: Always join a voice channel before loading tracks:
  1. Send Discord voice state update (op 4)
  2. Wait for voice events from Discord
  3. Forward to Lavalink
  4. Load and play tracks

3. Creating Voice Connections with Discord Library

Problem: Trying to manage voice connections directly with your Discord library while using Lavalink. Solution: Let Lavalink handle all voice connection logic. Your Discord library should only:
  • Send the initial voice state update (op 4)
  • Forward voice events to Lavalink
  • Not create its own voice connections

4. Ignoring Debug Logs

Problem: Not checking Lavalink server logs when issues occur. Solution: Check /logs/debug.log on your Lavalink server for detailed error information.
When Lavalink crashes (e.g., SIGKILL), your client must clean up voice connections.
If the Lavalink server suddenly dies, send this event to Discord to disconnect from voice channels:
{
  "op": 4,
  "d": {
    "guild_id": "GUILD_ID_HERE",
    "channel_id": null,
    "self_mute": false,
    "self_deaf": false
  }
}

Shard Connection Dependencies

When your Discord gateway shard connection dies, all associated Lavalink audio connections are terminated. This applies to both normal disconnections and resume attempts.
Maintain a stable Discord gateway connection to ensure uninterrupted audio playback.

Event Handling

Listen for WebSocket events to respond to player state changes:
ws.on('message', (data) => {
  const payload = JSON.parse(data);

  switch (payload.op) {
    case 'ready':
      console.log('Connected with session:', payload.sessionId);
      break;

    case 'playerUpdate':
      console.log('Player position:', payload.state.position);
      break;

    case 'event':
      switch (payload.type) {
        case 'TrackStartEvent':
          console.log('Track started:', payload.track.info.title);
          break;

        case 'TrackEndEvent':
          if (payload.reason === 'finished') {
            // Load next track
          }
          break;

        case 'TrackExceptionEvent':
          console.error('Track error:', payload.exception);
          break;

        case 'WebSocketClosedEvent':
          console.error('Voice connection closed:', payload.code, payload.reason);
          break;
      }
      break;
  }
});

Search Functionality

Lavalink supports searching on multiple platforms:
// YouTube search
const ytResults = await loadTracks('ytsearch:rickroll');

// YouTube Music search
const ytmResults = await loadTracks('ytmsearch:lofi hip hop');

// SoundCloud search
const scResults = await loadTracks('scsearch:remix');

// Direct URL
const directTrack = await loadTracks('https://www.youtube.com/watch?v=dQw4w9WgXcQ');
Search prefixes only work if the corresponding source managers are enabled on the Lavalink server.

Best Practices

Session Management

Enable resuming for production bots to handle reconnections gracefully

Error Handling

Always handle TrackExceptionEvent and WebSocketClosedEvent

State Tracking

Store session IDs and player state for crash recovery

Event Queuing

Implement a queue system for track management based on TrackEndEvent

Next Steps

Authentication

Secure your Lavalink connection

REST API

Full REST API reference

WebSocket API

Complete WebSocket documentation

Filters

Audio effects and filters

Build docs developers (and LLMs) love