Skip to main content
LiveKit ejecuta tus agentes de voz como workers alojados por ti. No existe una API en la nube desde la que Zelto pueda sincronizar las llamadas y los webhooks de las salas de LiveKit no incluyen la transcripción. Por eso, esta integración funciona mediante envío: el worker del agente envía a Zelto cada sesión finalizada con una clave de API. Utiliza el mismo endpoint independiente del proveedor, /webhooks/calls, que las demás fuentes, con la cabecera X-Zelto-Provider: livekit para identificar las llamadas como LiveKit.

Crear una clave

  1. Abre Configuración → Integraciones → LiveKit y haz clic en Crear clave de API.
  2. Asigna un nombre descriptivo, por ejemplo livekit-prod-worker.
  3. Copia la clave una sola vez. Zelto solo guarda su hash. Es la misma clave para toda la organización que utilizan la API REST y el servidor MCP.

Enviar una sesión

Guarda la clave como ZELTO_API_KEY en el entorno del worker. Al finalizar cada sesión, envía su transcripción a https://ingest.zelto.ai/webhooks/calls con el siguiente ejemplo. Instala aiohttp en el entorno del worker si aún no está disponible. Usa un identificador estable para el agente y un identificador único de sala o sesión para cada llamada. Reutiliza el identificador de llamada al reintentar una carga.
El cuerpo utiliza el formato canónico de llamada de Zelto, documentado en Proveedores personalizados y otros. Consulta esa página o la referencia de la API REST para ver todos los campos. Los roles válidos son user, assistant, system y tool. Todo es opcional salvo call.externalId y una referencia al agente. También puedes enviar durationSeconds, recordingUrl, endedReason, cost y un bloque customer. Una carga correcta devuelve { "received": true }.

Declarar la versión que atendió la sesión

Llama a forward_to_zelto con la etiqueta y el prompt real capturados para esa sesión. Por ejemplo, conserva agent_id="support-agent" para ambos despliegues y usa agent_version="support-2026-09-a" o "support-2026-09-b". El parámetro de Python se envía como version en el nivel superior de la carga. Cambia la etiqueta al modificar el prompt o el pipeline, aunque el prompt sea idéntico. Conserva la etiqueta original al reintentar o enviar después de otro despliegue. No uses un ID de sala como versión ni cambies el ID del agente con cada despliegue. La primera llamada procesada crea la versión declarada. Cuando las llamadas de ambas versiones estén visibles, podrás seleccionarlas para un experimento del agente. La etiqueta no captura el pipeline completo. Consulta Versiones de agentes para conocer las capturas, los límites de la alternativa automática y los pasos de verificación. Pasa version_config para incluir el contexto completo como versionConfig: LLM, STT, modelo/voz TTS, herramientas, flujos o ajustes propios. Debe ser un objeto JSON de hasta 64 KiB. También puedes registrarlo antes en Agentes → Versiones → Registrar versión o mediante POST /v1/agents/{id}/versions. Consulta el contrato de configuración.

Enviar invocaciones de herramientas

Si el agente invoca herramientas durante una sesión, envía cada llamada para que aparezca en la transcripción, se incluya en el análisis de IA y se registre como una traza de invocación de herramienta consultable. LiveKit guarda las herramientas en session.history.items como elementos function_call —la invocación con name, arguments y call_id— y function_call_output —el resultado asociado al mismo call_id—. Convierte cada function_call en un turno tool con un objeto toolCall y asócialo con su resultado mediante call_id:
Solo toolCall.name es obligatorio. arguments y result aceptan cualquier valor JSON; si reciben una cadena JSON, Zelto la analiza. status puede ser success, error o pending. El content del turno puede quedar vacío cuando existe toolCall; Zelto utiliza el nombre de la herramienta como etiqueta. Envía build_turns(session) como transcript.turns en la solicitud anterior.

Verificar la primera llamada

Envía una sesión y abre Conversaciones. Debería aparecer identificada como LiveKit en pocos segundos. Si no aparece, revisa la respuesta que recibió el worker: { "received": true } indica que Zelto aceptó la carga y una respuesta 4xx incluye el motivo del rechazo. Comprueba también que se envían las cabeceras Authorization y X-Zelto-Provider: livekit. Consulta Conectar un proveedor de voz.

Identificadores estables

  • agent.externalId identifica al agente. Zelto busca o crea un agente de LiveKit para cada valor distinto, así que utiliza un identificador estable por agente, no por sesión. El agent.name de la primera sesión le asigna el nombre.
  • call.externalId —el identificador de la sala o sesión— elimina duplicados. Si vuelves a enviar el mismo valor, Zelto actualiza la llamada existente con la transcripción o duración nuevas, o añade una grabación que faltaba, sin cobrar otra vez la llamada. Conserva también la misma referencia del agente. Envía la sesión cuando finalice y reenvía el cuerpo original completo si añades información.

Grabaciones y transcripciones

Este flujo envía la transcripción que el agente ya tiene. Adjunta una grabación agregando call.recordingUrl al mismo cuerpo de la solicitud. El audio puede proceder de egress o de tu propio servicio de grabación, incluido un bucket de S3.

Grabación disponible al finalizar la llamada

Incluye la URL en tu solicitud habitual a https://ingest.zelto.ai/webhooks/calls, con los encabezados existentes Authorization: Bearer $ZELTO_API_KEY, X-Zelto-Provider: livekit y Content-Type: application/json. Por ejemplo, el objeto call dentro del cuerpo completo de una sesión podría ser:
Conserva la referencia del agente, la transcripción, los metadatos y el contexto de la versión en el resto del cuerpo. Utiliza el identificador de llamada que ya envías a Zelto; el nombre del archivo no es un nuevo identificador de llamada.

