Skip to main content

Troubleshooting Egress

The following topics are covered:

Note: The information presented here is equally applicable to the current StartEgress API as well as the deprecated source-specific APIs (StartRoomCompositeEgress, StartWebEgress, StartParticipantEgress, StartTrackCompositeEgress, and StartTrackEgress). Where appropriate, this doc will refer to the current API surface; see the documentation for a mapping of old API to new API.

Egress joins your room as a participant of kind EGRESS and subscribes only to the tracks it needs. Each Egress then moves through the lifecycle below, and can fail from any state before it completes:

Loading diagram…

Call StartEgress to begin. If the call returns EGRESS_STARTING, the request was accepted but recording hasn't begun yet. If the call fails or throws an error, check the status code and error message for details.

During the session, use webhooks to monitor status changes, such as when recording starts or when an error occurs. Check the EgressStatus, error, and error_code fields of the EgressInfo structure.

You can also find each Egress's status, and any error, on its analytics page in the LiveKit dashboard. A successful Egress ends with status EGRESS_COMPLETE.

Common Egress errors#

Select an error to go to its section:

ErrorStatusCode
Start signal not receivedEGRESS_ABORTED412
page load errorEGRESS_FAILED400
participant X not found / track X not foundEGRESS_FAILED404
S3/GCP/Azure upload failedEGRESS_FAILED400
pipeline frozenEGRESS_FAILED500
egress timed outEGRESS_FAILED408
Session limit reachedEGRESS_LIMIT_REACHED413
concurrent egress sessions limit exceededStartEgress throws429
egress minutes exceededStartEgress throws429
api requests per minute limit exceededStartEgress throws429
[Feature] is not yet supportedEGRESS_FAILED400
S3 upload using AssumeRole is disabled for this accountStartEgress throws403

Start signal not received#

Status: EGRESS_ABORTED · Code: 412

Egress using a TemplateSource, including custom templates, waits for the page to signal that recording should begin. The most likely cause is that the template never sent that signal.

Specifically for custom templates:

  • To send the start signal, call console.log('START_RECORDING'), or call startRecording() from the template SDK.
  • A rendering error in your custom template, such as accessing an undefined value, can prevent the template from sending START_RECORDING. See Debugging custom templates later in this guide.
  • If this error happens intermittently, the most likely cause is an intermittent failure to load external network resources. Check which resources your template pulls in.

Notes: TemplateSource was formerly called RoomComposite Egress. The above does not apply to audio-only recordings that don't set a layout or custom_base_url.

