> ## 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.

# LiveKit

> Stream LiveKit Agents sessions into Zelto by forwarding each finished session from your worker.

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`](/docs/integrations/api-call-upload) 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](/api-reference/agents/list-agents) and
   [MCP server](/docs/mcp).

## 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:

```python theme={null}
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](/docs/integrations/api-call-upload), so see that
page (or the [REST API reference](/api-reference/agents/list-agents)) 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](/docs/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](/docs/guides/connect-a-voice-provider#troubleshoot-a-missing-call).

## 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](https://docs.livekit.io/home/egress/overview/), 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](/api-reference/agents/list-agents) 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.

## Related

* [Connect a voice provider](/docs/guides/connect-a-voice-provider) — pick a path and verify ingestion.
* [Custom & other providers](/docs/integrations/api-call-upload) — the canonical call shape this endpoint accepts.
* [Conversations](/docs/conversations) — where LiveKit calls land.
* [API reference](/api-reference/agents/list-agents) · [MCP](/docs/mcp) — read your data programmatically.
