please give me more information on setting up egre...
# ask-ai
m
please give me more information on setting up egress with livekit cloud and how I can connect it to my storage bucket etc.
t
Thanks for reaching out, Alexander. I'm an AI assistant and I'm here to help. Give me a moment while I research this topic.
Thanks — I’ll walk you through how to set up Egress on LiveKit Cloud and connect it to your storage bucket. Acknowledgement • You want to record/stream LiveKit sessions (egress) and save the output to your own cloud storage (S3-compatible, GCP, or Azure). Quick overview (what you can do) • Record full rooms (Room Composite), individual participants, single or multiple tracks, or record a webpage (Web Egress). • Output formats: file (MP4/WebM/Ogg), HLS (ts + m3u8), RTMP/SRT live streams, image snapshots, or WebSocket audio. • Supported storage targets: S3-compatible (AWS S3, MinIO, Cloudflare R2, Backblaze), Google Cloud Storage, Azure Blob Storage. What you must include • Your egress request must include storage credentials and the target bucket/container. • Your LiveKit access token must include the
roomRecord
permission when starting egress. How to connect each storage type • S3-compatible: ◦ Provide
access_key
,
secret
,
region
, and
bucket
. For non-AWS endpoints include
endpoint
. ◦ For some providers (MinIO, R2) set
force_path_style: true
. ◦ Example snippet:
Copy code
"s3": {
      "access_key": "your-access-key",
      "secret": "your-secret",
      "region": "us-west-2",
      "bucket": "your-bucket",
      "force_path_style": true
    }
• Google Cloud Storage: ◦ Provide the service account JSON in
credentials
(account needs Storage Object Creator) and
bucket
. ◦ Example:
Copy code
"gcp": {
      "credentials": "{...service account JSON...}",
      "bucket": "your-gcp-bucket"
    }
• Azure Blob Storage: ◦ Provide
account_name
,
account_key
, and
container_name
. ◦ Example:
Copy code
"azure": {
      "account_name": "your-account",
      "account_key": "your-key",
      "container_name": "your-container"
    }
