> ## Documentation Index
> Fetch the complete documentation index at: https://docs.zelto.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks

> Envía los resultados positivos de los monitores a tu sistema para actualizar el historial de tus clientes.

Conecta un receptor HTTPS público en **Configuración → Integraciones → Webhooks**.
Cuando un [monitor](/es/docs/monitors) seleccionado marca una llamada nueva,
Zelto envía un evento `monitor.matched`. Tu receptor puede guardar el resultado
en tu base de datos, CRM o historial del cliente. Zelto envía el evento a tu
endpoint HTTP; tu sistema controla la escritura en la base.

## Configurar un destino

Los propietarios y administradores gestionan los destinos y su historial.

1. Crea los monitores cuyos resultados positivos quieres recibir.
2. Elige **Agregar destino**, ingresa un nombre y una URL HTTPS pública en el
   puerto 443, y selecciona monitores (hasta 100), habilita las exportaciones
   completadas de monitores, o ambas opciones. **Seleccionar monitores
   actuales (hasta 100)** selecciona hasta 100 de la lista actual; los monitores futuros
   no se suscriben automáticamente.
3. Guarda el destino. Comienza **Pausado**. Copia el secreto de firma generado,
   que se muestra una sola vez, y guárdalo de forma segura en el receptor.
4. Elige **Enviar prueba**. Procesa `webhook.test` como se explica abajo y responde
   con cualquier código 2xx. Puedes probar el destino mientras está pausado.
5. Elige **Habilitar envíos** en los pasos de configuración del destino, o
   habilítalos desde **Editar destino**. Para `monitor.matched`, solo son elegibles
   las llamadas ingresadas después de la activación. Consulta **Última prueba** y el historial
   para confirmar la entrega; el programador normalmente recoge pendientes
   dentro de un minuto.

Para `monitor.matched`, la activación no envía eventos de llamadas existentes.
Agregar otro monitor conserva la cobertura de las suscripciones que no cambian.
Quitar y volver a agregar un monitor, o pausar y reactivar el destino, inicia la
cobertura desde la nueva activación. La hora original de la llamada del proveedor
puede ser anterior al ingreso en Zelto. Estas reglas de activación y suscripción
no limitan el contenido de las exportaciones completadas; una exportación solicitada
explícitamente puede incluir llamadas anteriores.

Los destinos muestran solo el hostname del receptor; las credenciales, rutas
y parámetros de consulta permanecen ocultos. Al editar, deja la URL vacía para conservarla. **Generar un nuevo secreto de
firma** rota el secreto y pausa el destino. Actualiza tu receptor, envía una prueba
y vuelve a habilitar los envíos. Cambiar la URL o el secreto cancela las entregas
pendientes; prepara tu receptor antes de habilitar los envíos. Una petición HTTP
ya iniciada puede llegar después de pausar o editar el destino.

## Eventos

### `monitor.matched`

Se registra un evento por el primer resultado positivo de cada llamada y monitor
mientras exista un destino elegible. Reprocesar o cambiar la versión del monitor
no crea otro evento para esa pareja. Un resultado antes negativo puede producir
su primer evento al volverse positivo. Los resultados negativos, no evaluados,
fallidos, de vista previa, benchmarks y experimentos aislados no emiten eventos.
Una corrección posterior no retracta una detección ya entregada.

Es un evento posterior al análisis, no una detección de keywords en vivo. Incluye
los valores tipados y explicaciones existentes del monitor. Una lista de keywords
detectadas solo está disponible si tu monitor realmente extrae esa lista.

Ejemplo sintético:

```json theme={null}
{
  "id": "019a0000-0000-7000-8000-000000000001",
  "type": "monitor.matched",
  "schemaVersion": 1,
  "occurredAt": "2026-10-14T15:30:00.000Z",
  "data": {
    "conversation": {
      "id": "019a0000-0000-7000-8000-000000000002",
      "externalId": "your-call-123",
      "endUserId": null,
      "startedAt": "2026-10-14T15:25:00.000Z",
      "url": "https://dashboard.zelto.ai/org/example/conversations/019a0000-0000-7000-8000-000000000002"
    },
    "agent": {
      "id": "019a0000-0000-7000-8000-000000000003",
      "name": "Collections agent"
    },
    "monitor": {
      "id": "019a0000-0000-7000-8000-000000000004",
      "name": "Complaint",
      "version": 3
    },
    "result": {
      "id": "019a0000-0000-7000-8000-000000000005",
      "affected": true,
      "overallScore": 100,
      "evaluatedAt": "2026-10-14T15:30:00.000Z",
      "items": [
        {
          "stageId": "complaint",
          "label": "Complaint",
          "kind": "boolean",
          "value": true,
          "reasoning": "The caller requests to file a complaint."
        }
      ]
    }
  }
}
```

Usa `data.conversation.externalId`, el ID de llamada que enviaste originalmente,
para unir el evento con tus registros. `endUserId` es un ID opaco opcional de la
persona que puedes enviar al [cargar llamadas](/es/docs/integrations/api-call-upload).
Cualquiera puede ser null si no se proporcionó. `company_id` identifica una empresa
o acreedor y no debe usarse como ID de la persona llamada.

