Hermes Agent · Parte 2

Construye un agente de WhatsApp con Cloud API, Chatwoot, Hermes y OpenAI

Una guía práctica para conectar WhatsApp Cloud API de Meta con Hermes Agent a través de Chatwoot y usar OpenAI: arquitectura, servicio puente, transferencia a agentes humanos y problemas operativos frecuentes.

25 min de lectura

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 conversation de la Responses API de Hermes para mantener la continuidad entre turnos; X-Hermes-Session-Key delimita 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:

ComponenteFunción
WhatsApp Cloud APICanal oficial de Meta para mensajes entrantes y salientes de WhatsApp
ChatwootBandeja de equipo, historial de conversaciones, asignación de agentes y transferencia a personas
Hermes AgentRuntime de agente autohospedado: herramientas, skills, memoria y sesiones
OpenAI APIProveedor 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 paraEquipos de soporte, transferencia a personas y auditoría de conversaciones en una bandeja de equipoBots individuales, asistentes personales y obtener el primer mensaje lo más rápido posible
Visibilidad para el equipoLos agentes ven todo el historial de WhatsApp en ChatwootNo hay bandeja compartida; debes implementar tus propios logs
Intervención humanaNativa mediante Chatwoot toggle_status → openDebes implementar la escalación
Responsable del webhookChatwoot (una URL de callback de Meta)Gateway de Hermes (hermes whatsapp-cloud)
Tiempo hasta la primera respuestaMayor: configuración del puente, la bandeja y el botMenor: asistente de configuración y túnel
Cumplimiento / operacionesLa auditoría de conversaciones está en Chatwoot; las sesiones de Hermes registran el razonamiento del agenteTú 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 1Implementación en WhatsApp
Sesiones como infraestructuraClave conversation de Responses API por conversación de Chatwoot (ver Paso 5)
Instrucciones dentro del repositorioAGENTS.md para reglas de soporte, palabra clave HANDOFF y tono, no prompts codificados en el puente
Exposición de herramientas según la interfazLimita 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 demandaDeja que Hermes cargue MEMORY.md / skills; el puente no inyecta texto de políticas
Controles de aprobación de escrituraskills.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:

  1. Cuenta de Meta Business y una aplicación de WhatsApp con Cloud API habilitada
  2. Número de teléfono empresarial dedicado (no tu WhatsApp personal)
  3. Instancia de Chatwoot, en la nube o autohospedada, con una URL HTTPS pública
  4. Hermes Agent instalado en un host capaz de ejecutar el gateway/servidor API
  5. Clave de OpenAI API con facturación habilitada
  6. 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:

  1. Crea un token de acceso permanente de System User en Meta Business Manager con los permisos whatsapp_business_messaging y whatsapp_business_management.
  2. En Chatwoot: Settings → Inboxes → Add inbox → WhatsApp.
  3. Ingresa tu número de teléfono, Phone Number ID y Business Account ID desde la página API Setup de Meta.
  4. Copia la URL del webhook de Chatwoot (formato: https://<your-chatwoot>/webhooks/whatsapp/<phone_number>) y el token de verificación.
  5. 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:

CredencialPropó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ámetroPropósito
conversation (body de Responses API)Continuidad del historial entre turnos para esta conversación de Chatwoot
X-Hermes-Session-KeyDelimita los proveedores de memoria a largo plazo (por ejemplo, Honcho) por canal; por sí solo no reproduce el historial
X-Hermes-Session-IdID 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ónAcción
El usuario pide hablar con una personaEl puente publica un mensaje de transferencia; toggle_status → open
Hermes tiene baja confianzaResponde 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 ChatwootEl estado de la conversación pasa a open; el puente ignora las conversaciones que no están en pending
Palabras clave de escalaciónEl 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

SecretoDóndePropósito
Token de Meta System UserConfiguración de la bandeja de ChatwootEnviar y recibir mensajes de WhatsApp mediante Cloud API
Token de verificación del webhook de MetaMeta Developer Portal + bandeja de ChatwootIntercambio 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 ChatwootServicio puentePublicar respuestas y cambiar el estado de la conversación
Secreto de firma del webhook del bot de ChatwootServicio puente (opcional)Verificar X-Chatwoot-Signature en los eventos entrantes de Agent Bot
API_SERVER_KEY de HermesServicio puente + ~/.hermes/.env de HermesAutenticar las llamadas a /v1/chat/completions
Clave de OpenAI APISolo ~/.hermes/.env de HermesProveedor 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 gateway y uvicorn

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íntomaCausa probableSolución
El webhook se verifica, pero no llegan mensajes a ChatwootLa aplicación no está suscrita a la WABAPOST a subscribed_apps
El envío falla con #131030El formato del número no coincide en modo de desarrolloAlinea el formato de la lista de números permitidos con el del contacto almacenado
El puente recibe un 401 de HermesAPI_SERVER_KEY incorrectaHaz que HERMES_API_KEY del puente coincida con la variable de entorno de Hermes
Respuestas duplicadasEl puente procesa mensajes salientes o conversaciones abiertasFiltra por message_type == 0 y status == "pending"
Respuesta vacía de HermesConfiguración incorrecta del modelo o proveedorEjecuta hermes model + hermes chat -q "test" localmente
Chatwoot indica «reauthorization required»El token expiró o el modo de la aplicación es incorrectoRenueva el token de System User; verifica que la aplicación de Meta esté en modo activo
Respuestas lentasCiclo de herramientas de Hermes en WhatsAppLimita las herramientas al toolset hermes-whatsapp; envía primero una confirmación para consultas lentas
El bot olvida turnos anterioresSe usó /v1/chat/completions sin estado y sin historialCambia 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.id o X-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:

  1. 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
  2. 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
  3. Hermes Agent — Gateway de mensajería — capacidades de la plataforma, toolset hermes-whatsapp. hermes-agent.nousresearch.com/docs/user-guide/messaging/
  4. Hermes Agent — Servidor API — endpoint compatible con OpenAI, encadenamiento de conversation en Responses API, X-Hermes-Session-Key, Sessions API. hermes-agent.nousresearch.com/docs/user-guide/features/api-server
  5. Hermes Agent — Proveedores de IA — OPENAI_API_KEY, proveedor openai-api. hermes-agent.nousresearch.com/docs/integrations/providers
  6. Hermes Agent — Configuración — config.yaml, configuración del proveedor de modelos. hermes-agent.nousresearch.com/docs/user-guide/configuration
  7. 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
  8. 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
  9. 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
  10. 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
  11. Documentación para desarrolladores de Chatwoot — Cambiar estado — POST .../toggle_status con {"status": "open"}. developers.chatwoot.com/api-reference/conversations/toggle-status
  12. Documentación para desarrolladores de Chatwoot — Crear mensaje nuevo — respuestas salientes, template_params de WhatsApp. developers.chatwoot.com/api-reference/messages/create-new-message
  13. 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):

  1. Chatwoot #11725 — subscribed_apps y entrega de mensajes entrantes. github.com/chatwoot/chatwoot/issues/11725
  2. Chatwoot #13097 — solución con subscribed_apps para mensajes entrantes que no llegan. github.com/chatwoot/chatwoot/issues/13097
  3. Chatwoot #13932 — formato de números de teléfono en modo de desarrollo #131030. github.com/chatwoot/chatwoot/issues/13932
  4. Chatwoot #13809 — diferencia entre el secreto de firma del webhook de Agent Bot y access_token. github.com/chatwoot/chatwoot/issues/13809

Serie:

  1. Parte 1: arquitectura de Hermes Agent y lecciones sobre RAG
  2. Aplicaciones RAG en la práctica — si los bots de soporte necesitan fundamentar sus respuestas en documentos con controles de evaluación
  3. Agentes con estado y sin estado en producción — ventajas y desventajas de la continuidad de sesión