Resumen
- La configuración de la aplicación de Facebook de Meta permite una sola URL de callback para webhooks (según las preguntas frecuentes de Chatwoot sobre WhatsApp). Para tener una bandeja de equipo y un agente de IA, deja que Chatwoot gestione el canal de WhatsApp Cloud API y usa Hermes como el cerebro detrás de un webhook de Chatwoot Agent Bot, no como un segundo receptor de webhooks de WhatsApp.
- Un stack práctico (July 2026, editorial): WhatsApp Cloud API → bandeja de Chatwoot → webhook de Agent Bot → pequeño servicio puente → servidor API de Hermes (compatible con OpenAI) con
OPENAI_API_KEY→ respuesta publicada a través de Chatwoot → Meta la entrega a WhatsApp. - Hermes conectado directamente a WhatsApp (
hermes whatsapp-cloud) es una opción razonable para un bot individual sin mesa de soporte. Este artículo se centra en el patrón Chatwoot + Hermes para equipos de soporte que necesitan visibilidad, asignación y transferencia a personas. - Puntos críticos para salir a producción: suscribe tu aplicación de Meta a la WABA (
subscribed_apps), respeta la ventana de atención al cliente de 24 horas, usa un token permanente de System User y prueba cuidadosamente el formato de los números de teléfono en modo de desarrollo. - El valor de Hermes aquí: un ciclo completo de agente (herramientas, skills, memoria y sesiones) respaldado por OpenAI, no un simple wrapper de ChatGPT en el puente. El puente de ejemplo usa el parámetro
conversationde la Responses API de Hermes para mantener la continuidad entre turnos;X-Hermes-Session-Keydelimita la memoria a largo plazo, no el historial de la conversación.
Qué aprenderás aquí
- Por qué Chatwoot y Hermes no deberían recibir ambos los webhooks de WhatsApp de Meta
- Una arquitectura de referencia para WhatsApp → Chatwoot → Hermes → OpenAI
- Cómo configurar la bandeja de WhatsApp Cloud API de Chatwoot (flujo manual)
- Cómo configurar Hermes con OpenAI y exponerlo mediante el servidor API
- Un puente mínimo con FastAPI que conecta los eventos de Chatwoot Agent Bot con Hermes
- Transferencia a agentes humanos, la ventana de mensajería de 24 horas y una lista de verificación operativa
- Fallas comunes extraídas de los hilos de issues de Meta y Chatwoot
Esta es la Parte 2 de la serie sobre Hermes Agent. Comienza con la Parte 1: arquitectura de Hermes y lecciones sobre RAG. La Parte 3 aborda operaciones programadas con cron; la Parte 4 trata sobre copilotos internos de soporte para agentes humanos.
La pregunta de arquitectura
Tienes cuatro componentes:
| Componente | Función |
|---|---|
| WhatsApp Cloud API | Canal oficial de Meta para mensajes entrantes y salientes de WhatsApp |
| Chatwoot | Bandeja de equipo, historial de conversaciones, asignación de agentes y transferencia a personas |
| Hermes Agent | Runtime de agente autohospedado: herramientas, skills, memoria y sesiones |
| OpenAI API | Proveedor de LLM al que Hermes llama para razonar y usar herramientas |
El error que debes evitar es apuntar el webhook de Meta tanto a Hermes como a Chatwoot. Según la guía manual de WhatsApp de Chatwoot, una aplicación de Facebook configura un solo endpoint de webhook: debes apuntarlo a la URL de callback de Chatwoot. Otros números de la misma aplicación pueden compartir ese endpoint mediante bandejas adicionales de Chatwoot, pero no puedes tener dos receptores independientes de webhooks de Meta.
Así que elige quién gestionará el webhook de WhatsApp:
Option A — Support desk (this article)
WhatsApp → Meta → Chatwoot → Bridge → Hermes (+ OpenAI) → Chatwoot → WhatsApp
Option B — Solo Hermes bot (no Chatwoot)
WhatsApp → Meta → Hermes gateway (hermes whatsapp-cloud) → OpenAI
Este artículo implementa la Opción A.
Cuándo usar la Opción A o la Opción B
| Opción A — Chatwoot + Hermes (este artículo) | Opción B — Hermes directo | |
|---|---|---|
| Ideal para | Equipos de soporte, transferencia a personas y auditoría de conversaciones en una bandeja de equipo | Bots individuales, asistentes personales y obtener el primer mensaje lo más rápido posible |
| Visibilidad para el equipo | Los agentes ven todo el historial de WhatsApp en Chatwoot | No hay bandeja compartida; debes implementar tus propios logs |
| Intervención humana | Nativa mediante Chatwoot toggle_status → open | Debes implementar la escalación |
| Responsable del webhook | Chatwoot (una URL de callback de Meta) | Gateway de Hermes (hermes whatsapp-cloud) |
| Tiempo hasta la primera respuesta | Mayor: configuración del puente, la bandeja y el bot | Menor: asistente de configuración y túnel |
| Cumplimiento / operaciones | La auditoría de conversaciones está en Chatwoot; las sesiones de Hermes registran el razonamiento del agente | Tú gestionas ambos registros de auditoría |
Arquitectura de referencia
CUSTOMER PHONE
|
v
+---------------------+
| WhatsApp Cloud API |
| (Meta Graph API) |
+----------+----------+
| HTTPS webhook
v
+---------------------+
| Chatwoot |
| WhatsApp inbox |
| conversations DB |
+----------+----------+
|
message_created webhook (Agent Bot)
v
+---------------------+
| Bridge service |
| (FastAPI) |
| - verify Chatwoot |
| - map conversation |
| - call Hermes API |
+----------+----------+
| OpenAI-compatible HTTP
v
+---------------------+
| Hermes API server |
| (+ AIAgent loop) |
+----------+----------+
|
v
+---------------------+
| OpenAI API |
| (gpt-4o, etc.) |
+---------------------+
Reply path:
Bridge --POST--> Chatwoot Messages API (agent bot token)
--> Chatwoot --> Meta --> customer WhatsApp
¿Por qué un servicio puente?
El Agent Bot de Chatwoot envía eventos message_created a tu outgoing_url. Hermes expone un servidor API compatible con OpenAI, no un adaptador nativo para Chatwoot. Un puente ligero traduce entre los dos protocolos.
El puente debe ser simple: validar los webhooks, extraer el mensaje del usuario, llamar a Hermes y publicar la respuesta. Mantén las skills, las herramientas, la memoria y la lógica de sesiones dentro de Hermes.
Aplica aquí los patrones de la Parte 1
La Parte 1 describió a Hermes como un harness, no como un simple proxy de LLM. Lleva estos patrones al stack de WhatsApp:
| Patrón de la Parte 1 | Implementación en WhatsApp |
|---|---|
| Sesiones como infraestructura | Clave conversation de Responses API por conversación de Chatwoot (ver Paso 5) |
| Instrucciones dentro del repositorio | AGENTS.md para reglas de soporte, palabra clave HANDOFF y tono, no prompts codificados en el puente |
| Exposición de herramientas según la interfaz | Limita las herramientas al toolset de WhatsApp en el Paso 3; evita exponer toda la terminal o el navegador en el chat con clientes |
| Memoria acotada + recuperación bajo demanda | Deja que Hermes cargue MEMORY.md / skills; el puente no inyecta texto de políticas |
| Controles de aprobación de escritura | skills.write_approval opcional si el bot aprende macros de soporte de las conversaciones |
Si tu bot de soporte también necesita fundamentar sus respuestas en documentos, consulta Aplicaciones RAG en la práctica para conocer los controles de evaluación antes de habilitar skills de qmd/wiki en canales de atención al cliente.
Requisitos previos
Antes de conectar los componentes:
- Cuenta de Meta Business y una aplicación de WhatsApp con Cloud API habilitada
- Número de teléfono empresarial dedicado (no tu WhatsApp personal)
- Instancia de Chatwoot, en la nube o autohospedada, con una URL HTTPS pública
- Hermes Agent instalado en un host capaz de ejecutar el gateway/servidor API
- Clave de OpenAI API con facturación habilitada
- HTTPS público para Chatwoot (Meta debe poder acceder a tu webhook). La documentación de WhatsApp Cloud de Hermes menciona Cloudflare Tunnel y ngrok para desarrollo local; Chatwoot solo necesita una URL accesible para Meta.
Paso 1: configura la bandeja de WhatsApp Cloud API de Chatwoot
Sigue la guía manual de WhatsApp Cloud API de Chatwoot:
- Crea un token de acceso permanente de System User en Meta Business Manager con los permisos
whatsapp_business_messagingywhatsapp_business_management. - En Chatwoot: Settings → Inboxes → Add inbox → WhatsApp.
- Ingresa tu número de teléfono, Phone Number ID y Business Account ID desde la página API Setup de Meta.
- Copia la URL del webhook de Chatwoot (formato:
https://<your-chatwoot>/webhooks/whatsapp/<phone_number>) y el token de verificación. - En Meta Developer Portal → WhatsApp → Configuration, pega la URL de callback y el token de verificación. Suscríbete al campo messages.
Paso crítico en Meta: suscribe la aplicación a tu WABA
Varios issues de GitHub de Chatwoot (#11725, #13097) reportan webhooks verificados a los que no llegan mensajes. Los reportes de la comunidad y el soporte de Meta señalan la necesidad de suscribir la aplicación a la WABA:
curl -X POST \
"https://graph.facebook.com/v<API_VERSION>/<WABA_ID>/subscribed_apps" \
-H "Authorization: Bearer <ACCESS_TOKEN>"
Reemplaza v<API_VERSION> por la versión de Graph API que aparece en el panel de tu aplicación de Meta (por ejemplo, v21.0 o una versión más reciente).
Confirma también en Webhook fields → messages que la entrega de mensajes esté suscrita. Chatwoot integró una automatización para esto en el PR #13278 (January 2026), pero aún conviene conocer el comando curl manual para depurar problemas.
Números de teléfono en modo de desarrollo
En modo de desarrollo, Meta restringe los números a los que puedes enviar mensajes. Agrega testers en API Setup → Manage phone number list. Si los mensajes salientes fallan con el error #131030 Recipient phone number not in allowed list, verifica que el formato del número almacenado coincida exactamente con el de la lista de números permitidos de Meta; el issue #13932 de Chatwoot documenta discrepancias en el formato de números argentinos.
Paso 2: configura Hermes con OpenAI
Hermes admite OpenAI mediante el proveedor openai-api. Según la documentación de proveedores de IA:
# ~/.hermes/.env
OPENAI_API_KEY=sk-...
# Interactive model selection
hermes model
# Choose "OpenAI API (direct)" and pick a model (e.g. gpt-4o)
O configúralo en ~/.hermes/config.yaml:
model:
provider: openai-api
default: gpt-4o
Verifica con un chat local:
hermes chat -q "Reply with exactly: Hermes OpenAI OK"
Consulta Configuración de modelos para conocer las variables de entorno específicas de cada proveedor y la estructura de config.yaml.
Opcional: contexto del proyecto para soporte
Agrega instrucciones específicas del repositorio que Hermes cargará automáticamente:
<!-- AGENTS.md in your project root -->
# Support Agent Rules
- You are Acme Corp's WhatsApp support assistant.
- Be concise. WhatsApp messages should be short paragraphs, not essays.
- If the user asks for a human, reply HANDOFF and stop.
- Never invent order IDs. Use the order_lookup tool when available.
Hermes carga AGENTS.md, .hermes.md y archivos de contexto compatibles según su documentación sobre el ensamblaje del prompt.
Paso 3: inicia el servidor API de Hermes
El servidor API expone Hermes como un endpoint HTTP compatible con OpenAI en el puerto 8642 por defecto.
# ~/.hermes/.env
API_SERVER_ENABLED=true
API_SERVER_KEY=change-me-to-a-long-random-secret
API_SERVER_HOST=127.0.0.1
API_SERVER_PORT=8642
# Start gateway (API server runs alongside it)
hermes gateway
Verificación de estado:
curl -H "Authorization: Bearer $API_SERVER_KEY" \
http://127.0.0.1:8642/health
El servidor API da acceso a quienes lo invocan al conjunto completo de herramientas de Hermes. Trata API_SERVER_KEY como un secreto de despliegue: la documentación lo exige incluso en loopback.
Limita las herramientas para WhatsApp
Los turnos de WhatsApp de cara al cliente no deberían exponer todas las herramientas de terminal y navegador. Según la documentación de solución de problemas de WhatsApp Cloud de Hermes, la asignación predeterminada de la plataforma usa el toolset hermes-whatsapp. Al iniciar el gateway para esta integración, restringe las herramientas expuestas a las que realmente necesitan los agentes de soporte:
# Example: start gateway with a WhatsApp-safe tool surface
hermes gateway --toolsets hermes-whatsapp,skills
Verifica el flag exacto para tu versión de Hermes (hermes gateway --help). Para llamadas exclusivas al servidor API, Hermes determina los toolsets de la plataforma api_server; confírmalos mediante GET /v1/toolsets con tu API_SERVER_KEY.
Paso 4: crea el Agent Bot de Chatwoot
Crea un bot con webhook en Chatwoot. Mediante la API (documentación para desarrolladores):
curl -X POST "https://<chatwoot-host>/api/v1/accounts/<account_id>/agent_bots" \
-H "api_access_token: <admin-token>" \
-H "Content-Type: application/json" \
-d '{
"name": "Hermes WhatsApp Agent",
"description": "Hermes + OpenAI support bot",
"outgoing_url": "https://<bridge-host>/chatwoot/webhook",
"bot_type": 0
}'
Guarda dos credenciales distintas de este paso:
| Credencial | Propósito |
|---|---|
access_token (agent bot) | El puente lo usa como api_access_token para publicar respuestas y cambiar el estado de la conversación |
| Secreto de firma del webhook (si aparece) | Es distinto del token del bot y se usa para verificar X-Chatwoot-Signature en los webhooks entrantes |
No confundas el access_token del bot con el token de verificación de WhatsApp de Meta ni con API_SERVER_KEY de Hermes. Según la guía de verificación de webhooks de Chatwoot, los webhooks a nivel de cuenta se firman con sha256=HMAC-SHA256(secret, "{timestamp}.{raw_body}"). Es posible que los webhooks de outgoing_url de Agent Bot aún no incluyan firmas en todas las versiones de Chatwoot (chatwoot/chatwoot#13809); implementa la verificación cuando tu instancia proporcione un secreto y, hasta entonces, restringe el tráfico entrante al puente mediante políticas de red.
Conecta el bot a tu bandeja de WhatsApp: Inbox Settings → Bot Configuration → selecciona el bot → Save. Las nuevas conversaciones tendrán el estado pending y Chatwoot enviará eventos mediante POST a tu puente.
Paso 5: construye el servicio puente
Un puente mínimo con FastAPI. Escucha los webhooks de Chatwoot Agent Bot, verifica las firmas cuando hay un secreto de firma disponible, llama a /v1/responses de Hermes con una clave conversation estable para mantener la continuidad entre turnos y publica la respuesta. El handler solo responde automáticamente a mensajes entrantes (message_type == 0) de conversaciones en estado pending.
Las reglas del canal (tono, palabra clave HANDOFF, macros de soporte) deben estar en AGENTS.md del Paso 2, no codificadas en el puente. Hermes añade las instructions del frontend a su prompt principal según la documentación del servidor API.
# bridge/main.py
import hashlib
import hmac
import json
import os
import time
import httpx
from fastapi import FastAPI, HTTPException, Request
app = FastAPI()
CHATWOOT_BASE = os.environ["CHATWOOT_BASE_URL"].rstrip("/")
CHATWOOT_ACCOUNT_ID = os.environ["CHATWOOT_ACCOUNT_ID"]
CHATWOOT_BOT_TOKEN = os.environ["CHATWOOT_BOT_TOKEN"]
CHATWOOT_BOT_WEBHOOK_SECRET = os.environ.get("CHATWOOT_BOT_WEBHOOK_SECRET", "")
HERMES_BASE = os.environ.get("HERMES_API_BASE", "http://127.0.0.1:8642")
HERMES_API_KEY = os.environ["HERMES_API_KEY"]
HERMES_MODEL = os.environ.get("HERMES_MODEL", "hermes-agent")
INCOMING = 0
OUTGOING = 1
def verify_chatwoot(request: Request, body: bytes) -> None:
"""Verify Chatwoot webhook signature when a signing secret is configured."""
if not CHATWOOT_BOT_WEBHOOK_SECRET:
return
timestamp = request.headers.get("X-Chatwoot-Timestamp", "")
received = request.headers.get("X-Chatwoot-Signature", "")
if not timestamp or not received:
raise HTTPException(status_code=401, detail="missing signature headers")
try:
ts = int(timestamp)
except ValueError as exc:
raise HTTPException(status_code=401, detail="invalid timestamp") from exc
if abs(time.time() - ts) > 300:
raise HTTPException(status_code=401, detail="stale webhook timestamp")
signed_payload = f"{ts}.".encode() + body
expected = "sha256=" + hmac.new(
CHATWOOT_BOT_WEBHOOK_SECRET.encode(),
signed_payload,
hashlib.sha256,
).hexdigest()
if not hmac.compare_digest(expected, received):
raise HTTPException(status_code=401, detail="invalid signature")
def is_incoming_message(message: dict) -> bool:
message_type = message.get("message_type")
return message_type in (INCOMING, "incoming")
def extract_response_text(data: dict) -> str:
for item in data.get("output", []):
if item.get("type") != "message":
continue
for part in item.get("content", []):
if part.get("type") == "output_text":
text = (part.get("text") or "").strip()
if text:
return text
return ""
async def ask_hermes(user_text: str, conversation_key: str) -> str:
# Responses API chains turns via `conversation` — unlike stateless /v1/chat/completions
payload = {
"model": HERMES_MODEL,
"input": user_text,
"conversation": conversation_key,
"store": True,
}
headers = {
"Authorization": f"Bearer {HERMES_API_KEY}",
"Content-Type": "application/json",
# Scopes long-term memory providers (e.g. Honcho) — not transcript replay by itself
"X-Hermes-Session-Key": conversation_key,
}
async with httpx.AsyncClient(timeout=120.0) as client:
resp = await client.post(
f"{HERMES_BASE}/v1/responses",
json=payload,
headers=headers,
)
resp.raise_for_status()
data = resp.json()
return extract_response_text(data)
async def post_chatwoot_reply(conversation_id: int, content: str) -> None:
url = (
f"{CHATWOOT_BASE}/api/v1/accounts/{CHATWOOT_ACCOUNT_ID}"
f"/conversations/{conversation_id}/messages"
)
headers = {"api_access_token": CHATWOOT_BOT_TOKEN}
payload = {
"content": content,
"message_type": OUTGOING,
"private": False,
}
async with httpx.AsyncClient(timeout=30.0) as client:
resp = await client.post(url, json=payload, headers=headers)
resp.raise_for_status()
async def handoff_to_human(conversation_id: int) -> None:
"""Toggle conversation status to open so human agents can take over."""
url = (
f"{CHATWOOT_BASE}/api/v1/accounts/{CHATWOOT_ACCOUNT_ID}"
f"/conversations/{conversation_id}/toggle_status"
)
headers = {"api_access_token": CHATWOOT_BOT_TOKEN}
payload = {"status": "open"}
async with httpx.AsyncClient(timeout=30.0) as client:
resp = await client.post(url, json=payload, headers=headers)
resp.raise_for_status()
@app.post("/chatwoot/webhook")
async def chatwoot_webhook(request: Request):
body = await request.body()
verify_chatwoot(request, body)
event = json.loads(body)
if event.get("event") != "message_created":
return {"status": "ignored"}
message = event.get("message", {})
conversation = event.get("conversation", {})
if conversation.get("status") != "pending":
return {"status": "ignored"}
if not is_incoming_message(message):
return {"status": "ignored"}
content = (message.get("content") or "").strip()
if not content:
return {"status": "ignored"}
conversation_id = conversation["id"]
conversation_key = f"chatwoot:wa:{conversation_id}"
reply = await ask_hermes(content, conversation_key=conversation_key)
if reply.upper().startswith("HANDOFF"):
await post_chatwoot_reply(
conversation_id,
"Connecting you with a human agent. Please hold.",
)
await handoff_to_human(conversation_id)
return {"status": "handoff"}
await post_chatwoot_reply(conversation_id, reply)
return {"status": "ok"}
Ejecútalo con:
export CHATWOOT_BASE_URL=https://support.example.com
export CHATWOOT_ACCOUNT_ID=1
export CHATWOOT_BOT_TOKEN=<agent-bot-access-token>
export CHATWOOT_BOT_WEBHOOK_SECRET=<webhook-signing-secret-if-available>
export HERMES_API_KEY=<API_SERVER_KEY>
export HERMES_MODEL=hermes-agent
uvicorn bridge.main:app --host 0.0.0.0 --port 8080
Expón el puente mediante HTTPS (proxy inverso o túnel). Chatwoot debe poder acceder a outgoing_url.
Continuidad de la sesión
/v1/chat/completions es sin estado: cada solicitud debe incluir el historial completo en messages[] o el modelo olvidará los turnos anteriores. El puente de ejemplo usa POST /v1/responses con una clave conversation estable (chatwoot:wa:{conversation_id}) para que Hermes encadene los turnos en el servidor.
| Header / parámetro | Propósito |
|---|---|
conversation (body de Responses API) | Continuidad del historial entre turnos para esta conversación de Chatwoot |
X-Hermes-Session-Key | Delimita los proveedores de memoria a largo plazo (por ejemplo, Honcho) por canal; por sí solo no reproduce el historial |
X-Hermes-Session-Id | ID de sesión asociado al historial cuando se usa /api/sessions/{id}/chat en su lugar |
Alternativas si el encadenamiento de Responses API no está disponible en tu versión de Hermes:
POST /api/sessions/{id}/chat: crea una sesión de Hermes por conversación de Chatwoot y guarda el ID de sesión en los atributos personalizados de Chatwoot- Incluye el historial de mensajes de Chatwoot en un arreglo
messages[]de/v1/chat/completions, que no mantiene estado
Consulta Agentes con estado y sin estado en producción para una perspectiva general de las ventajas y desventajas.
Paso 6: flujo de mensajes de extremo a extremo
1. Customer sends "Where is order 12345?" on WhatsApp
2. Meta POSTs webhook → Chatwoot /webhooks/whatsapp/<phone>
3. Chatwoot creates/updates conversation, fires Agent Bot webhook
4. Bridge receives message_created event
5. Bridge POSTs to Hermes `/v1/responses` with `conversation=chatwoot:wa:{id}` (OpenAI-backed)
6. Hermes may call tools (order lookup, knowledge search, etc.)
7. Bridge receives assistant text from the Responses `output` array
8. Bridge POSTs outgoing message to Chatwoot Messages API
9. Chatwoot sends via WhatsApp Cloud API → Meta → customer
Devuelve HTTP 200 desde el handler del webhook tan pronto como hayas validado el evento. El puente de ejemplo procesa la solicitud de forma síncrona para mayor claridad; si los ciclos de herramientas de Hermes superan el timeout del webhook de Chatwoot, mueve ask_hermes a una tarea en segundo plano y publica la respuesta cuando termine el agente.
Presupuesto de latencia: Meta → Chatwoot → puente → ciclo del agente de Hermes → Chatwoot → Meta. Los turnos de Hermes que usan muchas herramientas pueden tardar más de lo que esperan los usuarios de WhatsApp. Para preguntas frecuentes simples, restringe las herramientas del toolset de WhatsApp; para consultas complejas, envía primero una confirmación rápida como «Estoy consultando eso…».
Transferencia a un agente humano
El valor de Chatwoot está en el proceso de transferencia. Según la documentación de transferencia de Agent Bot, las conversaciones gestionadas por el bot comienzan con estado pending. Cuando el bot necesita a una persona, llama a la API Toggle Status con {"status": "open"} usando el access_token del agent bot.
| Situación | Acción |
|---|---|
| El usuario pide hablar con una persona | El puente publica un mensaje de transferencia; toggle_status → open |
| Hermes tiene baja confianza | Responde y ofrece transferir a una persona; opcionalmente, agrega una nota private para los agentes con un resumen de la traza |
| Un agente toma la conversación en la interfaz de Chatwoot | El estado de la conversación pasa a open; el puente ignora las conversaciones que no están en pending |
| Palabras clave de escalación | El puente busca coincidencias con una regex antes de llamar a Hermes |
Después de la transferencia, el puente debe dejar de responder automáticamente: verifica conversation.status == "pending" y message_type == 0 (entrante). Los agentes pueden devolver una conversación a la cola del bot cambiando el estado de nuevo a pending.
Reglas de mensajería de WhatsApp
Estas son restricciones de Meta, no límites de Hermes ni de Chatwoot:
Ventana de atención al cliente de 24 horas
La documentación de WhatsApp Cloud de Hermes señala que las respuestas enviadas más de 24 horas después del último mensaje del usuario requieren una plantilla aprobada previamente. La API de mensajes de Chatwoot admite template_params para mensajes de plantilla estructurados.
Planifica:
- Respuestas de IA de texto libre dentro de la ventana de 24h
- Plantillas aprobadas para retomar el contacto fuera de la ventana
Tokens y seguridad
| Secreto | Dónde | Propósito |
|---|---|---|
| Token de Meta System User | Configuración de la bandeja de Chatwoot | Enviar y recibir mensajes de WhatsApp mediante Cloud API |
| Token de verificación del webhook de Meta | Meta Developer Portal + bandeja de Chatwoot | Intercambio de verificación GET del webhook |
Meta App Secret (WHATSAPP_APP_SECRET) | Solo conexión directa de Hermes (hermes whatsapp-cloud) | Valida las firmas de los webhooks de Meta cuando Hermes los recibe directamente |
access_token del agent bot de Chatwoot | Servicio puente | Publicar respuestas y cambiar el estado de la conversación |
| Secreto de firma del webhook del bot de Chatwoot | Servicio puente (opcional) | Verificar X-Chatwoot-Signature en los eventos entrantes de Agent Bot |
API_SERVER_KEY de Hermes | Servicio puente + ~/.hermes/.env de Hermes | Autenticar las llamadas a /v1/chat/completions |
| Clave de OpenAI API | Solo ~/.hermes/.env de Hermes | Proveedor de LLM para el ciclo del agente |
Nunca coloques las claves de OpenAI o Hermes en el frontend de Chatwoot ni en el código del lado del cliente del puente.
Alternativa: Hermes directo (sin Chatwoot)
Si no necesitas una bandeja de equipo, puedes prescindir de Chatwoot:
hermes whatsapp-cloud # guided wizard
hermes gateway # starts Cloud API webhook on port 8090
Usa Cloudflare Tunnel para exponer https://<tunnel>/whatsapp/webhook a Meta. Configura OPENAI_API_KEY, establece WHATSAPP_CLOUD_ALLOWED_USERS y tendrás un ciclo directo Hermes ↔ WhatsApp ↔ OpenAI.
La contrapartida: no tendrás panel para agentes, interfaz de transferencia a personas ni analítica de conversaciones en Chatwoot. Es más rápido de implementar para bots personales.
Topología de despliegue
┌─────────────────────────────────────────────────────────┐
│ VPS / container host │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌─────────────┐ │
│ │ Hermes │ │ Bridge │ │ (optional) │ │
│ │ gateway + │◄──│ FastAPI │ │ nginx/Caddy │ │
│ │ API :8642 │ │ :8080 │ │ TLS proxy │ │
│ └──────────────┘ └──────┬───────┘ └─────────────┘ │
│ │ │
└─────────────────────────────┼────────────────────────────┘
│ HTTPS
v
┌──────────────────┐
│ Chatwoot (Cloud │
│ or self-hosted) │
└────────┬─────────┘
│
v
┌──────────────────┐
│ Meta WhatsApp │
│ Cloud API │
└──────────────────┘
Despliegue mínimo viable:
- Chatwoot en un host administrado o autohospedado con TLS
- Hermes + puente en un host con entrada HTTPS pública (VM, contenedor o túnel)
- Token de System User (no un token temporal de 24h)
- Supervisor de procesos para
hermes gatewayyuvicorn
Lista de verificación operativa
[ ] Meta app subscribed to WABA (subscribed_apps curl)
[ ] Webhook messages field enabled in Meta dashboard
[ ] Chatwoot inbox receives inbound test message
[ ] Agent Bot webhook fires on message_created
[ ] Bridge verifies webhook signatures when a signing secret is configured
[ ] Hermes API health check passes with API_SERVER_KEY
[ ] OpenAI billing and model access confirmed
[ ] End-to-end reply delivered to WhatsApp
[ ] Handoff path tested (human agent can take conversation)
[ ] 24h window + template message path documented for your team
[ ] Secrets rotated; no tokens in git
Quién se encarga de qué (operaciones)
- Rotación del token de Meta: plataforma/DevOps; token de System User en la configuración de la bandeja de Chatwoot
- Aprobación de plantillas de WhatsApp: operaciones de soporte + marketing; plantillas para retomar el contacto fuera de la ventana de 24h
- SLA de escalación: responsable de soporte; define cuándo el puente debe ejecutar
toggle_status→open - Auditoría de conversaciones: Chatwoot para los hilos visibles al cliente; base de datos de sesiones de Hermes para las trazas de razonamiento del agente
Fallas comunes
| Síntoma | Causa probable | Solución |
|---|---|---|
| El webhook se verifica, pero no llegan mensajes a Chatwoot | La aplicación no está suscrita a la WABA | POST a subscribed_apps |
El envío falla con #131030 | El formato del número no coincide en modo de desarrollo | Alinea el formato de la lista de números permitidos con el del contacto almacenado |
| El puente recibe un 401 de Hermes | API_SERVER_KEY incorrecta | Haz que HERMES_API_KEY del puente coincida con la variable de entorno de Hermes |
| Respuestas duplicadas | El puente procesa mensajes salientes o conversaciones abiertas | Filtra por message_type == 0 y status == "pending" |
| Respuesta vacía de Hermes | Configuración incorrecta del modelo o proveedor | Ejecuta hermes model + hermes chat -q "test" localmente |
| Chatwoot indica «reauthorization required» | El token expiró o el modo de la aplicación es incorrecto | Renueva el token de System User; verifica que la aplicación de Meta esté en modo activo |
| Respuestas lentas | Ciclo de herramientas de Hermes en WhatsApp | Limita las herramientas al toolset hermes-whatsapp; envía primero una confirmación para consultas lentas |
| El bot olvida turnos anteriores | Se usó /v1/chat/completions sin estado y sin historial | Cambia a la clave conversation de /v1/responses o a /api/sessions/{id}/chat |
Limitaciones de este puente mínimo
Antes de considerar que el ejemplo está listo para producción, ten en cuenta lo que omite:
- Sin idempotencia: los reintentos de webhooks de Chatwoot pueden producir respuestas duplicadas si no las deduplicas mediante
message.idoX-Chatwoot-Delivery. - Llamada síncrona a Hermes: los turnos con muchas herramientas pueden superar los timeouts del webhook; procesa de forma asíncrona en producción.
- Sin límite de tasa: el puente acepta todos los eventos
message_created; agrega una limitación por conversación. - La verificación de firmas depende de la versión: confirma la firma de Agent Bot en tu versión de Chatwoot (#13809).
- Sin eliminación de PII en los logs: elimina el contenido de los mensajes de clientes antes de registrar los payloads del puente.
Qué agregar después
Este artículo cubre la conexión de los componentes. Los bots de soporte suelen agregar:
- Mapeo de sesiones de Chatwoot → Hermes en Postgres/Redis
- Transferencia adecuada mediante las APIs de asignación de conversaciones de Chatwoot
- Trazas de evaluación (¿respondió el bot a partir de la fuente correcta?)
- Límites de tasa en el puente
- Skills de Hermes para tus macros de soporte y tu política de reembolsos
- Biblioteca de plantillas de mensajes para campañas salientes fuera de la ventana de 24h
Conclusión
Construir un agente de WhatsApp con Cloud API, Chatwoot, Hermes y OpenAI es un problema de composición, no la instalación de un solo producto:
- Chatwoot gestiona el webhook de WhatsApp y la bandeja de equipo
- Hermes + OpenAI se encargan del cerebro del agente, las herramientas y la memoria
- Un puente ligero conecta el protocolo Agent Bot de Chatwoot con la API de Hermes compatible con OpenAI
Esta separación te da mensajería mediante la API oficial de WhatsApp Cloud, transferencia a agentes humanos y un verdadero harness de agente, sin obligar a una sola herramienta a cumplir las tres funciones.
Fuentes
Fuentes primarias consultadas el July 3, 2026:
- Hermes Agent — WhatsApp Business (Cloud API) — conexión directa por webhook, variables de entorno (
WHATSAPP_APP_SECRET, etc.), ventana de 24h. hermes-agent.nousresearch.com/docs/user-guide/messaging/whatsapp-cloud - Hermes Agent — Referencia de variables de entorno —
API_SERVER_KEY, gateway y variables de entorno de proveedores. hermes-agent.nousresearch.com/docs/reference/environment-variables - Hermes Agent — Gateway de mensajería — capacidades de la plataforma, toolset
hermes-whatsapp. hermes-agent.nousresearch.com/docs/user-guide/messaging/ - Hermes Agent — Servidor API — endpoint compatible con OpenAI, encadenamiento de
conversationen Responses API,X-Hermes-Session-Key, Sessions API. hermes-agent.nousresearch.com/docs/user-guide/features/api-server - Hermes Agent — Proveedores de IA —
OPENAI_API_KEY, proveedoropenai-api. hermes-agent.nousresearch.com/docs/integrations/providers - Hermes Agent — Configuración —
config.yaml, configuración del proveedor de modelos. hermes-agent.nousresearch.com/docs/user-guide/configuration - Chatwoot — Cómo configurar un canal de WhatsApp (flujo manual) — bandeja de Cloud API, formato de URL del webhook, token de verificación. chatwoot.com/hc/user-guide/articles/1756799850-how-to-setup-a-whats_app-channel-manual-flow
- Chatwoot — Cómo usar Agent bots — eventos de webhook, transferencia mediante
toggle_status, conexión a la bandeja. chatwoot.com/hc/user-guide/articles/1677497472-how-to-use-agent-bots - Chatwoot — Cómo usar webhooks — verificación HMAC de
X-Chatwoot-Signature/X-Chatwoot-Timestamp. chatwoot.com/hc/user-guide/articles/1677693021-how-to-use-webhooks - Documentación para desarrolladores de Chatwoot — Crear Agent Bot — payload de la API y
access_token. developers.chatwoot.com/api-reference/account-agentbots/create-an-agent-bot - Documentación para desarrolladores de Chatwoot — Cambiar estado —
POST .../toggle_statuscon{"status": "open"}. developers.chatwoot.com/api-reference/conversations/toggle-status - Documentación para desarrolladores de Chatwoot — Crear mensaje nuevo — respuestas salientes,
template_paramsde WhatsApp. developers.chatwoot.com/api-reference/messages/create-new-message - Meta — Webhooks de registro integrado de WhatsApp — contexto de suscripción a la WABA. developers.facebook.com/docs/whatsapp/embedded-signup/webhooks
Evidencia de los registros de issues (secundaria, para problemas operativos):
- Chatwoot #11725 —
subscribed_appsy entrega de mensajes entrantes. github.com/chatwoot/chatwoot/issues/11725 - Chatwoot #13097 — solución con
subscribed_appspara mensajes entrantes que no llegan. github.com/chatwoot/chatwoot/issues/13097 - Chatwoot #13932 — formato de números de teléfono en modo de desarrollo
#131030. github.com/chatwoot/chatwoot/issues/13932 - Chatwoot #13809 — diferencia entre el secreto de firma del webhook de Agent Bot y
access_token. github.com/chatwoot/chatwoot/issues/13809
Serie:
- Parte 1: arquitectura de Hermes Agent y lecciones sobre RAG
- Aplicaciones RAG en la práctica — si los bots de soporte necesitan fundamentar sus respuestas en documentos con controles de evaluación
- Agentes con estado y sin estado en producción — ventajas y desventajas de la continuidad de sesión