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
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'
}
});
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.
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
}
}
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.
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
}
})
}
);
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
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
})
}
);
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 );
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
}
});
Check Resume Status
Verify resumption via the response header or ready op: Via WebSocket Response Header: 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.
1. Not Forwarding Voice Events to Lavalink
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:
Send Discord voice state update (op 4)
Wait for voice events from Discord
Forward to Lavalink
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.
Handling Lavalink Server Crashes
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