Agente de IA en WhatsApp con n8n: guía de producción paso a paso
Crea un agente de IA en WhatsApp con n8n para producción: webhook firmado, 200 inmediato, protección contra duplicados, nodo AI Agent con memoria, reintentos y traspaso.

Lo esencial
- Un agente de IA en WhatsApp con n8n solo necesita un nodo Webhook, un nodo Code que verifique la firma, un nodo AI Agent con memoria y un nodo HTTP Request que envíe la respuesta.
- Activa la opción Raw Body del nodo Webhook, porque la firma cubre los bytes exactos que envió whasp y un cuerpo JSON vuelto a serializar puede fallar la comprobación.
- Responde 200 justo después de verificar, descarta los duplicados con un nodo Remove Duplicates sobre el id del evento y envía las respuestas con una Idempotency-Key para que un reintento nunca envíe dos veces.
- En producción usa Postgres Chat Memory u otra memoria respaldada por una base de datos, porque Simple Memory vive en la memoria del proceso de n8n y olvida un chat tras una hora de silencio.
- El traspaso a un humano vive en n8n, no en whasp: guarda una marca por chat en una Data table y compruébala antes de cada llamada a la IA.
Para crear un agente de IA en WhatsApp con n8n, apunta un nodo Webhook a los eventos entrantes del agente, verifica la firma en un nodo Code, responde 200 de inmediato, descarta los eventos duplicados, ejecuta un nodo AI Agent con un modelo de chat y una memoria con el teléfono del remitente como clave, y envía la respuesta con un nodo HTTP Request. Esta guía usa whasp para la parte de WhatsApp y se probó en n8n 2.43.8.
¿Por qué no usar el nodo de WhatsApp de n8n?
El nodo WhatsApp Business Cloud integrado de n8n envía mensajes, plantillas y archivos multimedia a través de la WhatsApp Business Platform oficial de Meta. Desde el 15 de enero de 2026, los términos de esa plataforma prohíben los asistentes de IA de propósito general, como explica la prohibición de WhatsApp a los chatbots de IA, y las demás opciones tienen cada una sus propias contrapartidas. Esta guía usa un número que gestiona whasp. n8n no necesita ningún nodo especial para ello: whasp envía los eventos por POST a una URL y recibe las respuestas por una API HTTPS normal, así que los nodos Webhook y HTTP Request bastan.
La cadena tiene ocho pasos: Webhook, Verify signature (Code), Respond to Webhook, Text message? (If), Remove Duplicates, Is STOP? (If), AI Agent con su modelo y su memoria, y Send reply (HTTP Request). Probamos todo menos el nodo de Postgres en n8n 2.43.8 el 19 de octubre de 2026, con el modelo de lenguaje y la API de whasp simulados.
Necesitas un n8n accesible por HTTPS público (detrás de un proxy inverso, define N8N_WEBHOOK_URL), un número de whasp, una clave de API y una credencial de modelo.
¿Cómo recibes los eventos de whasp en n8n?
Añade un nodo Webhook y configura:
- HTTP Method: POST. Path:
whasp. - Respond: Using ‘Respond to Webhook’ Node.
- Options, Raw Body: activado.
El nodo Webhook tiene una URL de prueba y una URL de producción. La URL de prueba (/webhook-test/whasp) funciona mientras pulsas Listen for Test Event. La URL de producción (/webhook/whasp) funciona solo después de publicar el workflow. Registra la URL de producción en whasp. La respuesta incluye el secreto de firma (whsec_...), que guardas como WHASP_WEBHOOK_SECRET:
curl -X PUT https://api.whasp.ai/v1/webhook \
-H "Authorization: Bearer $WHASP_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://n8n.example.com/webhook/whasp"}'
Raw Body es importante. whasp firma los bytes exactos que envía, según el esquema de Standard Webhooks. Con la opción activada, el nodo conserva esos bytes en binary.data junto al body ya parseado. Volver a serializar el JSON parseado puede cambiar los espacios o el orden de las claves y hacer fallar la comprobación.
Llama a POST /v1/webhook/test para enviar un evento webhook.test y verlo llegar.
¿Cómo verificas la firma en un nodo Code?
Añade un nodo Code llamado Verify signature, modo Run Once for All Items, lenguaje JavaScript:
const crypto = require('crypto');
if (!$env.WHASP_WEBHOOK_SECRET) throw new Error('Set WHASP_WEBHOOK_SECRET (the whsec_ secret from whasp)');
const headers = $input.first().json.headers;
const id = headers['webhook-id'];
const timestamp = headers['webhook-timestamp'];
// Los bytes exactos que firmó whasp: la opción Raw Body del nodo Webhook los conserva en binary.data.
const raw = await this.helpers.getBinaryDataBuffer(0, 'data');
const key = Buffer.from($env.WHASP_WEBHOOK_SECRET.replace(/^whsec_/, ''), 'base64');
const expected = crypto.createHmac('sha256', key)
.update(`${id}.${timestamp}.`)
.update(raw)
.digest();
const fresh = Math.abs(Date.now() / 1000 - Number(timestamp)) <= 300; // 5 minutos
const signed = String(headers['webhook-signature'] ?? '').split(' ').some((part) => {
const [version, sig] = part.split(',');
if (version !== 'v1' || !sig) return false;
const given = Buffer.from(sig, 'base64');
return given.length === expected.length && crypto.timingSafeEqual(given, expected);
});
if (!id || !timestamp || !fresh || !signed) return [{ json: { valid: false } }];
const event = JSON.parse(raw.toString('utf8'));
const m = event.data?.message ?? {};
return [{
json: {
valid: true,
eventId: id,
type: event.type,
number: m.number, // el número de whasp que lo recibió
chat: m.chat, // el teléfono del remitente, en E.164
name: m.sender_name ?? null,
text: m.text ?? '',
},
}];
El código sigue la regla de whasp: HMAC-SHA256 sobre <id>.<timestamp>.<raw body>, con la clave formada por la parte del secreto posterior a whsec_ decodificada en base64, y una marca de tiempo de no más de cinco minutos de antigüedad. Devuelve valid: false en lugar de lanzar una excepción. Un error antes del nodo Respond to Webhook hace que n8n responda 500, y whasp reintentaría el evento hasta 12 veces. Un 401 limpio es mejor.
Dos ajustes hacen tropezar a mucha gente en n8n 2.x autoalojado, y ambos fallan con un mensaje claro. Desde n8n 2.0 el nodo Code bloquea las variables de entorno por defecto, y sin N8N_BLOCK_ENV_ACCESS_IN_NODE=false este nodo falla con “access to env vars denied”. Los módulos integrados también están bloqueados: sin NODE_FUNCTION_ALLOW_BUILTIN=crypto obtienes “Module ‘crypto’ is disallowed”. Con task runners externos, esa segunda variable va en la configuración propia del runner. En n8n Cloud, crypto está permitido. Si no puedes definir variables de entorno, crea una variable personalizada donde tu plan las tenga y lee $vars.WHASP_WEBHOOK_SECRET en su lugar.
¿Cómo respondes rápido y descartas duplicados?
whasp da por entregado cualquier 2xx recibido en 10 segundos. Si no, reintenta a los 30 segundos, a los 2 minutos, a los 10 minutos, a los 30 minutos y después cada hora, hasta 12 veces. Los eventos pueden llegar dos veces y desordenados. Tres nodos se ocupan de ello.
- Respond to Webhook. Respond With: No Data. Response Code:
{{ $json.valid ? 200 : 401 }}. El nodo pasa su entrada al siguiente después de responder, así que el workflow sigue ejecutándose mientras whasp ya tiene su respuesta. - Text message? (If). Continúa solo cuando
valides true,typees igual amessage.receivedytextno está vacío. Todo lo demás (message.sent,number.status_changed, una foto sin texto) cae en la rama false. Conecta ahímessage.failedynumber.status_changeda una alerta. - Remove Duplicates. Operation: Remove Items Processed in Previous Executions. Keep Items Where: Value Is New. Value to Dedupe On:
{{ $json.eventId }}. El historial por defecto es de 10.000 elementos.
Enviamos dos veces el mismo evento firmado y uno manipulado: el primero recibió una respuesta, el duplicado recibió un 200 y ninguna segunda ejecución, y el manipulado recibió un 401. Un límite: una vez que has respondido 200, un fallo dentro de n8n pierde ese evento, porque whasp no lo reenviará. Si eso importa, escribe cada evento en una tabla o cola antes de confirmarlo. Webhook de WhatsApp: firma, reintentos y eventos duplicados cubre el patrón.
¿Cómo configuras el nodo AI Agent, la memoria y la respuesta?
AI Agent. Source for Prompt (User Message): Define below. Prompt (User Message): {{ $json.text }}. En Options, System Message, escribe quién es el agente y cómo debe escribir, por ejemplo “Eres Ava, una asistente amable que escribe por WhatsApp. Solo texto plano, sin Markdown. Haz una pregunta a la vez.” Para un prompt más completo, mira prompts para un agente de IA en WhatsApp que suene humano. La respuesta del agente llega en $json.output.
Chat model. Conecta un OpenAI Chat Model o un Anthropic Chat Model. Ambos cargan la lista de modelos desde tu cuenta, así que elige ahí un modelo actual.
Memory. Conecta Postgres Chat Memory. Pon Session ID en Define below y Key en {{ $json.number }}:{{ $json.chat }}, de modo que cada persona tenga un historial por número. La tabla por defecto es n8n_chat_histories y Context Window Length vale 5 por defecto, así que súbelo a lo que permita tu presupuesto de prompt. Evita Simple Memory en producción. Su código fuente guarda las sesiones en un mapa dentro del proceso de n8n y borra cualquier sesión inactiva durante una hora, y su documentación dice que no funciona en queue mode. Los servidores de whasp no guardan mensajes, así que el historial es tuyo, y una base de datos que controlas es el sitio adecuado. Agente de IA en WhatsApp con memoria: historial, resúmenes, datos profundiza en ello.
Send reply. Añade un nodo HTTP Request:
| Campo | Valor |
|---|---|
| Method, URL | POST, https://api.whasp.ai/v1/messages |
| Authentication | Generic Credential Type, Header Auth: name Authorization, value Bearer wsk_... |
| Send Headers | Idempotency-Key: {{ $('Verify signature').first().json.eventId }} |
| Send Body, JSON, Using JSON | {{ { to: $('Verify signature').first().json.chat, text: $json.output } }} |
| Settings | Retry On Fail activado, Max Tries 3, Wait Between Tries 2000 ms |
Construye el cuerpo como una expresión que devuelve un objeto. Pegar el texto del modelo dentro de una cadena JSON falla con la primera comilla o el primer salto de línea. La clave de idempotencia hace seguros los reintentos: la misma clave devuelve el primer mensaje y nunca envía dos veces. n8n permite como máximo 5 intentos y 5 segundos entre ellos, así que cubre microcortes, no caídas largas. whasp responde 202 y envía los mensajes de un número en orden, con un segundo de diferencia aproximadamente.
¿Cómo manejas STOP, errores y ráfagas de mensajes?
whasp da de baja un chat por sí mismo cuando la persona envía STOP, UNSUBSCRIBE, OPTOUT, CANCEL, END, QUIT o cualquier grafía de “opt out”, y los envíos posteriores a ese chat devuelven 403 chat_opted_out. El mensaje sigue llegando a tu webhook, así que añade un nodo If llamado Is STOP? después de Remove Duplicates. Pon como valor la expresión {{ /^(STOP|UNSUBSCRIBE|OPTOUT|CANCEL|END|QUIT)$/.test($json.text) || /^opt[\s_-]*out$/i.test($json.text) }} y como condición Boolean, is true. En la rama true, salta el modelo y envía un único HTTP Request con "override_optout": true y una línea como “Te has dado de baja. Escríbeme cuando quieras para empezar de nuevo.” Las peticiones educadas como “por favor, para” te tocan a ti.
Para los fallos, abre Workflow Settings, define un Error workflow y constrúyelo con un nodo Error Trigger que avise por Slack o por correo. Fíjate en los 429: rate_limited significa más de 100 solicitudes a la API en 10 segundos por cuenta, y number_daily_limit significa que el número envió 1.500 mensajes en 24 horas. Un 409 number_unavailable significa que el número está baneado, sin conexión o retirado.
Las ráfagas requieren una decisión. Una persona que envía tres textos en cinco segundos lanza tres ejecuciones y recibe tres respuestas. Algunos desarrolladores del foro de n8n recomiendan agrupar en un solo turno los mensajes rápidos de un cliente antes de llamar al modelo. Esa parte no la construimos.
¿Cómo pasas un chat a una persona?
whasp es una tubería: no tiene bandeja de entrada ni toma de control, así que el estado del traspaso vive en n8n. Un diseño viable usa una Data table llamada chat_state con las columnas chat y handoff. Esta parte no la ejecutamos, pero las operaciones de los nodos están documentadas.
- Después de Remove Duplicates, añade un nodo Data table: Row, If Row Exists, con
chatigual a{{ $json.chat }}yhandoffen true. La rama true reenvía el mensaje a Slack o a tu helpdesk y se detiene. La rama false continúa hacia el agente. - Activa la marca con una operación Upsert row de Data table cuando el texto cumpla una regla como
/humano|persona real/i, o cuando tu agente lo pida. Después envía “Una persona te responderá aquí en breve.” - Construye un segundo workflow que se dispare cuando responda el equipo, llame a
POST /v1/messagescon el mismo chat y vuelva a ponerhandoffen false con un upsert al recibir un comando de reanudación.
Comprueba la marca otra vez justo antes de enviar la respuesta del modelo, porque un traspaso puede empezar mientras el modelo está pensando.
¿Qué hacer ahora?
Publica el workflow, registra su URL de producción con PUT /v1/webhook, envía POST /v1/webhook/test y escribe después a tu número desde tu propio teléfono. Si no llega nada, GET /v1/webhook/deliveries muestra cada intento y su estado. El inicio rápido recorre la API. Para el mismo flujo en código, mira Bot de WhatsApp con ChatGPT: agente con la API de OpenAI.
Preguntas
¿Cómo conecto n8n a WhatsApp para un agente de IA?
Recibe los mensajes entrantes con un nodo Webhook, ejecuta un nodo AI Agent y envía la respuesta con un nodo HTTP Request que llame a POST https://api.whasp.ai/v1/messages. El nodo WhatsApp Business Cloud de n8n usa la API oficial de Meta, que no sirve para un asistente de propósito general.
¿Por qué mi nodo Code de n8n dice que el módulo crypto no está permitido?
Desde n8n 2.0 el nodo Code se ejecuta con task runners y bloquea por defecto los módulos integrados. En n8n autoalojado, define NODE_FUNCTION_ALLOW_BUILTIN=crypto donde el task runner lee su entorno. n8n Cloud ya permite crypto.
¿Por qué mi nodo Code de n8n dice access to env vars denied?
n8n 2.0 bloquea por defecto las variables de entorno en el nodo Code. En n8n autoalojado define N8N_BLOCK_ENV_ACCESS_IN_NODE=false, o guarda el secreto como variable personalizada y léelo con $vars.
¿Cómo evito que n8n responda dos veces al mismo mensaje de WhatsApp?
Pon un nodo Remove Duplicates después del Webhook, configurado para quitar los elementos procesados en ejecuciones anteriores, y deduplica por el id del evento que viene en la cabecera webhook-id. Después envía la respuesta con ese mismo id como Idempotency-Key.
¿Qué nodo de memoria de n8n debe usar un agente de WhatsApp?
Usa Postgres Chat Memory o Redis Chat Memory, con el número de teléfono del remitente como clave de sesión. Simple Memory vive dentro del proceso, se descarta tras una hora sin uso y no funciona en queue mode.