`occurredAt` indica cuándo se registró el evento; `evaluatedAt` identifica el
resultado capturado. `startedAt` puede ser null. `overallScore` es un número o
null; `items[].value` conserva su tipo JSON original. El snapshot no cambia en
los reintentos. No se incluyen transcripciones completas, grabaciones, teléfonos,
prompts ni metadatos libres de la llamada. Los valores del monitor pueden contener
información del cliente: guárdalos con los mismos controles que sus resultados.

Los monitores que solo tienen filtros pueden devolver `items` vacío: pasar sus
filtros es el resultado positivo. No se realiza una extracción adicional de keywords.

### `webhook.test`

Las pruebas no contienen datos reales de llamadas o clientes:

```json theme={null}
{ "id": "019a0000-0000-7000-8000-000000000099", "type": "webhook.test", "schemaVersion": 1, "occurredAt": "2026-10-14T15:30:00.000Z", "data": { "test": true } }
```

Verifica la firma y devuelve 2xx para este tipo. **No** insertes una entrada en el
historial del cliente. Las pruebas se solicitan explícitamente desde la pantalla
del destino, aparecen con su tipo de evento en el historial y no reservan una
coincidencia del monitor.

## Verificar la firma

Cada petición contiene:

| Encabezado | Significado |
| - | - |
| `X-Zelto-Event-Id` | ID estable del evento; úsalo para evitar entradas duplicadas en el historial. |
| `Idempotency-Key` | El mismo ID de evento. |
| `X-Zelto-Delivery-Id` | ID de entrega para este destino. |
| `X-Zelto-Timestamp` | Timestamp Unix en segundos de este intento. |
| `X-Zelto-Signature` | `v1=` seguido del digest hexadecimal HMAC-SHA256. |

El mensaje firmado es `timestamp + "." + rawBody`. Verifica el **cuerpo original
sin procesar**, antes de parsear JSON o cambiar espacios. El timestamp y la firma
cambian en cada intento; el ID del evento se mantiene. El cuerpo también se mantiene
para `monitor.matched` y `webhook.test`; los enlaces de descarga de exportaciones se
renuevan en cada intento. Este verificador Node.js rechaza firmas de más de cinco
minutos de antigüedad:

```javascript theme={null}
import { createHmac, timingSafeEqual } from "node:crypto";

export function verifyWebhook(rawBody, timestamp, signature, secret, now = Date.now()) {
  if (!/^\d+$/.test(timestamp ?? "")) return false;
  const age = Math.abs(now / 1000 - Number(timestamp));
  if (!Number.isFinite(age) || age > 300) return false;
  if (!/^v1=[a-f0-9]{64}$/.test(signature ?? "")) return false;
  const expected = createHmac("sha256", secret)
    .update(`${timestamp}.${rawBody}`)
    .digest();
  return timingSafeEqual(expected, Buffer.from(signature.slice(3), "hex"));
}
```

Lee los encabezados desde tu framework HTTP y pasa el cuerpo UTF-8 original;
parsea JSON después de verificarlo. Rechaza verificaciones fallidas con una
respuesta que no sea 2xx. Mantén el secreto de firma del lado del servidor.

## Guardar una vez, confirmar y reintentar

Después de verificar, distingue por `type`. Para `monitor.matched`, identifica a
la persona usando el ID externo de la llamada o el `endUserId` proporcionado y
guarda el ID del evento y la entrada del historial en una misma transacción.
Impón una restricción única sobre el ID del evento. Devuelve 2xx solo después de
guardar de forma durable, o de poner el trabajo en una cola durable de tu sistema.
Si el ID ya fue procesado, devuelve 2xx sin insertar otra entrada.

La entrega es **al menos una vez**, sin garantía de orden. Una respuesta puede
perderse después del commit: los reintentos automáticos y manuales pueden repetir
un evento. No dedupliques por nombre de monitor ni supongas que solo un monitor
puede dar positivo en una llamada.

Zelto permite hasta seis intentos automáticos. Las esperas base entre intentos
son 5, 10, 20, 40 y 80 minutos, más hasta 30 segundos aleatorios. Se reintentan
respuestas distintas de 2xx, errores de red y tiempos de espera agotados; un
`Retry-After` válido en 429 o 503 pospone el siguiente intento si indica una fecha
posterior. Los destinos inválidos o privados fallan sin intentos repetidos.
DNS tiene un tiempo de espera de 5 segundos y HTTP de 15 segundos. No se siguen
redirecciones. El programador puede agregar una demora breve al intento vencido.
Las entregas se ejecutan separadas del análisis de llamadas.