How to start egress • Use one of: ◦ LiveKit CLI (example below) ◦ SDKs (Go, JS, Python, Ruby, Java) ◦ Direct HTTP/Twirp API calls • CLI example to start a room composite recording to S3: a. Create a JSON egress request with
type: "room-composite"
,
room_name
, and
file
output configured with your S3 object and credentials. b. Run:
Copy code
lk egress start --file request.json
(You can also call the Egress API directly with an authenticated request; ensure token includes
roomRecord
.) Key features and tips • AutoEgress: optionally start recordings automatically when rooms are created. • You can update streams (add/remove RTMP) during an active egress session. • Use HLS output for adaptive streaming; use MP4/WebM for single-file archives. • For raw audio processing, use WebSocket track egress (sends PCM frames). Links for implementation details • Egress overview and types: https://docs.livekit.io/home/egress/overview/ • Egress outputs (file, HLS, RTMP, WebSocket): https://docs.livekit.io/home/egress/outputs/ • Egress API reference: https://docs.livekit.io/home/egress/api/ • AutoEgress: https://docs.livekit.io/home/egress/autoegress/ Next step — what I need from you • Which egress type do you want (room composite, participant, track, or web)? • Which storage provider are you using (AWS S3 / S3-compatible, GCP, or Azure)? • Do you want a one-off manual start or AutoEgress (automatic recording when rooms start)? Tell me those three items and I’ll give a ready-to-run example request (CLI/JSON or SDK code) tailored to your setup. Sources: Recording and export | LiveKit docs | Track egress | LiveKit docs | Egress output types | LiveKit docs | Egress API | LiveKit docs | Auto egress | LiveKit docs
m
Can you give me a higher level idea of how egressing to a storage bucket works? Where do I define the egress parameters? On my livekit cloud server? What do I do in the console? Please answer these fundamental architecture questions before we go into the weeds.
t
Thanks — I’ll explain the high‑level architecture and where you define egress parameters before we dive into specifics. Quick summary • LiveKit Cloud runs the egress service for you — you do not install or manage servers to record/stream. • You define egress parameters in the egress request (API/CLI/SDK). That request tells LiveKit Cloud what to record, how to format it, and where to put the output (your storage bucket or streaming endpoint). • The LiveKit Console can be used to view and sometimes trigger egress or configure AutoEgress, but the detailed parameters come from the egress request you send. High‑level flow 1. You trigger egress (manual or automatic). ◦ Manual: you call the Egress API, use the CLI, or an SDK to start an egress job. ◦ Automatic: AutoEgress can be enabled to start egress when rooms are created. 1. The egress request includes: ◦ What to capture (room composite, participant, track, or web page). ◦ Output type/format (file, HLS, RTMP, WebSocket, snapshots). ◦ Destination credentials and location (S3/GCP/Azure details or RTMP URL). ◦ Optional rendering/layout and transcoding options. 1. LiveKit Cloud performs the egress. ◦ It renders/records the media, transcodes or segments if needed, and writes the output to the destination you specified. 1. Your storage receives the files/segments or your streaming endpoint receives the live stream. ◦ LiveKit does not store long‑term unless you write to your bucket; the bucket is your archive. Where you specify parameters • In the egress request payload (JSON) you send via: ◦ SDK call (Go/JS/Python/Java/Ruby) — convenient for programmatic control. ◦ LiveKit CLI — good for one-off jobs or testing. ◦ Direct API/Twirp request — for custom integrations. • The LiveKit Console: ◦ Shows status and logs of egress jobs. ◦ Can be used to enable AutoEgress and sometimes to start/stop egress depending on UI features. ◦ Does not replace the egress request — it’s mostly observability and simple controls. Permissions and credentials • Your egress call must be authenticated with a LiveKit token that has
roomRecord
permission. • The egress request must include valid storage credentials (S3/GCP/Azure) or a stream URL so LiveKit can write to your destination. Operational notes • No server configuration on LiveKit Cloud is required — just send the egress request and credentials. • For compliance/security, use least‑privilege credentials (e.g., bucket write permissions only). • Use AutoEgress if you want recordings to start automatically; otherwise start egress programmatically when needed. If you’d like, I can next: • Show a minimal example egress request for your storage provider, or • Explain how AutoEgress is enabled and what the Console shows when an egress job runs. Which would you prefer next?
m
i’m interested in setting up egress for all my sessions moving forward - whenever a livekit room is created, I want to egress it for storage and monitoring purposes. Thoughts on next steps?
t
Great — since you want every room recorded automatically, here’s a concise plan and the exact next steps to enable AutoEgress across your environment. What AutoEgress does (high level) • When a room is created, LiveKit starts an egress job automatically using the recording parameters you supply. • LiveKit Cloud runs the egress service for you; you only supply the egress settings and destination credentials. • Output is written directly to your storage bucket (or streamed to RTMP/SRT) — LiveKit doesn’t keep long‑term copies unless you write them to your bucket. Recommended approach (safe, reliable) 1. Define the AutoEgress policy in the CreateRoom call (include an
egress
object). This ensures recording starts as soon as the room exists. 2. Use
room
(composite) egress for full-session archive + monitoring, or
tracks
if you need per-participant raw files. 3. Provide storage credentials (S3/GCP/Azure) in the egress payload so LiveKit can upload recordings. 4. Ensure the token used to create rooms has both
roomCreate
and
roomRecord
permissions. 5. Test with a sample room, then roll out for production. Concrete next steps for you 1. Decide output type: single MP4 (composite) for archive and checks, or HLS if you need live monitoring during sessions. 2. Pick storage provider and prepare least-privilege credentials: ◦ S3: access_key, secret, bucket, region, (optional endpoint, force_path_style) ◦ GCP: service account JSON with Storage Object Creator ◦ Azure: account_name, account_key, container_name 1. Add
egress
to your CreateRoom request. Example pattern (composite → S3):
Copy code
{
  "name": "room-name",
  "egress": {
    "room": {
      "file": {
        "filepath": "recordings/{room_name}-{time}.mp4",
        "s3": {
          "access_key": "...",
          "secret": "...",
          "bucket": "my-bucket",
          "region": "us-west-2"
        }
      }
    }
  }
}
1. Ensure your CreateRoom JWT grants
roomCreate
and
roomRecord
. 2. Test: ◦ Create a room (or have a participant join). ◦ Verify egress job appears/activates in the Console or via
ListEgress
. ◦ Confirm the file lands in your bucket. 1. Iterate: tweak layout, filename patterns, retention in your storage, and monitoring alerts. Operational tips • If you need recordings to start immediately without waiting for CreateRoom to finish, you can start egress directly; LiveKit will handle room creation if needed. • Use AutoEgress for consistent, automated behavior; use separate manual egress calls only for ad‑hoc jobs. • Monitor egress status in the Console and via the Egress API for failures or retries. • Use least‑privilege credentials for uploads. Would you like: • A ready-to-run CreateRoom JSON tailored to your storage provider (S3/GCP/Azure), or • A short SDK example (Node/Go/Python) to create rooms with AutoEgress? Tell me which provider and which code language and I’ll generate the exact payload or snippet. Sources: Auto egress | LiveKit docs | Recording and export | LiveKit docs | Egress API | LiveKit docs | Self-hosting the Egress Service | LiveKit docs | CreateRoom() is Taking Too Long
m
Using a Python-based LiveKit cloud server - you’re saying when I instantiate the room I should pass in the egress parameters? You’re giving me some kind of JSON.
t
Yes — pass the egress parameters in the CreateRoom request (the JSON payload). When using Python, include an
egress
object on the CreateRoom call so the room starts recording automatically when created. Below is a minimal Python example using the LiveKit Python SDK style (construct the request payload JSON and call CreateRoom). Replace the auth and storage fields with your values.
Copy code
import json
from livekit import RoomServiceClient  # example import; use your SDK client
from google.auth import jwt as google_jwt  # only if needed for auth

# Example: composite room recording to S3
create_room_payload = {
    "name": "my-room",
    "egress": {
        "room": {
            "file": {
                "filepath": "recordings/{room_name}-{time}.mp4",
                "s3": {
                    "access_key": "YOUR_ACCESS_KEY",
                    "secret": "YOUR_SECRET",
                    "bucket": "your-bucket",
                    "region": "us-west-2",
                    "force_path_style": True  # set for non-AWS S3-compatible providers
                }
            }
        }
    }
}

# Create a client (instantiate with your LiveKit Cloud host and API key/secret or token)
client = RoomServiceClient("<https://cloud.livekit.io>", api_key="API_KEY", api_secret="API_SECRET")

# Call CreateRoom (method name may vary by SDK; pass the payload or mapped fields)
response = client.create_room(create_room_payload)  # or client.create_room(**create_room_payload)

print("CreateRoom response:", response)
Notes: • The
egress
object in the payload defines what to record (
room
,
tracks
,
web
) and the output destination (S3/GCP/Azure/RTMP). • Ensure the token or credentials used have
roomCreate
and
roomRecord
permissions. • For GCP, include service account JSON under
gcp.credentials
and `bucket`; for Azure, use
azure.account_name
,
account_key
, and
container_name
instead of
s3
. • You can also start AutoEgress by adding similar
egress
fields when your app programmatically creates rooms on participant join. Would you like a tailored snippet for GCP or Azure, or the exact call signature for the Python SDK you’re using?
m
Wait, i’m using automatic dispatch. Where am I actually creating a room? I have an iOS client that requests a token from my backend and auths in and joins the room. Where is my room being created where I can pass in the egress parameters
t
Good question — you’re correct that with automatic dispatch rooms are often created implicitly when the first client joins. Here are the practical ways to ensure egress is applied when you rely on iOS clients that obtain tokens and join directly. Short answer • If you want AutoEgress for every room, you must ensure the room is created (or egress started) server-side with the
egress
settings before or immediately when the first participant joins. Implicit room creation by the client does not include egress parameters unless you create the room yourself or start egress afterward. Options (pick one) 1) Pre-create the room from your backend (recommended) • Your backend calls CreateRoom (include the
egress
object) before issuing the join token to the iOS client. • The iOS client receives the token and joins the already-created room; AutoEgress starts automatically. • Where to do it: your backend service that issues tokens (same place that should call CreateRoom). 2) Start egress immediately after room creation (server-side reactive) • Detect room creation via your backend (e.g., webhook, room lifecycle event, or when you sign the first token) and call StartEgress or CreateRoom with
egress
. • This is useful if you prefer not to pre-create rooms but still want egress started programmatically. 3) Use Console AutoEgress (if available) • If LiveKit Console supports global AutoEgress configuration in your Cloud plan, enable it there so new rooms get the egress policy automatically. (Console is for observability and simple configs; programmatic control is still via CreateRoom/StartEgress.) Implementation notes • Tokens the client uses to join only carry permissions (e.g.,
roomRecord
) — they do not carry full egress configs. Egress parameters belong in the CreateRoom/StartEgress request payload. • Ensure the JWT used to call CreateRoom/StartEgress has
roomCreate
and
roomRecord
grants. • If you prefer minimal latency, your backend can call CreateRoom with egress right before issuing the token; LiveKit handles cross-region creation, but avoid blocking token issuance on long CreateRoom calls — instead create room asynchronously or start egress after the participant joins. Next step I can help with • Show a minimal backend flow (Python) that: 1) creates room with egress, 2) issues a join token to iOS, or • Provide a StartEgress example you can call after detecting a room was auto-created. Which would you like?
m
I see. Where do you reccomend I create this room? On my backend then? Do I need to make any client change son my iOS app? or will it simply just join an already made room instead of it trying to join one and waiting for it to be automatically created?
t
Short answer — yes: create the room on your backend before issuing the join token. Your iOS client needs no special changes; it will simply join the pre‑created room instead of triggering implicit creation. Why this is recommended • Creating the room server‑side lets you include the
egress
parameters (or start egress) reliably. • Tokens issued to the iOS client only carry permissions; they do not include egress configuration. • Server creation avoids race conditions and ensures recordings/monitoring start immediately. Minimal flow to implement 1. Backend: call CreateRoom with an
egress
object (or call StartEgress) and ensure the call uses a JWT that has
roomCreate
and
roomRecord
. 2. Backend: generate a join token for the iOS client (normal flow). 3. iOS client: use the token and call joinRoom as usual — no client changes required. Practical tips • Don’t block user signup on CreateRoom latency: create the room just before issuing the token, or create it asynchronously and start egress on first join. • Use least-privilege storage credentials for uploads. • Verify in Console or via
ListEgress
that the egress job is active after the room is created. • If you want every room recorded without server calls, ask about Console-level AutoEgress (may be limited); otherwise server creation is the reliable approach. Would you like a small Python backend example that (1) calls CreateRoom with
egress
, (2) mints a join token, and (3) returns it to your iOS client? If so, tell me which storage provider (S3/GCP/Azure) and I’ll give the exact snippet.
m
Ok great. my backend is in node js. Please give me the parameters that my backend needs to initiate the egress for every room it creates.
t
Thanks — below are the exact parameters your Node.js backend needs to start AutoEgress (room composite) for every room you create, plus a concise example using the LiveKit server SDK. What you must provide • roomName: the room identifier • egress output config (choose one): ◦ file (single MP4):
file.filepath
+ one of
file.s3
/
file.gcp
/
file.azure
◦ HLS (segments): `segments.playlistName`/`segments.filepath` +
segments.s3
/
segments.gcp
/
segments.azure
◦ stream (RTMP/SRT):
stream.urls: string[]
• Storage credentials: ◦ S3:
accessKey
,
secret
,
bucket
,
region
, optional
endpoint
, optional
forcePathStyle
◦ GCP:
credentials
(service account JSON string),
bucket
◦ Azure:
accountName
,
accountKey
,
containerName
• Optional egress options:
layout
(grid/speaker),
audioOnly
,
filename patterns
(use
{room_name}
,
{time}
,
{timestamp}
) • API auth: LiveKit API key/secret (server) with
roomRecord
permission • Optionally: webhook URL to receive egress completion events Minimal Node.js flow (summary) 1. Create room (optional) via RoomServiceClient or let room exist. 2. Call EgressClient.startRoomCompositeEgress with the outputs and options above. 3. Monitor status via ListEgress or webhooks. Minimal example (RoomComposite → S3)
Copy code
import { EgressClient, RoomServiceClient } from "livekit-server-sdk";

const LIVEKIT_URL = "https://<your-livekit-host>";
const API_KEY = "your-api-key";
const API_SECRET = "your-api-secret";

const roomService = new RoomServiceClient(LIVEKIT_URL, API_KEY, API_SECRET);
const egressClient = new EgressClient(LIVEKIT_URL, API_KEY, API_SECRET);

async function createAndStartEgress(roomName) {
  // 1. Create room (optional)
  await roomService.createRoom({ name: roomName });

  // 2. Start room composite egress (HLS segments example)
  const outputs = {
    segments: {
      // HLS playlist name/path written to bucket
      playlistName: "stream.m3u8",
      filepath: `recordings/${roomName}/{timestamp}/`, // optional path pattern
      s3: {
        accessKey: "AWS_ACCESS_KEY",
        secret: "AWS_SECRET",
        region: "us-west-2",
        bucket: "your-bucket",
        forcePathStyle: true // for non-AWS providers
      }
    }
  };

  const opts = {
    layout: "grid", // or "speaker"
    audioOnly: false
  };

  const info = await egressClient.startRoomCompositeEgress(roomName, outputs, opts);
  console.log("egress started:", info.egressId);
}
MP4 file variant (single file) • Use
file
instead of `segments`:
Copy code
const outputs = {
  file: {
    filepath: `recordings/${roomName}-{time}.mp4`,
    s3: { accessKey: "...", secret: "...", bucket: "...", region: "..." }
  }
};
GCP variant
Copy code
gcp: {
  credentials: JSON.stringify(serviceAccountJson),
  bucket: "your-gcp-bucket"
}
Azure variant
Copy code
azure: {
  accountName: "your-account",
  accountKey: "your-key",
  containerName: "your-container"
}
Permissions & tokens • Calls from your backend must use API key/secret that permit
roomRecord
. • iOS clients need no change; they will receive a join token and simply join the pre-created room. Operational notes • If you pre-create the room, issue the join token after CreateRoom (or start egress right after creating room). • If you prefer not to pre-create, call startRoomCompositeEgress when your backend detects the first participant joined. • Monitor egress via Console, ListEgress, or egress completion webhooks. If you want, I can: • Produce a full Express.js endpoint that (a) creates room, (b) starts egress, and (c) returns a join token, or • Generate the exact outputs object for your chosen provider (S3/GCP/Azure). Which would you prefer? Sources: RoomComposite Egress | LiveKit Docs | Egress API | LiveKit docs
m
Output object for GCP please.
t
Thanks — below are ready-to-use GCP output objects for both MP4 (single file) and HLS (segments) that you can pass to
startRoomCompositeEgress
from your Node.js backend. Key requirements •
gcp.credentials
must be the service account JSON string (service account needs Storage Object Creator on the bucket). • Use
{room_name}
,
{time}
, or
{timestamp}
in
filepath
for predictable names. • Backend calls must use API key/secret that include
roomRecord
. MP4 file output (single file)
Copy code
const outputsFile = {
  file: {
    filepath: "recordings/{room_name}-{time}.mp4",
    gcp: {
      credentials: JSON.stringify(serviceAccountJson), // service account JSON object/string
      bucket: "your-gcp-bucket"
    }
  }
};
HLS segments output (streamable)
Copy code
const outputsHls = {
  segments: {
    playlistName: "stream.m3u8",
    filepath: `recordings/${roomName}/{timestamp}/`, // folder prefix for segments
    gcp: {
      credentials: JSON.stringify(serviceAccountJson),
      bucket: "your-gcp-bucket"
    }
  }
};
Minimal Node.js usage example
Copy code
const { EgressClient } = require("livekit-server-sdk");
const egressClient = new EgressClient(LIVEKIT_URL, API_KEY, API_SECRET);

async function startGcpEgress(roomName) {
  const outputs = {
    // pick one:
    // ...outputsFile
    // or
    // ...outputsHls
  };

  const opts = { layout: "grid" };

  const info = await egressClient.startRoomCompositeEgress(roomName, outputs, opts);
  console.log("egress started:", info.egressId);
}
If you want, tell me: • whether you prefer MP4 or HLS, and • paste (or confirm) whether your service account JSON is available so I can show the exact
credentials
format to embed.
m
mp4 if fine. But we will actually do audio only. Do i need mp4 in that case?
t
Short answer — you don’t need MP4 if you don’t want video, but MP4 is fine for audio‑only recordings. Use the MP4
file
output and set
audioOnly: true
(or pick an audio‑only format like Ogg if you prefer). Below is the exact GCP MP4 output object for audio‑only and a minimal Node.js example to start a room composite egress that records audio only. GCP MP4 (audio‑only) output object
Copy code
const outputsFileAudioOnly = {
  file: {
    filepath: "recordings/{room_name}-{time}.mp4",
    gcp: {
      credentials: JSON.stringify(serviceAccountJson), // service account JSON string
      bucket: "your-gcp-bucket"
    }
  }
};
Node.js example (audioOnly flag)
Copy code
const { EgressClient } = require("livekit-server-sdk");
const egressClient = new EgressClient(LIVEKIT_URL, API_KEY, API_SECRET);

