discordjs/discord.js · warning · Error

Failed to parse packet

Error message

Failed to parse packet

What it means

After decrypting an RTP packet, parsePacket checks the decryption result; if decrypt returns a falsy value (empty/failed decrypt) it throws this generic Error. A null/empty payload means decryption produced nothing usable — usually a wrong nonce construction, wrong secret key, or a corrupted/short packet — so the packet cannot be yielded as an Opus frame.

Source

Thrown at packages/voice/src/receive/VoiceReceiver.ts:166

		secretKey: Uint8Array,
		userId: string,
		ssrc: number,
	): AudioPacket | null {
		// Parse key RTP Header fields
		const first = rtp.readUint8();
		const hasHeaderExtension = Boolean((first >> 4) & 0x01); // X field
		const cc = first & 0x0f; // CSRC Count field
		const sequence = rtp.readUInt16BE(2);
		const timestamp = rtp.readUInt32BE(4);

		// Compute unencrypted header size: fixed header + CSRC Identifiers + extension header if present
		let headerSize = 12 + 4 * cc;
		const extensionHeaderOffset = headerSize; // where the extension header starts, if present
		if (hasHeaderExtension) headerSize += 4; // extension header (profile ID + length)

		// Decrypt the RTP Payload
		let payload: Buffer = this.decrypt(rtp, mode, nonce, secretKey, headerSize);
		if (!payload) throw new Error('Failed to parse packet');

		// Strip padding (RFC3550 5.1)
		const hasPadding = rtp[0] && Boolean(rtp[0] & 0b100000);
		if (hasPadding) {
			const paddingAmount = payload[payload.length - 1]!;
			if (paddingAmount < payload.length) {
				payload = payload.subarray(0, payload.length - paddingAmount);
			}
		}

		// Skip the decrypted RTP Header Extension data if present
		if (hasHeaderExtension) {
			// Extension Header Length field
			const headerExtensionLength = rtp.readUInt16BE(extensionHeaderOffset + 2);
			payload = payload.subarray(4 * headerExtensionLength);
		}

		// Decrypt payload if in a DAVE session.

View on GitHub (pinned to a81ed8a306)

Solutions

  1. Wrap parsePacket/subscribe handlers so corrupt packets are skipped — individual packet loss is expected in RTP and safe to ignore.
  2. Rejoin the voice channel / recreate the receiver after a reconnect so a fresh secretKey is used.
  3. Update @discordjs/voice to the latest version to get nonce-handling fixes for your negotiated mode.
  4. Check network stability (packet loss, VPN/firewall interference) if a large fraction of packets fail.

Example fix

// before
receiver.subscribe(userId); // throws on corrupt packet, killing the pipeline
// after
const sub = receiver.subscribe(userId);
sub.on('error', (e) => {
  if (e.message === 'Failed to parse packet') return; // skip corrupt packet
  throw e;
});
Defensive patterns

Strategy: try-catch

Validate before calling

// Sanity-check the RTP datagram before parsing:
function isPlausibleRtp(buf: Buffer): boolean {
  return buf.length >= 12 && (buf[0]! >> 6) === 2; // version 2, min header length
}

Type guard

function isRtpPacket(buf: Buffer): boolean {
  return Buffer.isBuffer(buf) && buf.length >= 12 && (buf[0]! & 0xc0) === 0x80;
}

Try / catch

try {
  const opus = receiver.parsePacket(rtp, mode, nonce, secretKey);
} catch (e) {
  if (e.message === 'Failed to parse packet') return; // drop corrupt RTP packet, continue stream
  throw e;
}

Prevention

When it happens

Trigger: Receiving an RTP packet that fails to decrypt to a non-empty buffer: packets arriving with a mismatched nonce padding for the negotiated mode, key/mode mismatch between the connection and receiver, or malformed/truncated datagrams (e.g. headerSize >= datagram length).

Common situations: Flaky networks dropping/corrupting UDP packets; receiving packets from a different voice session after reconnect (stale secret key); older Discord server behavior with nonce suffix modes; non-audio datagrams hitting the socket.

Understand the failure class

Related errors


AI-assisted analysis of discordjs/discord.js@a81ed8a306 (2026-08-30). Data as JSON: /api/errors/e54fa44235619e97. Report an issue: GitHub.