Grabación disponible más tarde

Envía la sesión como siempre al finalizar. Cuando termine la carga de la grabación, copia el cuerpo original completo, agrega la URL y vuelve a enviarlo al mismo endpoint con los mismos encabezados:
Conserva la referencia original del agente, call.externalId, la transcripción, los metadatos y el contexto de la versión (version, systemPrompt y versionConfig, si los enviaste). Mantén también los detalles originales de la llamada: cliente, costo, duración y motivo de finalización. Un reenvío parcial con solo la grabación puede borrar campos omitidos, como el costo o el motivo de finalización. Guarda el cuerpo original si la notificación de grabación se procesa en otro trabajo o proceso. El reenvío adjunta la grabación a la misma conversación sin cobrar otra vez la llamada. Esto añade una grabación que faltaba o reemplaza una URL del proveedor que Zelto aún no ha capturado. Para recuperar una URL vencida o inaccesible, reenvía el cuerpo original completo con una URL nueva que permita descargar el audio. Reintentar después de que Zelto haya capturado la grabación conserva la copia almacenada. Una URL sin firma de un objeto privado de S3 no es suficiente: envía una URL GET prefirmada. Las respuestas de acceso denegado o recurso no encontrado reciben reintentos limitados; una URL cuya caducidad está confirmada requiere una URL nueva. Zelto no puede renovar las URL firmadas por tu servicio de almacenamiento.

Reproducción y captura

Los usuarios pueden reproducir, pausar y desplazarse por la grabación en el reproductor de audio de la conversación. Envía startSeconds / endSeconds de la transcripción relativos al inicio de la grabación para sincronizar la reproducción; adjuntar audio no genera ni reemplaza las marcas de tiempo. Zelto debe poder descargar la URL sin autenticación interactiva. También puedes usar una URL prefirmada: envíala después de completar la carga y mantenla válida hasta que termine la captura. Para archivos WAV, utiliza preferentemente un tipo de contenido de audio como audio/wav. Las URL WAV servidas como application/octet-stream también se capturan sin modificar los bytes del audio. Zelto pone la captura en cola de forma asíncrona y vuelve a alojar la grabación cuando está habilitado el almacenamiento de grabaciones de tu organización. Si tu organización lo ha desactivado, la reproducción usa tu URL, que debe seguir accesible y admitir rangos de bytes para desplazarse por el audio. Esta integración no cambia esa configuración. Una respuesta 200 con { "received": true } confirma la aceptación, no que la captura haya terminado. Antes de enviar grabaciones de todas las llamadas, verifica una en Conversaciones: debe seguir siendo una sola conversación, conservar su transcripción y sus detalles, y permitir reproducir y desplazarse por el audio. Si el almacenamiento está habilitado, confirma con Zelto que la captura terminó antes de dejar que venza la URL de origen. Si solo tienes audio y no una transcripción, utiliza el flujo de carga de archivos de la API REST; adjuntar call.recordingUrl aquí no solicita una transcripción.

Estado de la conexión

La tarjeta de LiveKit muestra Activo cuando Zelto ha ingerido al menos una llamada de LiveKit. La página de detalles de cada agente indica cuándo llegó la última sesión, para que puedas comprobar si el worker sigue enviando datos.

Transmitir trazas (OpenTelemetry)

El flujo anterior alimenta Conversaciones, la vista de transcripción y análisis de una llamada. Además, un agente de LiveKit puede transmitir trazas de OpenTelemetry a Trazas: el árbol de spans de cada llamada —LLM, TTS, STT, herramientas y spans propios— con latencia, tokens y costo. LiveKit instrumenta cada sesión automáticamente; solo debes registrar un exportador OTLP que apunte al endpoint de ingestión de Zelto. Crea una clave de ingestión de observabilidad de solo escritura en Configuración → Integraciones → Observabilidad o en Trazas → Conectar agente. Necesitas el rol owner o admin. Registra el exportador al principio del entrypoint, antes de session.start(). El código debe ejecutarse, no basta con definirlo:
OTLPSpanExporter() utiliza las variables de entorno estándar de OpenTelemetry y el SDK añade /v1/traces al endpoint:
Instala el exportador HTTP con pip install opentelemetry-exporter-otlp-proto-http. Este exportador HTTP de Python envía http/protobuf: mantén ese protocolo. Zelto también acepta JSON OTLP de exportadores compatibles. No se admite gRPC en :4317. Este flujo es independiente del envío de transcripciones: las trazas utilizan la clave de ingestión de solo escritura, no ZELTO_API_KEY, y no necesitan la cabecera X-Zelto-Provider. Consulta Trazas para entender qué contiene una traza y cómo leerla. Usa ZELTO_AGENT_EXTERNAL_ID con el mismo valor estable que agent.externalId en forward_to_zelto, y ctx.room.name como call.externalId. El ID externo crea o reutiliza el agente de LiveKit aunque la traza llegue antes que la llamada. La metadata añade la referencia de sesión a los spans de LiveKit. En tus propios spans, envía también session.id. Si una sesión contiene varios agentes, pon el ID externo en cada span en lugar de fijar uno compartido en el recurso. Consulta Enviar trazas para probar la entrega, configurar logs y diagnosticar errores, y la documentación de LiveKit para su integración con OpenTelemetry.

Contenido relacionado