async function startAudioOnlyEgress(roomName) {
  const outputs = outputsFileAudioOnly; // from above
  const opts = {
    layout: "speaker",    // optional; layout used for rendering (ignored for audio-only)
    audioOnly: true
  };

  const info = await egressClient.startRoomCompositeEgress(roomName, outputs, opts);
  console.log("egress started:", info.egressId);
}
Notes • MP4 container will contain only audio when
audioOnly: true
. • If you prefer pure audio formats, you can use WebM/Ogg outputs for smaller audio-only files. • Ensure the GCP service account has Storage Object Creator on the bucket and your backend API key has
roomRecord
permission. Would you like the Ogg/WebM variant or a full Express endpoint that (1) creates the room, (2) starts audio egress, and (3) returns a join token?
m
Please create the example endpoint and specifically highlight the sercets etc and information I need for it on my backend.
t
Thanks — below is a concise Express.js endpoint that (1) creates a room, (2) starts audio-only MP4 egress to GCP, and (3) returns a join token for your iOS client. I’ll also list exactly which secrets/credentials your backend needs and best-practice notes. Endpoint (Express + livekit-server-sdk)
Copy code
// server.js
const express = require("express");
const bodyParser = require("body-parser");
const { RoomServiceClient, EgressClient, AccessToken, grants: { VideoGrant } } = require("livekit-server-sdk");

const app = express();
app.use(bodyParser.json());

// === CONFIG (set these in environment variables, not in code) ===
const LIVEKIT_URL = process.env.LIVEKIT_URL;           // e.g. "<https://cloud.livekit.io>"
const LIVEKIT_API_KEY = process.env.LIVEKIT_API_KEY;   // server API key (has roomRecord permission)
const LIVEKIT_API_SECRET = process.env.LIVEKIT_API_SECRET; // server API secret

