NodeLink
Advanced

Voice Receive (Relay)

Receive real-time audio from Discord voice channels via NodeLink.

Voice Receive (Relay)

NodeLink supports an experimental feature that allows you to receive and relay audio frames from Discord voice channels to your application via a binary WebSocket.

Experimental Feature

This feature is currently experimental and may undergo breaking changes in the protocol. Use with caution in production.

Configuration

To enable voice receive, you must configure it in your config.js or via environment variables.

// config.js
voiceReceive: {
  enabled: true,
  format: "opus" // "opus" or "pcm_s16le" (48kHz, Stereo)
}
  • format: opus sends raw opus frames (low bandwidth). pcm_s16le decodes audio on the server and sends 16-bit Little Endian PCM (high bandwidth).

Connecting

Voice receive uses a separate WebSocket endpoint:

WS /v4/websocket/voice/{guildId}

You must provide the same Authorization and User-Id headers as the main WebSocket.

Binary Protocol

The Voice Relay uses a custom binary framing to minimize overhead. Unlike the main WebSocket, frames are sent as raw binary data, not JSON.

Frame Structure

Each packet consists of a header followed by the audio payload.

OffsetLengthTypeDescription
01UInt8Opcode: 1 (Start), 2 (Stop), 3 (Data)
11UInt8Format: 0 (Opus), 2 (PCM)
21UInt8Guild ID Length (L1)
3L1StringGuild ID
3+L11UInt8User ID Length (L2)
4+L1L2StringUser ID
4+L1+L24UInt32BESSRC (Discord Sync Source)
8+L1+L24UInt32BETimestamp
12+L1+L2VarBinaryPayload (Audio Data)

Opcodes

  • 1 (Start): Sent when a user starts speaking. Payload is empty.
  • 2 (Stop): Sent when a user stops speaking. Payload is empty.
  • 3 (Data): Sent for every audio frame received.

Implementation Example (Node.js)

const WebSocket = require('ws');

const ws = new WebSocket('ws://localhost:3000/v4/websocket/voice/123456789', {
  headers: {
    'Authorization': 'youshallnotpass',
    'User-Id': '987654321',
    'Client-Name': 'MyBot/1.0.0'
  }
});

ws.on('message', (data) => {
  if (!(data instanceof Buffer)) return;

  const op = data.readUInt8(0);
  const format = data.readUInt8(1);
  
  let offset = 2;
  const guildLen = data.readUInt8(offset++);
  const guildId = data.toString('utf8', offset, offset + guildLen);
  offset += guildLen;

  const userLen = data.readUInt8(offset++);
  const userId = data.toString('utf8', offset, offset + userLen);
  offset += userLen;

  const ssrc = data.readUInt32BE(offset);
  offset += 4;
  const timestamp = data.readUInt32BE(offset);
  offset += 4;

  const payload = data.slice(offset);

  if (op === 3) {
    // Process audio payload
    console.log(`Received ${payload.length} bytes from ${userId}`);
  }
});

On this page