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 POST each finished session at end of call — for example from your entrypoint’s shutdown callback:
import os, aiohttp
from livekit.agents import AgentSession

async def forward_to_zelto(session: AgentSession, room_name: str, agent_id: str):
    turns = [
        {"role": item.role, "content": item.text_content}
        for item in session.history.items
        if item.text_content
    ]
    async with aiohttp.ClientSession() as http:
        await http.post(
            "https://api.zelto.ai/webhooks/calls",
            headers={
                "Authorization": f"Bearer {os.environ['ZELTO_API_KEY']}",
                "X-Zelto-Provider": "livekit",
            },
            json={
                "agent": {"externalId": agent_id},
                "call": {"externalId": room_name},
                "transcript": {"turns": turns},
            },
        )
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 }.

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.