This guide walks through connecting Discourse’s Voice plugin to a LiveKit media server. By default, voice calls run peer-to-peer: every participant sends their audio directly to every other participant. That works great for small rooms, but bandwidth grows with room size. Routing calls through LiveKit keeps each participant’s upload constant no matter how many people join.
Prerequisites
Before you start, make sure you have:
- A Discourse site running the Voice plugin enabled
- A LiveKit server you can reach, with its API credentials. You can self-host LiveKit or use a managed offering such as LiveKit Cloud.
- Admin access to your Discourse site.
Step 1 — Enable the Voice plugin
Voice ships bundled with Discourse. Go to Admin → Settings → Plugins and
turn on the Voice (voice_enabled) setting.
Step 2 — Provision a LiveKit server and note the credentials
You need three things from your LiveKit deployment: the WebSocket URL, the
API key, and the API secret.
These steps are relatively straightforward with LiveKit Cloud:
- Create a project in the LiveKit Cloud dashboard.
- From the project’s Settings → Keys page, reveal the API key and
API secret. - Note the WebSocket URL (for example
wss://my-project.livekit.cloud).
For Self-hosted LiveKit installations, please refer to the respective documentation.
Step 3 — Configure the LiveKit connection in Discourse
Go to Admin → Plugins → Voice, open the settings and fill in the LiveKit section:
| Setting | What to enter |
|---|---|
voice_livekit_url |
The WebSocket URL, e.g. wss://livekit.example.com (or ws:// for plain-HTTP labs). |
voice_livekit_api_key |
The API key from Step 2. |
voice_livekit_api_secret |
The API secret from Step 2. |
voice_livekit_room_policy |
Which rooms use LiveKit. |
The voice_livekit_room_policy setting decides how rooms pick a transport:
| Policy | Behaviour |
|---|---|
disabled |
All calls run peer-to-peer (the default). |
per_room |
Room creators/managers opt rooms in individually with a Use media server (SFU) checkbox in the room form. |
all_rooms |
Every room is routed through LiveKit. |
Step 5 — Optional extra settings
These settings fine-tune the integration:
| Setting | Purpose |
|---|---|
voice_livekit_room_prefix |
Namespace prefix for room names on LiveKit. With multiple sites sharing one LiveKit server, set a unique prefix per site. Empty defaults to the site’s database name. |
voice_livekit_mesh_fallback |
When a LiveKit token cannot be issued and the room is empty, start on the peer-to-peer mesh instead of failing the join. Off by default — silent degradation can hide a LiveKit outage. |
voice_livekit_recording_enabled |
Allow room moderators to record calls that run on LiveKit. Recordings are produced by LiveKit Egress and stored on your LiveKit deployment’s storage (S3, GCS, local disk) — not uploaded to Discourse. |
voice_livekit_recording_filepath |
The filepath passed to LiveKit Egress, e.g. voice/{room_name}-{utc}. Supports placeholders such as {room_name}, {room_id}, {time}, {utc}; the file extension is added automatically. |
Step 6 — Optional: enable LiveKit webhooks
Webhooks are a reconcile-only backstop. When a participant’s connection dies
abruptly or a room ends, webhooks let the plugin clean up presence and room
state within seconds instead of waiting out the heartbeat TTL. They are
optional — if they can’t be delivered, calls still work, cleanup just takes
a little longer.
On the LiveKit server, add the webhook endpoint (the plugin shows the exact
snippet on its admin dashboard until the first webhook arrives):
# livekit.yaml
webhook:
api_key: <your-api-key>
urls:
- https://forum.example.com/voice/livekit/webhook
Restart LiveKit. Deliveries are authenticated with the API secret —
no additional shared secret to configure.
Step 7 — Verify the integration
Check the Discourse Voice admin dashboard
Go to Admin → Plugins → Voice → Dashboard. Once any LiveKit setting is
present, the dashboard shows a LiveKit media server status card with
live checks:
- configuration status (which settings are present and the active policy),
- whether access tokens can be signed with the key pair,
- whether the server is reachable and how many rooms are active,
- the last automatic connectivity check,
- whether webhooks are being received.
Use Refresh to run an on-demand probe.
Here is a screenshot of the Voice plugin dashboard with LiveKit enabled:
Check the LiveKit dashboard
There are details about the calls on the LiveKit dashboard, too. Here is an example:

