LiveKit

Connect your own voice bot — built with Pipecat, the LiveKit Agents framework, or anything that can join a LiveKit room — and let RubricHQ’s simulated caller talk to it over real audio.

Both parties join one LiveKit room:

  • rubric-test-caller — RubricHQ’s AI caller, running your scenario.
  • your agent-under-test — the bot you’re testing.

They publish and subscribe to each other’s audio; RubricHQ records the call and produces transcript + audio metrics.

Prefer Daily as your transport? The model is identical — see Daily.


The model: rooms, tokens, and who creates them

A few LiveKit facts that decide how the integration is wired:

  • A room is one call/session. A token is a JWT that authorizes one identity to join one room.
  • Minting a token requires the LiveKit project’s API key + secret (the token is signed with the secret; its iss is the API key). Joining a room requires only the room URL + a token — never the API key.
  • So the only real question is who creates the room and mints the tokens. That gives two modes:
ModeRoom managementWho creates the room + mints tokensWhose LiveKit credsWhat you implement
You create roomsclientYouYours (stay on your side)An endpoint that creates a room and returns a join token for our caller
We create roomsrubricRubricHQYours, stored in RubricHQAn endpoint that joins a room we created (or a registered LiveKit agent we dispatch)

Configure this under Agent → Channels → Web, provider LiveKit.

Credentials are only needed for “We create rooms” mode — because that’s where we mint the tokens. In “You create rooms” mode we never see your API key; you hand us a scoped token.


Credentials (for “We create rooms”)

Provide your LiveKit project credentials in either place — RubricHQ resolves per-agent override → workspace default → error:

  1. Workspace defaultSettings → Integrations → Transport Providers → LiveKit: URL, API Key, API Secret. Used by every agent that doesn’t override.
  2. Per-agent overrideAgent → Channels → Web → “We create rooms” → LiveKit credentials. Overrides the workspace default for that one agent.

The API Secret is write-only: once saved it shows “Configuration saved” and is never sent back to the browser. Leave it untouched to keep it; click Edit to replace it.


Mode 1 — You create rooms (client)

Recommended for most custom/Pipecat bots. You own the LiveKit project; RubricHQ never needs your keys.

Config

FieldValue
Room managementYou create rooms (client)
Bot Runner URLYour endpoint, e.g. https://your-bot.example.com/api/create-and-join

Sequence

  1. RubricHQ POSTs to your Bot Runner URL with the run context.
  2. Your service creates a LiveKit room, starts your bot in it, and returns a join token for our caller.
  3. RubricHQ’s caller joins that room with the token you returned, and the conversation runs.

What we send → your Bot Runner URL

1POST {bot_runner_url}
2Content-Type: application/json
3
4{
5 "rubric_run_id": "1234",
6 "metadata": {
7 "scenario_id": 8,
8 "scenario_name": "Cooperative Upbeat Payment Flow",
9 "agent_id": 1
10 }
11}

What you return (immediately, in the HTTP response)

1{
2 "room_name": "client-33791bbd",
3 "room_url": "wss://your-project.livekit.cloud",
4 "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
5}

The token is a join token you mint for RubricHQ’s caller. Decoded, it looks like:

1{
2 "name": "rubric-test-caller",
3 "video": { "roomJoin": true, "room": "client-33791bbd", "canPublish": true, "canSubscribe": true },
4 "iss": "<your-livekit-api-key>"
5}

The room + token come back in the HTTP response — not via a webhook. By the time you respond 200, the room must exist and the token must be valid, because RubricHQ’s caller joins immediately.

Reference bot runner (client mode)

1# POST /api/create-and-join
2import secrets
3from livekit.api import LiveKitAPI, CreateRoomRequest, AccessToken, VideoGrants
4
5async def create_and_join(req):
6 room = f"client-{secrets.token_hex(4)}"
7 async with LiveKitAPI(url=LK_URL, api_key=LK_KEY, api_secret=LK_SECRET) as lk:
8 await lk.room.create_room(CreateRoomRequest(name=room, empty_timeout=300))
9
10 # mint a token for OUR caller (you sign it with YOUR key/secret)
11 caller_token = (AccessToken(LK_KEY, LK_SECRET)
12 .with_identity("rubric-test-caller")
13 .with_grants(VideoGrants(room_join=True, room=room, can_publish=True, can_subscribe=True))
14 .to_jwt())
15
16 start_your_bot_in(room) # join the room with your own bot token
17 return {"room_name": room, "room_url": LK_URL, "token": caller_token}

Mode 2 — We create rooms (rubric)

RubricHQ creates the room on your LiveKit project (using the credentials above), mints both tokens, then gets your bot into that room. How we reach your bot depends on the agent type:

Agent typeHow your bot joinsYou implement
PipecatWe POST your bot’s join endpoint with room credentialsPOST /api/session that joins the given room
CustomWe POST your Bot Runner URL with room credentialsa join endpoint (same shape)
LiveKit (registered Agents worker)We issue an explicit dispatch by agent_namenothing — your worker auto-receives the job

Config

FieldValue
Room managementWe create rooms (rubric)
Web Call URL / Bot Runner URLYour join endpoint, e.g. https://your-bot.example.com (we call …/api/session)
LiveKit credentialsURL / API Key / API Secret (per-agent or workspace)
Agent Name(LiveKit worker only) the name to dispatch; leave blank for automatic dispatch

Sequence (Pipecat / Custom)

  1. RubricHQ creates a room on your project and mints a bot_token (for your bot) and a test_caller_token (for our caller).
  2. RubricHQ POSTs your join endpoint with the room name, the bot_token, and the room URL.
  3. Your bot joins that room with the token we minted; our caller joins too; the conversation runs.

What we send → your join endpoint

1POST {web_call_url}/api/session
2Content-Type: application/json
3
4{
5 "room_name": "rubric-179-ab12cd34",
6 "bot_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
7 "livekit_url": "wss://your-project.livekit.cloud"
8}

What you return

1{ "status": "started", "room_name": "rubric-179-ab12cd34" }

The bot_token we minted for your bot, decoded:

1{
2 "name": "pipecat-bot",
3 "video": { "roomJoin": true, "room": "rubric-179-ab12cd34", "canPublish": true, "canSubscribe": true },
4 "iss": "<your-livekit-api-key>"
5}

Your bot doesn’t need its own credentials here — it joins our room with the bot_token we minted (signed by your project’s key, so it’s valid on your project). It only needs the room_name, bot_token, and livekit_url from the request.

Reference bot runner (rubric mode — Pipecat)

1# POST /api/session — join a room RubricHQ already created
2async def create_session(req):
3 start_your_bot(
4 room_name=req["room_name"],
5 token=req["bot_token"], # minted by RubricHQ with YOUR project's key
6 url=req["livekit_url"],
7 )
8 return {"status": "started", "room_name": req["room_name"]}

LiveKit explicit dispatch (registered Agents workers)

If your bot is a registered LiveKit Agents worker, you don’t need a join endpoint. Set Agent Name to your worker’s name and RubricHQ issues a dispatch into the room it created:

1await lk.agent_dispatch.create_dispatch(
2 CreateAgentDispatchRequest(agent_name="my-voice-agent", room="rubric-179-ab12cd34",
3 metadata='{"rubric_run_id": 179}')
4)

Leave Agent Name blank to rely on your worker’s automatic dispatch (it joins every new room on the project).

The dispatch only pulls in an agent if a worker registered under that agent_name is online and connected to the project. RubricHQ always issues the dispatch correctly (you can confirm it via lk.agent_dispatch.list_dispatch(room_name=…)), but if no matching worker is running, only our caller is in the room and the run will have no agent to talk to.


Optional room config — “LiveKit Config (JSON)”

When RubricHQ creates the room (“We create rooms”), you can pass extra room-creation options as JSON under Agent → Channels → Web → LiveKit Config (JSON). They’re optional and forwarded to LiveKit’s CreateRoom call; RubricHQ’s required keys (the room name) always take precedence.

1{
2 "empty_timeout": 600,
3 "departure_timeout": 120,
4 "max_participants": 5,
5 "metadata": { "team": "qa", "suite": "billing" },
6 "min_playout_delay": 0,
7 "max_playout_delay": 0,
8 "sync_streams": false,
9 "room_preset": "my-preset"
10}
KeyTypeDefaultDescription
empty_timeoutint (sec)300How long the room stays open with no participants (before the first joins / after a brief empty period) before LiveKit closes it.
departure_timeoutint (sec)LiveKit default (~20s)How long the room stays open after the last participant leaves.
max_participantsint10Maximum simultaneous participants.
metadatastring | objectArbitrary room metadata. Objects are JSON-encoded to a string (LiveKit stores metadata as a string).
min_playout_delayint (ms)Minimum jitter-buffer playout delay. Raise to smooth choppy audio; keep low for latency.
max_playout_delayint (ms)Maximum jitter-buffer playout delay.
sync_streamsboolEnable A/V stream synchronization on playback.
room_presetstringName of a room preset configured on your LiveKit project; its settings are applied as the base.

Only the keys above are honored — any other keys are ignored. For the authoritative meaning of each, see the LiveKit CreateRoom reference.

This JSON only applies in “We create rooms” mode. In “You create rooms” mode you create the room, so you set these options yourself in your own CreateRoom call.


Choosing a mode

1

Do you want RubricHQ to hold your LiveKit credentials?

  • No → use You create rooms (client). Implement create-and-join. We never see your keys.
  • Yes → use We create rooms (rubric).
2

In “We create rooms”, how is your bot reached?

  • Pipecat / custom bot with an HTTP server → implement POST /api/session to join the given room.
  • Registered LiveKit Agents worker → set Agent Name (or leave blank for auto-dispatch); implement nothing.

Results

When the call ends, the transcript, audio recording (speaker-separated when available), and metrics appear automatically on the run in RubricHQ — your bot runner doesn’t need to report anything back. If a call can’t proceed (missing/incorrect credentials, room creation failed, or the bot never joins) the run is marked failed with the reason rather than left hanging.

A reference Pipecat bot runner that supports both modes only needs two endpoints: POST /api/create-and-join (client mode) and POST /api/session (rubric mode). Everything else — recording, transcription, metrics — is handled by RubricHQ.