Eventos y webhooks¶
ZEngine publica eventos de dominio en EventBridge bus zen-dev-domain-events.
Integradores pueden suscribirse vía:
- Webhook HTTPS (recomendado): registras una URL, ZEngine hace POST con firma HMAC.
- Cross-account EventBridge: tu cuenta AWS recibe los eventos directamente (configuración por ticket de plataforma).
Registro¶
Admin SPA → Integraciones → Webhooks → + Nuevo. Campos:
- URL HTTPS pública.
- Filtro de event types (lista o
*). - Secret para HMAC (se genera o lo provees).
Firma HMAC¶
Cada POST incluye:
x-zen-signature: t=1747576981,v1=ab12cd...
x-zen-event-type: Conversation.Started
x-zen-event-id: 01HZ...
x-zen-tenant-id: iplacex-demo
v1 es HMAC-SHA256(secret, "{t}.{body}"). Valida tolerancia de timestamp
de ±5 min.
const expected = crypto
.createHmac('sha256', secret)
.update(`${t}.${rawBody}`)
.digest('hex');
if (!timingSafeEqual(expected, v1)) reject(401);
Catálogo de eventos¶
El registro canónico vive en packages/event-contracts/ (Zod + JSON Schema).
Resumen V1.1:
Tenant.*¶
| Type | Cuándo | Payload clave |
|---|---|---|
Tenant.Created |
Nuevo tenant aprovisionado. | tenantId, name, tier, createdAt |
Tenant.Updated |
Cambia configuración (outbox de tenant-mgmt). | tenantId, changedFields, version |
Tenant.Suspended |
Tenant suspendido. | tenantId, reason |
Conversation.*¶
| Type | Cuándo | Payload clave |
|---|---|---|
Conversation.Started |
Primera interacción de una sesión. | conversationId, channel, widgetId, user |
Conversation.MessageReceived |
Mensaje IN del usuario. | conversationId, messageId, text, attachments |
Conversation.MessageDelivered |
Mensaje OUT entregado. | conversationId, messageId, channel, provider |
Conversation.HandedOff |
Transferido a humano. | conversationId, channel, queue |
Conversation.Closed |
Conversación finalizada. | conversationId, duration, resolution |
Channel.*¶
| Type | Cuándo |
|---|---|
Channel.Enabled |
Canal activado para un tenant. |
Channel.Disabled |
Canal desactivado. |
Channel.TemplateApproved |
Template WhatsApp aprobado por Meta. |
Channel.TemplateRejected |
Template rechazado. |
Survey.*¶
| Type | Cuándo |
|---|---|
Survey.Triggered |
NPS lanzado tras handoff/close. |
Survey.Completed |
Usuario respondió. Payload incluye score, comment. |
Survey.Skipped |
Usuario cerró sin responder. |
Envelope estándar¶
{
"id": "01HZ8...",
"source": "zen.flow-engine",
"detailType": "Conversation.MessageReceived",
"time": "2026-05-18T14:23:01.234Z",
"tenantId": "iplacex-demo",
"correlationId": "01HZ...",
"version": 1,
"detail": { /* payload específico */ }
}
version permite evolución compatible. Bumps mayores → nuevo detailType
(p. ej. Conversation.MessageReceived.v2).
Garantías¶
- At-least-once delivery. Tu consumer debe ser idempotente por
id. - Orden: no garantizado entre eventos. Usa
timeycorrelationId. - Reintentos: 24h con backoff exponencial. Si tu endpoint devuelve 4xx/5xx persistente, el evento va a DLQ y se notifica al tenant.
Sandbox local¶
Para probar sin exponer tu localhost: usa ngrok o cloudflared y
registra la URL temporal en el Admin SPA. Eventos se redirigirán ahí.