Objetivos de aprendizaje
Después de leer este artículo podrás:
- Elegir entre una librería no oficial y la Cloud API oficial de Meta
- Ejecutar un bot con Baileys que guarda la sesión y reconecta solo
- Comprobar que el bot está en línea y corregir los errores más comunes
En esta página
- ¿Librería no oficial o Cloud API?
- ¿Qué significa mantener un bot de WhatsApp 24/7?
- ¿Cómo ejecutar un bot con Baileys?
- ¿Cómo sobrevive la sesión a los reinicios?
- ¿Cómo usar la Cloud API oficial?
- ¿Cómo saber si el bot está en línea?
- ¿Cuáles son los errores comunes?
- Cómo hacerlo en Square Cloud
- Preguntas frecuentes
Un bot de WhatsApp es un programa que recibe los mensajes enviados a tu número, decide qué responder y envía la respuesta. Para funcionar todo el día necesita un proceso vivo y una sesión autenticada que sobreviva a los reinicios. Son los dos puntos donde falla la mayoría de los bots.
Esta guía muestra los dos caminos: Baileys, una librería no oficial que se conecta como WhatsApp Web, y la Cloud API, la interfaz oficial de Meta. Los ejemplos usan la versión LTS activa de Node.js y la API de Baileys 7. Si npm install trae otra versión mayor, lee las notas de versión del proyecto antes de seguir.
¿Librería no oficial o Cloud API?#
| Baileys (no oficial) | Cloud API (oficial) | |
|---|---|---|
| Cómo se conecta | WebSocket, como dispositivo vinculado al número | Webhook HTTPS + llamadas a la Graph API |
| Sesión | Carpeta de autenticación en disco | Token de acceso, sin sesión |
| Servidor | Proceso siempre encendido | Servidor HTTPS público con certificado válido |
| Escribir primero | Libre, pero con riesgo de baneo | Solo plantillas (templates) aprobadas |
| Riesgo | Baneo por violar los Términos de Servicio | Sin ese riesgo, pero exige cuenta comercial |
La Cloud API entrega los mensajes recibidos por webhook. Cuando el usuario te escribe, se abre una ventana de atención de 24 horas, que se renueva con cada mensaje nuevo suyo. Dentro de ella respondes con texto libre. Fuera, solo con plantillas preaprobadas. Meta cobra por mensaje de plantilla entregado, y los mensajes de texto libre dentro de la ventana no se cobran. Las tarifas y las reglas de cobro vigentes están en la página de precios de Meta.
Para comparar las librerías a fondo, mira Baileys, whatsapp-web.js o Cloud API. Para armar la lógica del bot, mira cómo hacer un bot de WhatsApp.
¿Qué significa mantener un bot de WhatsApp 24/7?#
Significa tener el proceso corriendo sin parar, en una máquina que no se apaga, y la sesión guardada para que un reinicio no obligue a vincular otra vez. En Baileys, la sesión son las claves y credenciales escritas en la carpeta de autenticación. Si esa carpeta desaparece, el bot se vuelve un dispositivo nuevo y hay que vincularlo de nuevo.
Por eso, correr el bot en tu laptop sirve para probar, pero no para producción. Por la misma razón, el disco importa tanto como el proceso: en entornos donde el disco se borra en cada deploy, el bot pierde la sesión en cada actualización.
¿Cómo ejecutar un bot con Baileys?#
Crea el proyecto e instala las dependencias:
npm init -y
npm pkg set type=module
npm install baileysBaileys 7 es un módulo ES, de ahí el type=module. Define dos variables de entorno: WA_PHONE, el número del bot solo con dígitos y código de país (por ejemplo 5215512345678), y opcionalmente AUTH_DIR, la carpeta de la sesión. No dejes el número ni las credenciales en el código.
Crea index.js:
import makeWASocket, {
DisconnectReason,
useMultiFileAuthState,
} from "baileys";
const AUTH_DIR = process.env.AUTH_DIR ?? "auth_info";
const PHONE = process.env.WA_PHONE;
async function start() {
const { state, saveCreds } = await useMultiFileAuthState(AUTH_DIR);
const sock = makeWASocket({ auth: state });
sock.ev.on("creds.update", saveCreds);
sock.ev.on("connection.update", async ({ connection, lastDisconnect, qr }) => {
// Primera ejecución: sin sesión guardada, pide un código de vinculación.
if (qr && !sock.authState.creds.registered) {
if (!PHONE) return console.error("Define WA_PHONE para vincular.");
console.log("Código de vinculación:", await sock.requestPairingCode(PHONE));
}
if (connection === "open") console.log("Conectado a WhatsApp.");
if (connection === "close") {
const code = lastDisconnect?.error?.output?.statusCode;
if (code === DisconnectReason.loggedOut) {
console.error("Sesión cerrada desde el celular. Borra la carpeta y vincula de nuevo.");
process.exit(1);
}
if (code === DisconnectReason.connectionReplaced) {
console.error("Otra instancia tomó la sesión. Saliendo.");
process.exit(1);
}
start(); // caída de red o reinicio pedido por WhatsApp: reconecta
}
});
sock.ev.on("messages.upsert", async ({ messages, type }) => {
if (type !== "notify") return;
for (const msg of messages) {
if (msg.key.fromMe || !msg.message) continue;
const text =
msg.message.conversation ?? msg.message.extendedTextMessage?.text;
if (text?.trim().toLowerCase() === "!ping") {
await sock.sendMessage(msg.key.remoteJid, { text: "pong" });
}
}
});
}
start();Ejecútalo con WA_PHONE=5215512345678 node index.js. En el WhatsApp del celular, abre Dispositivos vinculados, elige vincular con número de teléfono y escribe el código que apareció en el log. Con la conexión abierta, envía !ping desde otro número: el bot responde pong.
En versiones anteriores, la opción printQRInTerminal imprimía el código QR sola. En Baileys 7 está obsoleta y ya no imprime nada: el QR llega en el campo qr del evento connection.update para que lo dibujes como prefieras. El código de vinculación evita el QR y es más fácil de leer en el log de un servidor.
¿Cómo sobrevive la sesión a los reinicios?#
useMultiFileAuthState escribe credenciales y claves en la carpeta de autenticación, y el evento creds.update llama a saveCreds cada vez que algo cambia. En la siguiente ejecución, Baileys lee la carpeta y conecta sin QR ni código. La documentación del proyecto advierte que las claves deben guardarse cada vez que cambian; si no, los mensajes dejan de llegar al destinatario.
Tres cuidados:
- No borres ni sobrescribas la carpeta en un deploy. Mantenla fuera del ZIP y del repositorio.
- Trata la carpeta como una contraseña. Quien la copie controla la sesión.
- Una sola instancia por sesión. Dos copias del bot con las mismas credenciales se tumban entre sí.
useMultiFileAuthState es solo un ejemplo: el código fuente de Baileys no lo recomienda para producción, con la salvedad de que quizá sirva para un bot. En un proyecto más grande, guarda el estado en una base de datos.
¿Cómo usar la Cloud API oficial?#
En la Cloud API no hay sesión ni vinculación. Necesitas una cuenta comercial en Meta, un número de teléfono comercial y un servidor con HTTPS válido (los certificados autofirmados no se admiten). Mira cómo funciona Let's Encrypt si necesitas un certificado.
Meta verifica el endpoint con una petición GET: si hub.verify_token coincide con tu valor, respondes 200 con el hub.challenge. Los mensajes llegan por POST, firmados en la cabecera X-Hub-Signature-256. Un servidor mínimo, solo con la librería estándar de Node.js:
import { createServer } from "node:http";
import { createHmac, timingSafeEqual } from "node:crypto";
const {
VERIFY_TOKEN,
APP_SECRET,
WA_TOKEN,
PHONE_NUMBER_ID,
GRAPH_API_VERSION,
PORT = "8080",
} = process.env;
if (!GRAPH_API_VERSION) throw new Error("Define GRAPH_API_VERSION (formato vXX.0) con una versión del changelog de la Graph API");
async function reply(to, body) {
await fetch(`https://graph.facebook.com/${GRAPH_API_VERSION}/${PHONE_NUMBER_ID}/messages`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${WA_TOKEN}`,
},
body: JSON.stringify({
messaging_product: "whatsapp",
to,
type: "text",
text: { body },
}),
});
}
createServer(async (req, res) => {
const url = new URL(req.url, "http://localhost");
if (url.pathname !== "/webhook") return res.writeHead(404).end();
if (req.method === "GET") {
const ok =
url.searchParams.get("hub.mode") === "subscribe" &&
url.searchParams.get("hub.verify_token") === VERIFY_TOKEN;
return res
.writeHead(ok ? 200 : 403)
.end(ok ? url.searchParams.get("hub.challenge") : "");
}
const chunks = [];
for await (const chunk of req) chunks.push(chunk);
const raw = Buffer.concat(chunks);
const expected = Buffer.from(
"sha256=" + createHmac("sha256", APP_SECRET).update(raw).digest("hex"),
);
const got = Buffer.from(req.headers["x-hub-signature-256"] ?? "");
if (got.length !== expected.length || !timingSafeEqual(got, expected)) {
return res.writeHead(401).end();
}
res.writeHead(200).end(); // responde rápido; Meta reintenta las entregas fallidas
for (const entry of JSON.parse(raw).entry ?? [])
for (const change of entry.changes ?? [])
for (const m of change.value?.messages ?? [])
if (m.type === "text") await reply(m.from, `Dijiste: ${m.text.body}`).catch(console.error);
}).listen(Number(PORT));Meta reintenta las entregas fallidas hasta por 7 días, así que el mismo evento puede llegar dos veces. En producción, guarda el id de cada mensaje e ignora los repetidos. Variables como WA_TOKEN y APP_SECRET entran por el entorno: mira qué son las variables de entorno. La versión de la Graph API también, porque cada versión deja de funcionar unos dos años después de su lanzamiento: pon en GRAPH_API_VERSION la más reciente del changelog de la Graph API, en formato vXX.0, y cuando envejezca basta con cambiar el valor y reiniciar.
¿Cómo saber si el bot está en línea?#
- Envía un mensaje de prueba (
!ping) y revisa la respuesta. - Con Baileys, busca
Conectado a WhatsApp.en el log y vigila si aparecen caídas seguidas. - Con la Cloud API, usa la prueba de webhook del panel de Meta y confirma que tu servidor responde 200.
- Confirma que el proceso se reinicia solo si se detiene. En un VPS, esa es la función de
systemdopm2.
¿Cuáles son los errores comunes?#
- El bot pide código en cada reinicio: la carpeta de autenticación se borró o el disco no es persistente. Guárdala en un lugar que sobreviva a los deploys.
- Sesión cerrada (
loggedOut): el dispositivo se quitó en Dispositivos vinculados, o WhatsApp invalidó la sesión. Borra la carpeta y vincula de nuevo. - Dos bots tumbándose entre sí (
connectionReplaced): hay otra instancia con las mismas credenciales, incluso en tu PC. Deja solo una. Mira también por qué se desconecta un bot de WhatsApp. - El webhook de la Cloud API no valida:
hub.verify_tokendistinto del configurado, URL sin HTTPS válido o ruta equivocada. - Mensaje rechazado fuera de la ventana de 24 horas: solo las plantillas aprobadas pueden iniciar la conversación cuando la ventana se cierra.
Cannot use import statement outside a module: falta"type": "module"en elpackage.json.
Cómo hacerlo en Square Cloud#
Square Cloud es hosting self-service: subes el proyecto, defines la memoria y controlas todo desde el panel. Funciona con Baileys, WPPConnect, whatsapp-web.js y la Cloud API oficial. La carpeta de autenticación queda en el disco de la aplicación, así que la sesión sigue conectada después de un reinicio.
Los contenedores están siempre encendidos, sin modo de suspensión, con reinicio automático (activado por defecto en el deploy desde el panel) cuando el proceso termina con error. WA_PHONE, WA_TOKEN, GRAPH_API_VERSION y las demás variables se definen por aplicación en el panel, y los logs en tiempo real muestran el código de vinculación y los eventos de conexión. Tú haces el deploy por ZIP, CLI, GitHub o la extensión de VS Code, y squarecloud.app es opcional: el panel genera el archivo con runtime, memoria y archivo principal, o tú lo escribes en la raíz del proyecto.
Todos los planes son de pago y se cobran en reales. Hobby cuesta R$12,49 al mes, con 1 GB de RAM compartido entre tus aplicaciones, y el mínimo por aplicación en segundo plano, como un bot, es de 256 MB. Las aplicaciones web, como el webhook de la Cloud API, parten de 512 MB y reciben un subdominio squareweb.app con HTTPS. Mira la página de hosting para bots de WhatsApp.
Hosting de bots
Tu bot en línea 24/7, con reinicio automático
Preguntas frecuentes
¿El bot de WhatsApp necesita que mi computadora esté encendida?
Necesita un proceso corriendo en algún lugar. Si es tu PC, el bot se cae cuando se apaga o pierde internet. En un servidor siempre encendido sigue respondiendo sin depender de tu máquina.
¿Tengo que vincular el bot cada vez que se reinicia?
No, mientras la carpeta de autenticación esté guardada en disco y no se borre. Baileys lee las credenciales de esa carpeta y reconecta sin vincular de nuevo. Solo hay que vincular otra vez si la sesión se cierra desde el celular.
¿Necesito un dominio para hospedar un bot de WhatsApp?
Con Baileys no, porque el bot abre la conexión por su cuenta. Con la Cloud API oficial sí: Meta entrega los mensajes por webhook, que exige una URL pública con HTTPS y certificado válido.
¿Usar Baileys puede hacer que baneen mi número?
Sí. Las librerías no oficiales no están autorizadas por WhatsApp, y los mantenedores de Baileys dicen que no avalan usos que violen los Términos de Servicio de WhatsApp, y dejan la responsabilidad en manos de quien la usa. Para uso comercial, la Cloud API oficial evita ese riesgo.
Más en Bots
Ver los 33 artículos- ¿Cuánto cuesta hospedar un bot de Discord?El costo de hospedar un bot de Discord depende de la RAM, el tiempo en línea, la base de datos y los servicios externos. Aprende a estimar el tuyo.
- Correr un bot en el celular (Termux) o en tu PC vs hospedarloUn bot en tu PC o celular (Termux) se cae cuando el equipo se apaga, se suspende o pierde la red. Compara las tres opciones y cuándo sirve cada una.
- Cómo mantener tu bot de Discord en línea 24/7Para mantener un bot de Discord en línea 24/7, ejecuta el proceso en una máquina siempre encendida con reinicio automático. Compara PC, VPS con pm2 o systemd.
- Por qué tu bot de Discord se apaga o aparece offlineUn bot de Discord aparece offline cuando el proceso se cae, el token es inválido, falta una intent o se acaba la memoria. Diagnostica y corrige cada causa.