Any of the following can also cause this issue:

  • The room was deleted or ended before the Egress started. An Egress with a TemplateSource is driven by its room.
  • The room closed automatically because no participants were present (Egress and agent participants don't count), and the room's empty or departure timeout fired.
  • The Egress is stopped before any participant publishes. (If you stop it very soon after starting, the error may instead read Stop called before pipeline could start).

Debugging custom templates#

Status: EGRESS_FAILED · Code: 400 (page load error)

To test your template locally, use the lk egress test-template command, described in Test your template. For example, I test a diagnostics template served on localhost like this:

1
lk egress test-template \
2
--base-url http://localhost:3000/templates/diagnostics \
3
--room nss-test \
4
--layout grid \
5
--publishers 2

This opens my template with two demo publishers, and I can use the browser's JavaScript console to debug any issues.

A faulty template can cause two types of error:

  • Start signal not received (412), if the template never sends START_RECORDING or has a rendering error, as explained earlier.
  • page load error, if loading the template returns an HTTP status of 400 or above. Most often this means the template URL (the value of customBaseUrl) can't be found, but it can also be an access issue or a connection timeout. The URL can't be localhost or on a private network, because Egress running on LiveKit Cloud must be able to reach it.

A third error, template error, happens when the template page reports an error as soon as it loads. For example, the default template does this when its URL or token parameters are missing. This differs from a rendering error, which happens after the page loads and is covered earlier.

Participant or track not found#

Status: EGRESS_FAILED · Code: 404

If you list specific tracks to record in your MediaSource and any one of them hasn't started publishing within 30 seconds of your StartEgress call, the Egress fails. The error message names one of the missing tracks, for example participant user-z9ednxN not found or track TR_456789 not found. The same error and logic apply to the deprecated Participant, Track and TrackComposite APIs.

To avoid this race condition, start the Egress after the participant or track has started publishing.

Bucket credential validity#

Status: EGRESS_FAILED · Code: 400

Non-segmented file outputs upload to your S3, GCP or Azure bucket when the Egress finishes, using the credentials you passed in StorageConfig when you called StartEgress. If there's a problem with those credentials, the Egress fails with code 400 and an S3/GCP/Azure upload failed error that includes more detail, for example:

1
"error": "GCP upload failed:
2
Post \"https://storage.googleapis.com/upload/storage/v1/b/dcc-test-egress/o
3
?alt=json
4
&name=egress-test-1791378262047-2026-10-07T130429.mp4
5
&prettyPrint=false
6
&projection=full
7
&uploadType=multipart\":
8
auth: cannot fetch token: 400
9
Response: {
10
\"error\": \"invalid_grant\",
11
\"error_description\": \"Invalid JWT Signature.\"
12
}",

Because the upload happens at the end of the recording but you provide the credentials at the start, the credentials must stay valid for at least as long as the recording. As an alternative to long-lived access keys, LiveKit supports AWS AssumeRole for S3 Egress, but only on specific plans. See the pricing page for details.

Segmented file outputs upload continuously, but they have the same problem: if the credentials expire partway through the recording, segment uploads start to fail.

Pipeline frozen#

Status: EGRESS_FAILED · Code: 500

This error means the output couldn't be finalized within 30 seconds of the end of the stream. You shouldn't see it on LiveKit Cloud. If you do, contact support.

If you self-host Egress, make sure you're on the latest release. If you still see pipeline frozen, open an issue in the Egress repo with your Egress version, the request you used and the Egress logs.

Egress timed out#

Status: EGRESS_FAILED · Code: 408

An EGRESS_FAILED with code 408 and error egress timed out only occurs on LiveKit Cloud and may indicate an internal error. If you see it, wait to see whether a later egress_ended arrives and check your storage bucket, then contact support if you still need help. Self-hosted Egress never returns this error.

Duration limit#

Status: EGRESS_LIMIT_REACHED · Code: 413

The maximum duration of an Egress depends on its output type. The Egress time limits section of the documentation lists the limits for LiveKit Cloud.

If one Egress has two or more outputs, the shortest limit applies to the whole Egress. For example, an Egress with both a file and an HLS output is limited to the file time limit.

When an Egress reaches its limit, it ends with error Session limit reached and code 413. The recorded data is still finalized and sent to your output destinations, but anything after the time limit isn't recorded.

If these limits are too short, you have two options:

  • Switch to an output type with a longer limit, for example, if File is too short, use Segmented HLS File output instead.
  • Track how long your Egress has been running, and start a new Egress just before the limit. Then stop the Egress that's about to hit the limit. This uses an extra concurrent Egress slot during the overlap, but ensures you don't miss any of the recording.

If you self-host Egress, you can configure duration limits with the session_limits setting.

Egress usage limits#

Status: StartEgress throws an error and no Egress is created · Code: 429

Your LiveKit Cloud project or workspace has several Egress usage limits: how many Egress sessions you can run concurrently, how many total Egress minutes you can use, and how many API requests your project can make per minute. The API request limit covers all LiveKit APIs, not just Egress.

You can find your concurrent Egress session limit and API request limit on the relevant page of the LiveKit Cloud dashboard. The Recording and export section of the pricing page lists the Egress limits for each plan and Egress type.

When you exceed a limit, your StartEgress call throws an error before any Egress is created. The error details show which limit you exceeded, for example concurrent egress sessions limit exceeded. How the error surfaces depends on the client API you use. Unlike the other errors in this guide, it comes from the API call rather than the Egress service, so you're responsible for catching and handling it in your code. Egresses that are already running aren't affected.

To resolve it, wait until your limits reset, upgrade your account, or request an increase (available on the Scale plan and above).

Unsupported features#

Status: EGRESS_FAILED · Code: 400

An error in the form [Feature] is not yet supported can have several causes. The most common is <codec> is not yet supported, which means you need to publish your track with a supported codec.

AssumeRole not enabled#

Status: StartEgress throws an error and no Egress is created · Code: 403

AWS AssumeRole lets Egress assume a role in your AWS account when it uploads to S3, so you don't need to pass long-lived access keys. The error S3 upload using AssumeRole is disabled for this account means AssumeRole isn't enabled for your project; it isn't a problem with your IAM policy. AssumeRole isn't enabled on every plan, so see the pricing page for details.

Storage and upload questions#

Should I use a unique filepath for FileOutput?#

Yes, filenames should be unique. By default, Egress names files {room_name}-{time}, which is suitable for most cases. Other filename templates are described in the documentation.

Does LiveKit automatically back up my Egress recordings?#

No. By default, LiveKit doesn't back up your Egress recordings.

How does upload retry logic work?#

Egress relies on the storage provider's SDK to retry file and segmented file uploads, and doesn't add any retry logic of its own.

Recording content questions#

Why doesn't the recording match what the agent heard?#

Egress records the raw room tracks. The audio your agent hears, which you can access through Agent Insights, includes noise cancellation and other processing. The difference between the two is expected and isn't a bug.

Egress does record any processing that happens before the track is published, such as frontend noise cancellation.

Why is audio missing during silences on phone calls?#

On SIP calls where the carrier uses silence suppression, part of a caller's speech can be missing from the Egress recording, even though the agent heard and transcribed it. If you use Egress with a MediaSource or an audio-only TemplateSource, the workaround is to ask your carrier to disable silence suppression on the SIP trunk.

Why do the audio and video drift apart?#

You shouldn't see this issue on LiveKit Cloud. If you self-host Egress, use version 1.15.0 or later, which includes the fix in #1368.

Why are participants missing?#

The default grid layout only shows video tracks, so only participants who publish video get a tile.

Why is my video blank?#

There are two common reasons:

  1. You haven't specified a video track to record. For example, you configured a MediaSource with only the audio field, or set audio_only on your TemplateSource or WebSource. The output then has no video stream.
  2. The participant's camera is inactive. Egress fills the video with black frames, or, with a TemplateSource, the participant's tile shows a placeholder.

Layout and template questions#

Why don't layouts respond to UpdateLayout()?#

The default layouts don't implement onLayoutChanged, so UpdateLayout has no effect on them. Use a custom template instead.

How do I implement a watermark or overlay?#

Use a custom template to add a watermark or overlay.

Webhook and lifecycle questions#

Why am I receiving duplicate webhooks?#

Webhooks for a given Egress arrive in a predictable order, but repeats, duplicates and missing IDs all happen in normal operation, so make your handlers idempotent. Program defensively and handle these cases:

  • egress_updated sent more than once with ACTIVE.
  • Multiple egress_ended events for one Egress ID, or an egress_ended with an empty ID. If you receive more than one egress_ended for the same ID, treat the last one delivered as correct.
  • No webhooks at all, when StartEgress is rejected.
  • A participant_joined webhook for the Egress participant itself.

Also follow the Best practices for managing webhook event streams.

Why does my Egress end when a track is republished?#

A MediaSource that selects tracks by track ID (formerly Track or TrackComposite Egress) ends when every track it records has ended. If it records more than one track, republishing one of them doesn't end the Egress, but the republished track is silently ignored.

A republished track gets a new ID, so start a new Egress after a republish. Muting a track doesn't change its ID.

Alternatively, use a MediaSource that selects the participant by identity (participant_video). It doesn't end when tracks are unpublished or republished.

Why didn't my auto egress run?#

AutoEgress simplifies your workflow by starting recordings automatically when a room is created or when tracks are published to a room. The downside is that you have less visibility when something goes wrong while creating the Egress.

Auto egress can record the room as a composite (the room field of egress in CreateRoom), starting when the room is newly created. If that Egress fails to start, no Egress webhook is sent. LiveKit Cloud still creates the room, so only the Egress is affected.

Issues specific to self-hosted Egress#

  • no response from servers (503) from StartEgress usually means LiveKit server and Egress aren't connected to the same Redis. Point both at the same Redis instance, and make sure Egress can reach your LiveKit server.
  • Chrome logging is off by default. Turn on debug.enable_chrome_logging when you debug custom templates.

Related