Esta sección describe cómo funcionan los canales WebSocket de chat (JSBOX) en la API de Mailoo, qué eventos transportan y qué debes configurar en el host de la API pública en producción (proxy, TLS, escalado).
Resumen de la integración de chat (REST, claves, BFF): chat-widget{.interpreted-text role="doc"} y chat-widget-nextjs-example{.interpreted-text role="doc"}.
::: {.contents local=""} :::
Dónde vive el WebSocket
El WebSocket no escucha en un puerto separado. El mismo proceso Node.js que sirve HTTP (Hono) crea un http.Server y, en upgrade, conecta clientes a ws (WebSocketServer con noServer: true). Las rutas son fijas en el código del servidor:
- Propietario (panel) ---
/api/v1/chat/ws - Visitante (widget) ---
/api/v1/chat/visitor-ws
En producción, los clientes se conectan al mismo host y esquema que la API REST, por ejemplo wss://api.example.com/api/v1/chat/ws?.... No necesitas un "puerto de WebSocket" separado --- HTTPS/WSS en el servicio API publicado (normalmente 443 detrás de un balanceador de carga) es suficiente.
Dos canales y autenticación
Propietario (panel): /api/v1/chat/ws
Parámetros de consulta: integrationId, token.
token--- JWT de acceso Keycloak (igual queAuthorization: Bearerpara REST). En desarrollo, un bypass configurado puede aceptar un token de desarrollo (como en la aplicación).- Después del
upgrade, la API verifica el usuario y que la integración existe, su tipo es JSBOX y pertenece a ese usuario. - En caso de éxito, el cliente se registra en el hub en memoria por
integrationIdy recibe{"type":"connected","integrationId":"..."}.
Visitante: /api/v1/chat/visitor-ws
Parámetros de consulta: projectUid, integrationId, sessionPublicId.
- Sin clave API: la confianza viene de una sesión de chat existente en la BD (creada por el primer mensaje del webhook) con
publicId,integrationIdyproject.uidcoincidentes. - En caso de éxito --- registro en el hub por
integrationId+sessionPublicId, respuesta{"type":"connected","sessionPublicId":"..."}.
Hub y eventos
La implementación es un módulo en memoria (mapas de conexión por integración y por sesión). Cuando aparece un nuevo mensaje de chat (entrante del visitante o respuesta saliente del propietario), el servidor difunde JSON como:
{
"type": "chat.message",
"payload": {
"messageId": "...",
"integrationId": "...",
"sessionPublicId": "...",
"messageType": "INBOUND|OUTBOUND",
"content": "...",
"senderEmail": "...",
"senderName": null,
"createdAt": "..."
}
}
Los clientes usan esto para actualizar la UI sin polling REST completo. Si WebSocket no está disponible, la aplicación puede recurrir a polling (como en la consola de chat o el widget).
Lista de verificación de producción (API pública de Mailoo)
Lista de verificación para equipos que exponen la API de Mailoo en internet. No se publica un "puerto de WebSocket" separado: necesitas un proxy inverso, TLS y un modelo de escalado correctos.
-
Mismo origen que REST
El widget del navegador y el panel construyen URLs como
wss://<API_HOST>/api/v1/chat/.... Asegúrate de queAPI_PUBLIC_URL/ DNS público apunten al mismo host que ejecuta este proceso (o un balanceador de carga estable delante). -
TLS y ``wss:``
En un sitio HTTPS el widget debe usar WSS. El proxy necesita certificados válidos; la ruta a la API coincide con HTTPS ordinario.
-
Proxy: soporte de Upgrade
Para
/api/v1/chat/wsy/api/v1/chat/visitor-ws(y para la API en general si WS comparte el mismolocation) necesitas:
-
HTTP/1.1 hacia el upstream;
-
Cabeceras
UpgradeyConnection(patrón típico de WebSocket); -
timeouts de lectura aumentados para conexiones de larga duración (decenas de minutos / horas), o el proxy cortará sockets inactivos.
Fragmento de ejemplo nginx (mismo upstream que REST):
location / { proxy_pass http://api_backend; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_read_timeout 3600s; proxy_send_timeout 3600s; }Asegúrate de que tu proxy inverso (o balanceador de carga en la nube) reenvíe las cabeceras WebSocket Upgrade/Connection y use timeouts largos de lectura/envío en la ubicación de la API.
-
Firewall / grupos de seguridad
Mismas reglas que una API HTTPS normal (por ejemplo entrada 443 al balanceador de carga). Salida a BD, Keycloak, S3 --- según tu arquitectura; WebSocket no cambia eso.
-
Múltiples réplicas de la API (importante)
El hub de chat actual vive solo en la memoria del proceso. Una conexión de propietario en réplica A no verá una difusión realizada en réplica B si otro pod manejó el mensaje.
Opciones:
-
ejecutar temporalmente una única réplica de la API para tiempo real confiable;
-
o habilitar sesiones pegajosas (cliente fijado a un pod) por cookie/IP en el balanceador --- esto solo ayuda si los webhooks de creación de mensaje y ambos WebSockets llegan a la misma instancia (poco confiable con múltiples workers);
-
el escalado horizontal de tiempo real verdadero necesita un broker compartido (Redis Pub/Sub, etc.) --- no implementado en el código actual.
Documenta la estrategia elegida en tu runbook de producción.
-
Lado del sitio (no la API)
El BFF de Next.js y el entorno como
MAILOO_CHAT_WS_ORIGIN/ base de la API deben apuntar al host de la API pública que sirve WSS. Eso se configura en la aplicación del sitio, no como un paso separado de publicación de WebSocket.
Resumen breve
Pregunta Respuesta
¿Puerto WebSocket separado? No --- mismo servicio y ruta que REST (vía 443 y el proxy).
¿Qué debe soportar el proxy? Upgrade/WebSocket, timeouts largos, TLS para wss.
¿Algo que exponer además de HTTP(S)? No, si la API ya está publicada sobre HTTPS.
¿Múltiples pods de API? Hub en memoria: réplica única, sesiones pegajosas o un futuro bus de eventos.
Consulta también
chat-widget{.interpreted-text role="doc"} --- REST, claves, CORS, sesioneschat-widget-nextjs-example{.interpreted-text role="doc"} --- BFF de Next.js y entorno de origen WebSocket- OpenAPI:
https://api.mailoo.app/docs/v1