Skip to main content
You can let visitors talk to an SLNG voice agent directly in the browser. You need a small backend to create the web session (keeps your SLNG key off the client) and a frontend that connects to LiveKit for real-time audio.

Placeholders

The snippets below use these placeholders. Replace them with your own values before running the code.

Prerequisites

  • A configured Voice Agent with its agent ID
  • An SLNG key (get one at app.slng.ai)
  • A backend you can deploy server-side code to (Node.js, Deno, Python, etc.)
  • A frontend project with React (the examples below use React, but the LiveKit client SDK works with any framework)

How it works

  1. The browser asks your backend to start a session.
  2. Your backend calls the Voice Agents web-sessions endpoint and forwards the LiveKit credentials back.
  3. The browser connects to the LiveKit room, publishes the mic, and plays the agent’s audio.

Step 1: Create a backend endpoint

Your backend proxies the Voice Agents API so the SLNG key never reaches the browser.
Never call the Voice Agents API directly from client-side code. Your SLNG_API_KEY must stay server-side.
The only call you need is:
The response includes the fields you need for the frontend:

Step 2: Install the LiveKit client SDK

Step 3: Connect to the LiveKit room

Call your backend to get a session, then connect to the room:
The browser will prompt the user for microphone access on createLocalAudioTrack(). If your page is not served over HTTPS, most browsers will block the request.
Noise cancellation is applied automatically on the server: the agent runs background voice cancellation on web participants’ audio before transcription. You don’t need any client-side audio processing or filter libraries.

Step 4: Play the agent’s audio

Attach the agent’s remote audio track to the DOM so the browser plays it:

Step 5: Handle real-time messages

The agent publishes real-time events over a LiveKit data channel on the slng.transcript.v1 topic. Every message is a UTF-8-encoded JSON object with a common envelope: v (payload version, currently 1), type, and created_at (Unix timestamp in seconds):
There is one more message type, call_event (level, stage, and optional data) — customer-safe call lifecycle events. It shares the same envelope but is recorded with the call on the SLNG backend, not published over the room data channel, so your page never receives it.

conversation_item_added

Sent when a completed turn — from the user or the agent — is added to the conversation history:

user_input_transcribed

Streams the speech-to-text output while the user is talking. You receive a series of interim messages (is_final: false), each carrying the transcript of the utterance so far, followed by one final message (is_final: true):
This is how you render live captions: show transcript while is_final is false, then clear the caption on the final message (the completed turn also arrives as conversation_item_added):

agent_state_changed and user_state_changed

Sent whenever the agent or the user transitions between states:
Agent states are initializing, idle, listening, thinking, and speaking. User states are speaking, listening, and away. The agent’s state is also mirrored to the lk.agent.state attribute on the agent participant (see Step 7).

close

Sent when the agent ends the session — because it called its hang-up tool, the user was idle too long, the maximum session duration was reached, or something failed. This is how your page knows the call is over:
The room disconnects shortly afterwards, so treat close as your “call ended” signal: stop the mic, update the UI, and don’t offer reconnect-style retries for reason: "normal".

debug

Verbose diagnostics (pipeline stages, errors with stack traces). These are only published to the room when room-data debugging is enabled for your deployment — off by default, so don’t build UI on them:

Step 6: Add mute and disconnect controls

Step 7: Track what the agent is doing

Use the agent_state_changed data message from Step 5 to know whether the agent is listening, thinking, or speaking, and drive a visual indicator or avatar animation from it. Don’t infer this from audio levels (RoomEvent.ActiveSpeakersChanged) — they can tell you the agent is talking, but they can’t distinguish thinking from idle:
The same state is also mirrored to the lk.agent.state attribute on the agent participant, so as an alternative you can read it without parsing data messages:

Optional: Add a visual persona

A voice-only interface gives users no visual cue about what the agent is doing. Adding an animated persona (an orb, waveform, or avatar) makes the experience feel more responsive. Two ready-made libraries work well here:

Vercel AI SDK Persona

A React component with built-in states: idle, listening, speaking, thinking. Drop it in and map LiveKit events to states.

ElevenLabs Conversational UI

Orb and avatar components designed for voice interfaces, with audio-reactive animations.
To wire either library up, map the agent state from Step 7 to a persona state — the states line up almost one-to-one:

Putting it all together

A minimal React component with all the steps above wired together:

Next steps