Chat Adapter for Baileys

Error Handling and Validation

Errors you may hit with the Baileys WhatsApp adapter and how to handle them.

This guide explains the errors you might encounter when using the WhatsApp adapter and how to handle them gracefully.


Understanding ValidationError

The adapter throws ValidationError (from @chat-adapter/shared) when you pass invalid arguments or call methods in the wrong state. These errors have:

  • A clear message explaining what went wrong
  • The adapter name as context
  • Suggestions for fixing the issue

Always wrap adapter calls in try-catch blocks when dealing with user input or external data:

import { ValidationError } from "@chat-adapter/shared";

bot.onSubscribedMessage(async (thread, message) => {
  try {
    await requireBaileysAdapter(thread).sendLocation(
      thread.id,
      999,  // Invalid latitude
      -122.4194
    );
  } catch (err) {
    if (err instanceof ValidationError) {
      // Send a friendly error to the user
      await thread.post("Invalid coordinates. Latitude must be between -90 and 90.");
      return;
    }
    throw err; // Re-throw unexpected errors
  }
});

Common validation scenarios

Adapter name contains colon

When it happens: Creating an adapter with : in the adapterName.

Why: Thread IDs use adapterName:encodedJid format, so : is reserved.

// This throws immediately
const wa = createBaileysAdapter({
  adapterName: "baileys:main",  // ❌ Invalid, contains ":"
  auth: { state, saveCreds },
});
// ValidationError: Invalid adapterName "baileys:main". ":" is not allowed.

Fix: Use hyphens or underscores instead:

adapterName: "baileys-main"  // ✅ Valid

Socket not connected

When it happens: Calling any method that requires an active connection before connect(), before the WhatsApp socket is open, or after disconnect().

Affected methods: All sending methods (postMessage, reply, sendPoll, etc.) plus markRead, setPresence, fetchGroupParticipants.

const wa = createBaileysAdapter({ auth: { state, saveCreds } });
// Forgot to call wa.connect()

await wa.setPresence("available");
// ValidationError: Socket not connected. Call adapter.connect() and wait for WhatsApp to open first.

Fix: Ensure proper startup sequence:

await bot.initialize();
await wa.connect();  // Start the WebSocket

// ✅ Use setPresence only after WhatsApp is open, for example from an
// already-running command handler or your own connection-open integration.
await wa.setPresence("available");

Invalid thread ID format

When it happens: Passing a malformed thread ID to decodeThreadId() or any method that accepts thread IDs.

Why: Thread IDs must follow adapterName:base64url(jid) format.

// Wrong adapter name prefix
wa.decodeThreadId("slack:MTU1NTEyMzQ1NjdAcy53aGF0c2FwcC5uZXQ");
// ValidationError: Invalid Baileys thread ID: slack:MTU1...

Fix: Only use thread IDs that came from the adapter itself (via handlers or openDM).


Reply message belongs to different adapter

When it happens: Calling reply() with a message that came from a different adapter instance.

Common in: Multi-account setups where you accidentally mix up waMain and waSales.

const waMain = createBaileysAdapter({ adapterName: "main", auth: authMain });
const waSales = createBaileysAdapter({ adapterName: "sales", auth: authSales });

bot.onSubscribedMessage(async (thread, message) => {
  // If message came from waSales, this will fail:
  await waMain.reply(message, "Got it!");
  // ValidationError: reply: message belongs to adapter "sales", not "main"
});

Fix: Use the thread's attached adapter instead of a hardcoded reference:

bot.onSubscribedMessage(async (thread, message) => {
  const wa = requireBaileysAdapter(thread);  // ✅ Gets the right adapter
  await wa.reply(message, "Got it!");
});

Reply message thread mismatch

When it happens: The message's thread ID doesn't match its raw JID, which usually indicates data corruption or manual thread ID construction.

// Rare: only happens with corrupted data
wa.reply(corruptedMessage, "text");
// ValidationError: reply: message threadId does not match the quoted message JID

Fix: Don't manually construct thread IDs. Use openDM() or thread IDs from handlers.


Invalid location coordinates

When it happens: Passing out-of-range latitude or longitude to sendLocation().

await wa.sendLocation({
  threadId,
  latitude: 999,
  longitude: -122.4194,
});
// ValidationError: sendLocation: latitude must be between -90 and 90. Received 999.

await wa.sendLocation({
  threadId,
  latitude: 37.7749,
  longitude: 999,
});
// ValidationError: sendLocation: longitude must be between -180 and 180. Received 999.

Fix: Validate coordinates before calling:

function isValidLat(lat: number): boolean {
  return Number.isFinite(lat) && lat >= -90 && lat <= 90;
}

Invalid poll configuration

When it happens: sendPoll() receives bad parameters.

IssueError message
Empty questionsendPoll: question must not be empty.
Too few optionssendPoll: WhatsApp polls require between 2 and 12 options. Received 1.
Too many optionsSame as above, when > 12
Empty option stringsendPoll: poll options must not be empty.
Bad selectableCountsendPoll: selectableCount must be an integer >= 0. Received -1.
await wa.sendPoll({
  threadId,
  question: "",
  options: ["A", "B"],
});
// ValidationError: sendPoll: question must not be empty.

await wa.sendPoll({
  threadId,
  question: "Question?",
  options: ["Only one option"],
});
// ValidationError: WhatsApp polls require between 2 and 12 options. Received 1.