const GCP_SERVICE_ACCOUNT_JSON = process.env.GCP_SA_JSON; // full JSON string (base64 if you prefer)
const GCP_BUCKET = process.env.GCP_BUCKET;               // target bucket name

const JOIN_TOKEN_TTL = 60 * 60; // seconds (adjust as needed)

// instantiate clients
const roomService = new RoomServiceClient(LIVEKIT_URL, LIVEKIT_API_KEY, LIVEKIT_API_SECRET);
const egressClient = new EgressClient(LIVEKIT_URL, LIVEKIT_API_KEY, LIVEKIT_API_SECRET);

// === Helper: generate join token for iOS client ===
function generateJoinToken(identity, roomName) {
  const at = new AccessToken(LIVEKIT_API_KEY, LIVEKIT_API_SECRET, { identity });
  const grant = new VideoGrant({ room: roomName, roomJoin: true });
  // include roomRecord? not required for client join, server starts egress
  at.addGrant(grant);
  at.setValidFor(JOIN_TOKEN_TTL);
  return at.toJwt();
}

// === Endpoint: create room, start audio-only egress, return join token ===
<http://app.post|app.post>("/create-room-and-token", async (req, res) => {
  try {
    const { roomName, identity } = req.body;
    if (!roomName || !identity) return res.status(400).json({ error: "roomName and identity required" });

    // 1) Create room (idempotent)
    await roomService.createRoom({ name: roomName });

    // 2) Start room composite egress (audio-only MP4 → GCP)
    const outputs = {
      file: {
        filepath: `recordings/${roomName}-{time}.mp4`,
        gcp: {
          credentials: GCP_SERVICE_ACCOUNT_JSON, // must be the JSON string
          bucket: GCP_BUCKET
        }
      }
    };

    const opts = {
      audioOnly: true,
      // layout ignored for audioOnly, but you can set other options if needed
    };

    const egressInfo = await egressClient.startRoomCompositeEgress(roomName, outputs, opts);

    // 3) Generate join token for client
    const token = generateJoinToken(identity, roomName);

    return res.json({
      token,
      roomName,
      egressId: egressInfo.egressId
    });
  } catch (err) {
    console.error("create-room-and-token error:", err);
    return res.status(500).json({ error: err.message || "internal error" });
  }
});

// start server
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => console.log(`listening on ${PORT}`));
Secrets & information your backend needs (and where to store them) • LIVEKIT_URL: LiveKit Cloud base URL. • LIVEKIT_API_KEY & LIVEKIT_API_SECRET: your server API key/secret. Must have roomRecord permission so the server can start egress. Store in environment variables or a secrets manager (do NOT check into source). • GCP_SERVICE_ACCOUNT_JSON: service account JSON (string). The service account must have the "Storage Object Creator" role on your bucket. Store this in a secrets manager or environment variable (consider base64 encoding it). • GCP_BUCKET: destination bucket name. • Optional: webhook endpoint URL if you want egress completion notifications (configure in LiveKit egress call or your account settings). Permissions & best practices • Use least-privilege credentials: ◦ LiveKit API key: grant only the server privileges needed (roomCreate, roomRecord). ◦ GCP service account: only Storage Object Creator on the specific bucket. • Keep secrets in environment variables or a managed secrets store (AWS Secrets Manager, GCP Secret Manager, Vault). • Do not embed secrets in client code (iOS app should only receive the join token). • Rotate keys periodically and monitor access logs. Operational notes • startRoomCompositeEgress is idempotent per egress job; you’ll get an
egressId
to track the job. • Monitor status via ListEgress or configure webhooks to be notified of completion/failure. • If you prefer not to block token issuance, call create/start egress right before issuing token or in background; creating room is fast but can involve cross-region delays. • If you ever want per-participant audio files instead of a composite, use
tracks
egress instead. Would you like: • The same endpoint but returning the egress status URL or hooking up an egress completion webhook handler? • The Express example with GCP JSON read from a file (if you prefer that over env var)?