Skip to main content
LiveKit runs your voice agents as workers you host — there is no cloud API for Zelto to pull calls from, and LiveKit’s room webhooks don’t carry a transcript. So the LiveKit integration is push-based: your agent worker forwards each finished session to Zelto with an API key. It uses the same provider-agnostic /webhooks/calls endpoint as every other source, tagged with X-Zelto-Provider: livekit so the calls show up as LiveKit.

Mint a key

  1. Open Settings → Integrations → LiveKit and click Create API key.
  2. Give it a descriptive name (e.g. livekit-prod-worker).
  3. Copy the key once — Zelto only stores a hash. It is the same org-wide key used by the REST API and MCP server.

Forward a session

Set the key as ZELTO_API_KEY in your worker’s environment, then install the official Python helper. It exports the finalized transcript and tool calls from LiveKit’s on_session_end callback; when LiveKit has a local recording path it uploads the audio directly to Zelto too.
agent_external_id is your stable identifier for this deployed agent, not a per-room name. The SDK sends session data to Zelto’s dedicated AWS ingest endpoint (https://ingest.zelto.ai), retries transient failures, and exports the transcript even if recording upload is unavailable. For a self-hosted or regional endpoint, set ZELTO_INGEST_URL (or pass ingest_url=); the old ZELTO_API_URL / api_url= names remain compatibility aliases. The manual payload mapping remains available below for LiveKit Agents versions before 1.0 or custom workflows:
The body is Zelto’s canonical call shape — the same one documented under Custom & other providers, so see that page (or the REST API reference) for the full field list. Roles must be user, assistant, system, or tool. Everything except call.externalId and an agent reference is optional; you can also send durationSeconds, recordingUrl, endedReason, cost, and a customer block. A successful upload returns { "received": true }.

Forward tool calls

If your LiveKit agent invokes tools during a session, forward each one so it appears inline in the transcript, feeds Zelto’s AI analysis, and lands in the queryable tool-call traces. LiveKit records tools in session.history.items as function_call items (the invocation, with name / arguments / call_id) and function_call_output items (the result, keyed by the same call_id). Map each function_call to a tool turn carrying a structured toolCall, and pair it with its output by call_id:
Only toolCall.name is required; arguments and result accept any JSON (a JSON string is parsed for you), and status is success, error, or pending. The turn’s content can be empty when a toolCall is present — Zelto labels the turn with the tool name. Send build_turns(session) as transcript.turns in the POST above.

Verify the first call

Forward a session, then open Conversations — it shows up tagged as LiveKit within a few seconds. If it doesn’t, check the response your worker got from the POST — a { "received": true } body means Zelto accepted it, a 4xx returns the reason — and confirm the worker is sending the Authorization and X-Zelto-Provider: livekit headers. See Connect a voice provider.

Stable ids

  • agent.externalId identifies the agent. Zelto finds or creates a LiveKit agent for each distinct value, so use a stable id per agent (not per session). The first session’s agent.name names it.
  • call.externalId (the room or session id) is the dedup key. Re-posting the same id updates the call in place — refreshing the transcript, recording, or duration — and never double-charges. Send the session once when it ends, then re-send later if you enrich it.

Recordings vs. transcripts

This flow forwards the transcript your agent already has. If you also run egress, include the recording as call.recordingUrl and Zelto re-hosts the audio. If you only have audio and no transcript, upload the file through the REST API instead and Zelto transcribes it.

Connection status

The LiveKit card shows Active once Zelto has ingested at least one LiveKit call. Each agent’s detail page shows when its last session arrived, so you can tell at a glance whether the worker is still forwarding.

Stream traces (OpenTelemetry)

The forwarding above feeds Conversations — the transcript-and-analysis view of a call. Separately, a LiveKit agent can stream OpenTelemetry traces into Traces: the per-call span tree (LLM, TTS, STT, tool, and your own spans) with latency, tokens, and cost. LiveKit auto-instruments each session — you only register an OTLP exporter pointed at Zelto’s ingest host. Create a write-only observability ingest key in Traces → Connect agent (needs the owner or admin role), then register the exporter at the start of your entrypoint, before session.start() — it must actually run, not just be defined:
OTLPSpanExporter() reads the standard OpenTelemetry environment variables, and the SDK appends /v1/traces to the endpoint for you:
Install the HTTP exporter: pip install opentelemetry-exporter-otlp-proto-http. The Python SDK sends http/protobuf and doesn’t implement http/json, so keep the protocol at http/protobuf (Zelto accepts both encodings anyway). gRPC (:4317) is not accepted. This is separate from the transcript forwarding above: traces use the write-only ingest key — not your ZELTO_API_KEY — and need no X-Zelto-Provider header. See Traces for what a trace holds and how to read it.