Fix: Validate user input before creating polls:

async function createSafePoll(threadId: string, question: string, options: string[]) {
  const trimmedQuestion = question.trim();
  const validOptions = options.map(o => o.trim()).filter(o => o.length > 0);
  
  if (trimmedQuestion.length === 0) {
    throw new Error("Question cannot be empty");
  }
  if (validOptions.length < 2 || validOptions.length > 12) {
    throw new Error("Need 2-12 valid options");
  }
  
  return wa.sendPoll({
    threadId,
    question: trimmedQuestion,
    options: validOptions,
  });
}

Not a group thread

When it happens: Calling fetchGroupParticipants() on a DM thread.

// thread.isDM is true
await wa.fetchGroupParticipants(threadId);
// ValidationError: fetchGroupParticipants: thread is not a group

Fix: Check thread.isDM first:

if (!thread.isDM) {
  const participants = await wa.fetchGroupParticipants(thread.id);
  // ...
}

Message has no remote JID

When it happens: Extremely rare: the raw Baileys message is missing its JID field when calling reply().

wa.reply(incompleteMessage, "text");
// ValidationError: reply: message has no remoteJid

This indicates corrupted or synthetic message data. Ensure you're only replying to genuine incoming messages from handlers.


Context doesn't belong to a Baileys adapter

When it happens: Calling requireBaileysAdapter() with a thread or adapter from a different platform (e.g., passing a Slack thread to a WhatsApp adapter).

Common in: Multi-adapter bots where handler logic accidentally mixes up adapter types.

// Assuming 'thread' came from a Slack adapter, not WhatsApp
const wa = requireBaileysAdapter(thread);
// ValidationError: This context does not belong to a Baileys adapter.

Fix: Ensure you're using the correct adapter for the context. Use isBaileysAdapter() to check first if unsure:

if (isBaileysAdapter(thread.adapter)) {
  const wa = requireBaileysAdapter(thread);
  // ... use WhatsApp-specific methods
}

Internal: sendMessage returned no message

When it happens: Extremely rare: Baileys' sendMessage() returns undefined, usually due to a network or protocol failure.

// Rare internal error
await wa.postMessage(threadId, "Hello");
// ValidationError: sendMessage returned no message.

This indicates a serious Baileys or network failure. Retry the operation or check your connection. If persistent, check Baileys logs for underlying issues.


WebSocket errors (not ValidationError)

Baileys emits WebSocket errors that aren't ValidationError instances. These indicate network or protocol issues rather than input validation problems.

Common WebSocket error scenarios

SituationWhat happensHow to handle
Connection dropped unexpectedlyBaileys auto-reconnects automaticallyNo action needed; the adapter handles this
Logged out (status code 401)Session invalidated from WhatsApp appDelete saved credentials and re-authenticate with a fresh QR scan
Network timeoutTemporary connection lossThe adapter will retry with exponential backoff
Server restart (code 515)Expected during QR scan handshakeThe adapter reconnects automatically to complete authentication

Handling logged-out errors

When the bot is logged out from the WhatsApp app (code 401), you need to clear credentials and restart:

import fs from "fs";

function onLoggedOut() {
  console.warn("Bot was logged out. Delete auth_info/ and restart.");
  fs.rmSync("./auth_info", { recursive: true, force: true });
  process.exit(1);
}

See Events and Lifecycle for detailed reconnection behavior and how to monitor connection state.


Best practices for error handling

Distinguish validation from unexpected errors

bot.onSubscribedMessage(async (thread, message) => {
  try {
    const wa = requireBaileysAdapter(thread);
    await wa.sendLocation({
      threadId,
      latitude: lat,
      longitude: lng,
    });
  } catch (err) {
    if (err instanceof ValidationError) {
      // User input problem, tell them
      await thread.post(`Invalid input: ${err.message}`);
      return;
    }
    
    // System problem: log and maybe alert
    console.error("Unexpected error:", err);
    await thread.post("Something went wrong. Please try again.");
  }
});

Pre-validate user input

Don't let validation errors reach the adapter; check first:

function validatePhoneNumber(num: string): boolean {
  return /^\d{10,15}$/.test(num.replace(/\D/g, ""));
}

Handle missing attachments gracefully

Not an error, but worth checking:

if (!attachment.fetchData) {
  await thread.post("This attachment can't be downloaded.");
  return;
}

Log validation errors for debugging

} catch (err) {
  if (err instanceof ValidationError) {
    console.warn("Validation failed:", err.message);
    // Handle gracefully...
  }
}

Error message reference

MethodValidation errors
constructorInvalid adapterName "X". ":" is not allowed.
replymessage belongs to adapter "X", not "Y", message has no remoteJid, message threadId does not match the quoted message JID
markReadSocket not connected (via _requireSocket)
sendLocationlatitude must be between -90 and 90, longitude must be between -180 and 180
sendPollquestion must not be empty, between 2 and 12 options, options must not be empty, selectableCount must be an integer >= 0
fetchGroupParticipantsthread is not a group
decodeThreadIdInvalid Baileys thread ID: X
requireBaileysAdapterThis context does not belong to a Baileys adapter.
Internal (_toRawMessage)sendMessage returned no message.
All sending methodsSocket not connected. Call adapter.connect() first.

On this page