Cómo recibir mensajes de WhatsApp por webhook (ejemplo en Node.js)
Preguntarle a la API cada pocos segundos si llegó un mensaje es lento, caro y escala mal. El webhook invierte la lógica: cada mensaje recibido se convierte en un POST a la URL que tú controlas, y tu sistema reacciona en segundos. Es la base de chatbots, CRM y automatizaciones de atención. Aquí entiendes el flujo, ves un ejemplo real en Node.js con Express y te llevas una lista de verificación de producción para no perder mensajes ni sufrir timeouts. El código sirve para cualquier proveedor que envíe eventos por webhook, y al final ves cómo queda en ZapZap API.
Publicado el · Actualizado el · 9 min de lectura
Este artículo también está disponible en: Português · English
Qué es un webhook y por qué es mejor que el polling
Un webhook es un endpoint HTTP en tu servidor que queda esperando a ser llamado. Cuando ocurre un evento del lado de WhatsApp, como un mensaje recibido, una confirmación de lectura o un cambio en el estado de la conexión, el proveedor hace un POST a tu URL con los datos del evento en el cuerpo de la solicitud. Tú procesas ese cuerpo y respondes 200. Así de simple. La diferencia con el polling es enorme: en el polling tienes que preguntar cada cierto tiempo si llegó algo nuevo, lo que gasta recursos, tiene retraso y escala mal cuando tienes muchos números conectados.
Con webhook la entrega es prácticamente instantánea y solo te activan cuando realmente hay algo que atender. Esto reduce el costo de infraestructura y mejora la experiencia: un cliente envía un mensaje y tu bot puede responder en uno o dos segundos. Para atención al cliente, calificación de leads y automatización de ventas, esa latencia baja tiene un impacto directo en la conversión.
El precio de esta arquitectura es que debes mantener un endpoint público, estable y rápido. Si se cae o demora demasiado, el proveedor puede considerar la entrega fallida y reintentar después, o en algunos casos dejar de intentarlo. Por eso, manejar bien la respuesta y la idempotencia no es un detalle: es lo que separa una integración de juguete de una que aguanta producción.
Cómo es el payload de un mensaje recibido
El formato exacto varía entre proveedores, pero la estructura conceptual es siempre parecida. El cuerpo del POST suele traer un campo que indica el tipo de evento (por ejemplo messages o message), el identificador de la instancia o número que recibió, los datos de quien envió (número, nombre en WhatsApp), el contenido del mensaje y un campo booleano que indica si el mensaje fue enviado por ti mismo (el famoso fromMe).
Ese fromMe es una de las mayores causas de bugs en las integraciones de WhatsApp. Cuando tu propio sistema envía un mensaje, muchos proveedores también disparan un evento de webhook para él. Si no filtras por fromMe, tu bot puede responder a su propio mensaje y entrar en un bucle. La regla práctica: al inicio del handler, si el evento es de un mensaje enviado por ti, devuelve 200 y no hagas nada.
También conviene manejar los tipos de contenido. El texto plano es el caso más simple, pero recibirás audio, imagen, documento, ubicación, respuesta de botón y reacción. Cada uno llega con una estructura de campos distinta. Empieza manejando texto, asegura el flujo y solo después agrega los demás tipos según la necesidad real de tu producto. No intentes cubrir todo de una vez.
Antes de escribir cualquier lógica pesada, haz un log crudo del cuerpo recibido en un entorno de pruebas y envía un mensaje real al número. Ver el JSON real llegando es la forma más rápida y honesta de entender el formato de tu proveedor. La documentación ayuda, pero el payload real manda.
Ejemplo práctico: un webhook en Node.js con Express
A continuación hay un handler mínimo, pero que ya incluye los cuidados que importan. Usa Express, hace el parseo de JSON, ignora los mensajes propios, extrae el texto y responde 200 rápido. El procesamiento pesado (llamar a la base de datos, a una IA, enviar la respuesta) queda fuera del camino crítico de la respuesta HTTP.
- ```js
- const express = require('express');
- const app = express();
- app.use(express.json({ limit: '2mb' }));
- app.post('/webhook', (req, res) => {
- // 1. Responde rápido: confirma la recepción primero
- res.sendStatus(200);
- const evento = req.body;
- // 2. Ignora los mensajes enviados por ti mismo (evita bucle)
- const msg = evento?.message || evento?.data || {};
- if (msg.fromMe) return;
- // 3. Extrae los campos que necesitas
- const de = msg.sender || msg.from;
- const texto = msg.text || msg.body || '';
- if (!texto) return; // por ahora, solo maneja texto
- // 4. Procesa fuera del camino de la respuesta HTTP
- processarMensagem(de, texto).catch((e) =>
- console.error('error al procesar', e)
- );
- });
- async function processarMensagem(de, texto) {
- console.log(`Mensaje de ${de}: ${texto}`);
- // aquí llamas a tu base de datos, a tu IA, o envías la respuesta
- }
- app.listen(3000, () => console.log('webhook escuchando en el puerto 3000'));
- ```
Fíjate en el orden: res.sendStatus(200) va antes de procesar. Es intencional. El proveedor solo necesita saber que lo recibiste; no necesita esperar a que termines de pensar la respuesta. Si dejas al proveedor esperando a que la IA genere texto o a que la base de datos responda, corres el riesgo de superar su timeout y recibir el mismo mensaje de nuevo en un reintento. Confirma primero, trabaja después.
Los nombres de campo del ejemplo (message, data, sender, text) son ilustrativos y pueden cambiar según el proveedor. Ajústalos después de ver el payload real. Si usas ZapZap API, los formatos exactos de cada tipo de evento están descritos en formato legible por máquina en api.zapzapapi.com/llms.txt, que sirve como fuente única para armar tu parser sin adivinar.
Buenas prácticas para no perder mensajes en producción
Responde siempre con un 2xx y hazlo rápido. Un webhook que demora más de unos pocos segundos es un webhook problemático. Mueve cualquier trabajo lento, como una llamada a una IA, una escritura lenta en la base de datos o el envío de una respuesta, fuera de la solicitud, usando una cola o al menos un procesamiento asíncrono que no bloquee la respuesta. El endpoint tiene una única obligación síncrona: decir “recibido”.
Trata los reenvíos como algo esperado, no como una excepción. Las redes fallan, los timeouts ocurren, y un proveedor serio reenviará el evento si no recibió tu 200. Eso significa que el mismo mensaje puede llegar dos veces. Si cada llegada dispara una respuesta o una escritura, generas duplicados. La defensa es la idempotencia: guarda el ID único del mensaje y, antes de procesar, verifica si ya atendiste ese ID. Si ya lo hiciste, responde 200 y detente.
Valida el origen del webhook. Tu URL es pública, así que cualquiera que la descubra puede enviar POST falsos. Según el proveedor, validas un token en el header, una firma HMAC o un secreto en la propia URL. Nunca confíes ciegamente en el cuerpo recibido. Y registra logs de las entregas fallidas: si un POST tuyo devuelve error, quieres saber qué mensaje fue y por qué, para reprocesarlo sin perder el lead.
Un detalle fácil de olvidar: en el entorno de desarrollo tu máquina local no tiene URL pública. Usa una herramienta de túnel para exponer el puerto local durante las pruebas, o configura el webhook apuntando a un servidor de staging real. Crear la instancia con el webhook apuntando a localhost simplemente no recibe nada.
Recibiendo mensajes de WhatsApp con ZapZap API
ZapZap API es una API de WhatsApp no oficial basada en instancias: conectas un número real escaneando el código QR y empiezas a enviar y recibir mensajes por API, sin depender de la aprobación ni de las colas de la API oficial. Cada número activo cuesta R$15 al mes en el modelo prepago, y hay un crédito de bienvenida (sujeto a la campaña vigente). Para quien necesita poner en marcha una automatización rápido y sin burocracia de verificación, es el camino más directo.
En la práctica, registras la URL de tu webhook y cada instancia empieza a reenviar los eventos recibidos a tu endpoint en el mismo formato de POST que mostramos arriba. Conectas el handler de Node.js, filtras fromMe, manejas la idempotencia y ya tienes un bot funcional. Los detalles de configuración y los formatos de payload están documentados en la página de integración por webhook, y la referencia general de los recursos disponibles está en la página de API de WhatsApp.
Además de lo básico de enviar y recibir, la plataforma suma recursos que importan cuando operas con un número no oficial de verdad: calentamiento de las instancias en comunidad para darle historial al número, mecanismos antibloqueo que reducen el riesgo de restricción, IA configurable por instancia y módulos para flujos específicos. Recibes el mensaje en tu webhook y decides si respondes con tu propia lógica, con la IA de la instancia o lo reenvías a tu CRM.
Una nota de honestidad: ninguna solución con número no oficial elimina el riesgo de restricción. El calentamiento y el antibloqueo reducen ese riesgo y dan previsibilidad, y el webhook es la puerta de entrada para construir encima, repartiendo el volumen y actuando como un usuario real. Con el handler listo, el siguiente paso es conectar una instancia y ver llegar el primer mensaje a tu endpoint.
Resumen rápido
- El webhook entrega el mensaje en tiempo real, por POST a tu servidor, sin polling.
- Responde 200 rápido y procesa el trabajo pesado de forma asíncrona, fuera del camino de la respuesta HTTP.
- Filtra fromMe al inicio del handler para que el bot no responda a su propio mensaje y entre en un bucle.
- Maneja los reenvíos con idempotencia (guarda el ID del mensaje) y valida el origen del webhook por token o firma.
- En ZapZap API, cada instancia reenvía los eventos a tu URL. Los formatos están en la documentación de webhooks y en api.zapzapapi.com/llms.txt.
Sigue leyendo
Lee también
Preguntas frecuentes
¿Necesito un servidor con IP fija para recibir webhooks?
No necesitas IP fija, pero sí una URL pública y estable con HTTPS. En producción usa un dominio propio que apunte a tu servidor o servicio en la nube. En desarrollo, usa una herramienta de túnel para exponer tu puerto local temporalmente, ya que localhost no recibe llamadas externas.
¿Por qué mi bot sigue respondiendo a su propio mensaje?
Porque no estás filtrando el campo fromMe. Muchos proveedores disparan un evento de webhook también para los mensajes que envía tu sistema. Verifica fromMe al inicio del handler y, si es verdadero, devuelve 200 sin procesar. Esto corta el bucle de raíz.
¿Qué pasa si mi servidor se cae y no respondo 200?
El proveedor considera la entrega fallida y normalmente intenta reenviar el evento después, dentro de una ventana de reintentos. Por eso maneja los reenvíos con idempotencia: guarda el ID del mensaje e ignora los duplicados. Si las fallas persisten por mucho tiempo, puedes perder eventos, así que monitorea y registra las entregas con error.
¿Puedo recibir mensajes sin la API oficial de WhatsApp?
Sí. ZapZap API usa instancias con un número real conectado por código QR, lo que permite enviar y recibir por webhook sin la aprobación ni las colas de la API oficial. El costo es de R$15 por número activo al mes, prepago, con crédito de bienvenida al crear la cuenta, sujeto a la campaña vigente. Recuerda que un número no oficial siempre conlleva algún riesgo de restricción, mitigado con calentamiento y antibloqueo.
Recibe el primer mensaje en tu endpoint
Conecta un número por código QR y registra la URL de tu webhook. Al verificar tu teléfono recibes un crédito de bienvenida para probar sin depositar (uno por teléfono, sujeto a la campaña vigente).
