Baileys se conecta a WhatsApp por WebSocket. Para enrutar esa conexión por un proxy, pasas un agente personalizado al crear el socket, y eso es lo que hace que cada número salga por su propia dirección en lugar de por la IP del VPS.
Por qué importa
Un servidor con diez instancias de Baileys tiene, por defecto, diez números saliendo por la misma dirección IP: la de la máquina. Si esa dirección tiene un problema, los diez se ven afectados a la vez.
Además, la IP de un proveedor de nube pertenece a un bloque compartido con cientos de proyectos más, cuyo historial no controlas.
Instalación
npm install socks-proxy-agent
Para proxy HTTP en lugar de SOCKS5:
npm install https-proxy-agent
Configuración básica con SOCKS5
import makeWASocket from '@whiskeysockets/baileys'
import { SocksProxyAgent } from 'socks-proxy-agent'
const proxyUrl = 'socks5://usuario:[email protected]:1080'
const agent = new SocksProxyAgent(proxyUrl)
const sock = makeWASocket({
auth: state,
agent, // conexión principal (WebSocket)
fetchAgent: agent, // descarga de medios
printQRInTerminal: true,
})
Fíjate en los dos campos. agent enruta la conexión principal; fetchAgent enruta la descarga de medios. Definir solo el primero deja el tráfico de medios saliendo por la IP de la máquina, una incoherencia que pasa desapercibida con facilidad.
Un proxy por instancia
Encapsula la creación del socket para que cada número reciba su propio proxy:
import makeWASocket, { useMultiFileAuthState } from '@whiskeysockets/baileys'
import { SocksProxyAgent } from 'socks-proxy-agent'
const instances = [
{ name: 'cliente-a', proxy: 'socks5://user_a:[email protected]:1080' },
{ name: 'cliente-b', proxy: 'socks5://user_b:[email protected]:1080' },
]
async function start({ name, proxy }) {
const { state, saveCreds } = await useMultiFileAuthState(`./auth/${name}`)
const agent = new SocksProxyAgent(proxy)
const sock = makeWASocket({
auth: state,
agent,
fetchAgent: agent,
printQRInTerminal: true,
})
sock.ev.on('creds.update', saveCreds)
sock.ev.on('connection.update', ({ connection, lastDisconnect }) => {
if (connection === 'close') {
const status = lastDisconnect?.error?.output?.statusCode
console.log(`[${name}] desconectado (${status}), reconectando`)
// recrea el socket con el mismo proxy
setTimeout(() => start({ name, proxy }), 5000)
}
if (connection === 'open') {
console.log(`[${name}] conectado`)
}
})
return sock
}
for (const instance of instances) {
await start(instance)
}
Lo importante está en la gestión de la reconexión: el agente se define al crear el socket, así que recrear el socket sin volver a pasar el proxy hace que el número vuelva a salir por la IP de la máquina, sin avisar.
Contraseña con caracteres especiales
Si la contraseña contiene @, :, / o #, codifícala antes de montar la URL:
const user = encodeURIComponent('tu-usuario')
const pass = encodeURIComponent('tu@contrasena')
const proxyUrl = `socks5://${user}:${pass}@p1.ejemplo.com:1080`
La @ es el caso crítico: separa la credencial del host en la estructura de la URL, así que una contraseña con @ rompe la cadena si no se codifica.
Valida antes de conectar
Confirma la salida antes de generar el código QR:
import { SocksProxyAgent } from 'socks-proxy-agent'
import fetch from 'node-fetch'
const agent = new SocksProxyAgent('socks5://usuario:[email protected]:1080')
const res = await fetch('https://ipinfo.io/json', { agent })
console.log(await res.json())
// esperado: la IP contratada y el código del país elegido (por ejemplo, "MX" o "ES")
Si la respuesta muestra la IP del VPS, el agente no se está aplicando y no tiene sentido conectar el número todavía.
Problemas comunes
ECONNREFUSED al crear el socket: puerto equivocado. Confirma que usaste el puerto de SOCKS5.
Error de autenticación en el proxy: contraseña con caracteres especiales sin codificar, o credencial con un espacio de más.
Se conecta y se desconecta en bucle: puede ser una sesión corrupta. Borra la carpeta de autenticación de esa instancia y vuelve a conectar. Si persiste, revisa la versión de Baileys.
Los medios no se descargan, los mensajes sí funcionan: definiste agent pero olvidaste fetchAgent.
Todas las instancias con la misma IP: el proxy se definió de forma global, no por instancia. Revisa la función de creación del socket.
Límites de lo que resuelve el proxy
El proxy controla el origen de la conexión. No controla las políticas de WhatsApp, los límites de envío, la calidad del número ni las denuncias de los destinatarios.
Un número bloqueado por su patrón de envío es un problema de operación, no de red, y cambiar de IP no lo corrige. Cómo distinguir un bloqueo de una desconexión está en número bloqueado en la API de WhatsApp.
Si usas Evolution API en lugar de Baileys directamente, mira la guía de Evolution API. Para elegir el tipo de IP, mira el proxy para WhatsApp Business.