El **Historial de entregas** muestra las últimas 50 entregas, sus estados,
intentos, resúmenes acotados de errores y hasta 100 resultados recientes de
intentos. Las detecciones muestran el monitor y un enlace a la llamada mientras
se conserve su contenido, con su referencia externa cuando exista. La búsqueda
y los filtros de estado se aplican a estas últimas 50 entregas. El ID del evento
y el registro de intentos están en **Detalles técnicos e intentos**. Las entregas
pendientes se actualizan cada 15 segundos mientras la página está visible; las
actualizaciones se pausan al editar o consultar un secreto de una sola
visualización. Puedes usar **Actualizar** para consultar manualmente.
Usa **Reenviar** para una entrega
terminal mientras se conserve su contenido y el destino esté habilitado (las
pruebas también pueden reenviarse con el destino pausado). Se usa la configuración
actual, el mismo ID y manifiesto (con un enlace de descarga renovado para las
exportaciones), y un nuevo presupuesto de intentos automáticos.
Una entrega activa o con un lease vigente no se puede reiniciar; espera su
finalización o el vencimiento del lease. No hay replay masivo de llamadas históricas.

El contenido se conserva por un máximo de 30 días desde el ingreso de la llamada,
o menos según la política efectiva de retención de la organización. Borrar la
llamada, el monitor o la organización elimina el contenido relacionado. Un
contenido vencido no se puede reenviar. Se conserva un identificador mínimo del
evento mientras existan la llamada y el monitor para no emitir la misma primera
coincidencia después de limpiar el contenido.

## Otras funciones de webhooks

Estos webhooks por llamada son distintos del
[webhook semanal de reportes de monitores](/es/docs/reports), cuyo payload
`monitor_report.weekly` y deduplicación por ID de entrega no cambian. Los webhooks
de [integraciones de voz](/es/docs/integrations/index) envían llamadas **a** Zelto;
esta integración envía detecciones **desde** Zelto a tu sistema.

## Exportaciones de monitores completadas

Los propietarios y administradores pueden habilitar **Recibir exportaciones de
monitores completadas** para un destino. Esto autoriza archivos completos de los
monitores que seleccione un miembro de la organización; las suscripciones a
monitores no restringen el contenido de las exportaciones. Los destinos existentes
mantienen esta opción desactivada. En **Monitores → Exportaciones → Crear
exportación**, elige un receptor activo y aprobado en **Notificar al completar**,
o conserva **Sin notificación**. Un destino puede recibir solo exportaciones,
sin suscribirse a monitores individuales.

Zelto registra un evento `monitor_export.completed` por exportación y lo envía
solo cuando el archivo privado está completo. Usa los mismos encabezados de firma:

```json theme={null}
{
  "id": "019a0000-0000-7000-8000-000000000011",
  "type": "monitor_export.completed",
  "schemaVersion": 1,
  "occurredAt": "2026-10-15T12:00:00.000Z",
  "data": {
    "exportId": "019a0000-0000-7000-8000-000000000012",
    "format": "csv",
    "rowCount": 150,
    "bytes": 18000,
    "sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
    "completedAt": "2026-10-15T12:00:00.000Z",
    "fileExpiresAt": "2026-11-14T12:00:00.000Z",
    "downloadUrl": "https://private-storage.example/signed-download",
    "downloadExpiresAt": "2026-10-15T13:00:00.000Z"
  }
}
```

Verifica HMAC sobre los bytes originales recibidos y luego descarga `downloadUrl`
con GET; no requiere iniciar sesión en Zelto ni una API key. Trata el enlace como
una credencial temporal y nunca lo registres en logs. Comprueba el tamaño y el
SHA-256 del archivo y evita procesarlo más de una vez usando el ID del evento.
El enlace puede usarse varias veces antes de vencer. Devuelve 2xx para confirmar
el evento; esto no confirma que el archivo se haya descargado o procesado.

Los enlaces duran **hasta una hora**, con un límite adicional según la vigencia
del archivo, la retención de webhooks y las credenciales de firma. Usa siempre
`downloadExpiresAt`. Cada reintento o reenvío manual renueva **solo `downloadUrl`
y `downloadExpiresAt`**, conservando el ID del evento y el manifiesto. Esta es una
excepción explícita al cuerpo inmutable de los otros tipos de evento. No rechaces
un reintento válido porque cambió su enlace de descarga.

Una notificación fallida no hace fallar la exportación completada. Se aplican los
seis intentos, las esperas, el historial y el reenvío manual descritos arriba.
Los administradores pueden reenviar mientras sigan vigentes la autorización
original del destino, la membresía del creador, el evento y el archivo. Los
archivos permanecen disponibles durante 30 días, pero la retención de webhooks
puede impedir reenvíos antes. Un archivo vencido requiere una nueva exportación
con datos actuales.

Pausar el destino, cambiar su URL o secreto, quitar la aprobación de exportaciones
o perder la membresía del creador impide emitir enlaces nuevos; reactivar el
destino no restaura una autorización revocada. Las peticiones HTTP ya autorizadas
pueden llegar y los enlaces ya emitidos vencen naturalmente. Tras una revocación,
crea una nueva exportación y selecciona explícitamente un receptor aprobado.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.