Header Logo

Embedding a Realtime AI Avatar in a Phone Support Workflow with Next.js and LiveKit

Embedding a Realtime AI Avatar in a Phone Support Workflow with Next.js and LiveKit

Build a realtime AI avatar into a Next.js LiveKit support flow with server-side sessions, lip-sync sync, and secure token handling.

Introduction


Phone support is one of those workflows where a “good enough” AI voice agent still feels incomplete if it has no visible presence. A live, lip-synced face changes the interaction model: it makes the agent easier to follow, gives users something to focus on, and fits naturally into web-based support flows where a call can be accompanied by an embedded UI.


This post walks through the practical side of embedding a realtime AI avatar into a Next.js support experience backed by LiveKit. By the end, you should understand where the avatar belongs in the architecture, how the voice and video streams stay synchronized, and how to wire the pieces together without exposing credentials in the browser.


Architecture: keep the voice agent and the avatar in separate lanes


The cleanest mental model is to treat the support agent as two realtime subsystems:


  • Audio/control plane: the voice agent handles speech-to-text, LLM reasoning, tool calls, and text-to-speech.

  • Visual plane: the avatar consumes the agent’s generated speech and renders a talking face that is synchronized with that audio.


With LiveKit, your agent typically joins a room as a participant. The avatar then appears as another media source in that room or as a separate rendered stream, depending on the integration. The important constraint is that the avatar’s mouth motion needs to track the timing of the TTS audio, not just the raw text. If those two drift apart, the result looks uncanny immediately.


In practice, you want the agent runtime to emit speech in the same cadence that the avatar rendering pipeline expects. That means:


  1. Receive the user’s audio.

  2. Transcribe and feed the transcript into your agent logic.

  3. Generate the response text.

  4. Stream TTS audio and the corresponding avatar animation with aligned timestamps.


The exact transport details vary by agent stack, but the failure modes are consistent: buffering too aggressively increases latency; streaming too early can cause the avatar to “start” before the audio is ready; and re-chunking audio without respecting timestamps can break lip sync.


Embedding in a Next.js support page


If your support flow is already web-based, the UI usually has three concerns:


  • connect the user to the LiveKit room,

  • mount the avatar/video surface, and

  • keep the overall experience responsive while the agent is speaking, listening, or waiting.


Next.js is a good fit because it lets you keep room-token minting and any support-session state on the server, while the client only receives short-lived credentials. Do not put API keys or long-lived secrets in the browser. That rule applies whether you are using LiveKit tokens, avatar session APIs, or both.


A minimal shape for the support page is:


export default async function SupportPage() {

}
export default async function SupportPage() {

}
export default async function SupportPage() {

}


That example is intentionally incomplete: the real job is to establish the media connection and mount the avatar renderer where your layout expects it. The server-side part is what matters operationally. Use it to authorize the user, mint ephemeral access, and attach support metadata like ticket ID or customer segment.


Realtime synchronization: what actually matters for lip sync


People often focus on “video generation” here, but the engineering problem is mostly synchronization. A realtime avatar looks believable only if the face animation tracks the prosody and timing of the speech stream closely enough that the viewer’s brain accepts it as one continuous source.


There are a few practical implications:


  • Latency budget: if your agent takes too long to start speaking, the conversation feels broken even if the output is correct.

  • Chunk size: larger TTS chunks are easier on the backend, but can make the avatar start and stop unnaturally.

  • Interruptibility: support agents need barge-in. If the user talks over the avatar, the pipeline should stop playback cleanly and transition back to listening.

  • State transitions: idle, listening, thinking, and speaking states should be explicit in the UI so the user knows what the system is doing.


For phone support specifically, I would avoid treating the avatar as a decorative widget. It should be tied to the same session lifecycle as the call: connect, authenticate, listen, speak, escalate, terminate. If the visual layer is allowed to keep running after the call has ended, you end up with stale sessions and confusing UI state.


One simple pattern is to store a single support-session object on the server and derive both the LiveKit room token and the avatar session from it. That makes teardown deterministic and helps with auditing, usage tracking, and retry logic.


Server-side creation of a realtime avatar session


Whether you create the avatar from your app backend or from an internal tool, keep the call server-side and use a short-lived API key only on the trusted path. The API surface is REST-based, which makes it easy to create sessions from Next.js route handlers or a small Python service.


import requests

session = resp.json()
import requests

session = resp.json()
import requests

session = resp.json()


That snippet is illustrative only; the exact request shape lives in the docs. The point is the boundary: your backend creates or updates the avatar session, then passes only the session identifier or short-lived connection data to the browser. From there the client can render the avatar without ever seeing your API key.


If you are using Python for orchestration, the SDK gives you the same basic ergonomics with less boilerplate than raw HTTP. For example:


from protoface import Client

)
from protoface import Client

)
from protoface import Client

)


In a production support system, I would couple this with a database record keyed by your own support ticket ID. That lets you resume or inspect sessions, and it gives support staff a clean place to see what was created when a customer reports a problem.


Using the LiveKit agent plugin to add the face to an existing voice agent


If you already have a LiveKit-based voice agent, the most direct path is to drop in the avatar plugin rather than rebuilding your media pipeline. The plugin for LiveKit Agents is published as livekit-plugins-protoface, and the integration is intentionally narrow: it gives your voice agent a synchronized talking face while leaving the core agent logic alone. The plugin repository and examples are a good place to start if your stack is already on LiveKit. See the GitHub organization for the relevant code and quickstarts.


A sketch of the integration looks like this:


from livekit.plugins import protoface

)
from livekit.plugins import protoface

)
from livekit.plugins import protoface

)


The important thing here is not the constructor signature; it is the control flow. The plugin should sit at the point where your agent already knows what it is about to say. That is where the lip sync metadata can be paired with the audio output. If you try to bolt the avatar on after TTS has already finished, you will only get a delayed animation, not a realtime one.


If you are using Pipecat instead of LiveKit Agents, the same general model applies, and the integration docs are explicit about where the video service sits in the pipeline. The Pipecat guide is useful if your architecture already uses that framework.


Operational concerns: security, limits, and support workflow hygiene


There are a few non-obvious issues that show up once support traffic becomes real traffic.


Credential handling: the browser should never receive an API key. If you need the client to initiate or join a session, use your backend to mint ephemeral access or to proxy the request. For embedded web experiences, customer-managed iframe embeds are a practical option because they avoid exposing backend credentials entirely.


Rate limiting and abuse: support widgets are public-facing surfaces. If you expose a realtime avatar on a page, you need a sane limit on session duration and origin access. Otherwise you will discover very quickly that “just one more tab” can become a cost problem. Protoface’s iframe embeds are designed around parent-origin allowlists and per-embed limits, which is exactly the kind of guardrail you want for customer-facing deployments.


Fallback behavior: when the video path fails, the voice path should still work. Degrade gracefully to audio-only instead of failing the whole support interaction. A support flow is a utility, not a demo.


Observability: log the session ID, room ID, and support ticket ID together. You will need that correlation when a user reports choppy lip sync, late responses, or a session that ended unexpectedly.


Where Protoface fits


This is the point where Protoface is useful if you want the avatar layer without building the video-face infrastructure yourself. For a LiveKit-based support bot, the practical entry point is the LiveKit Agents plugin, which adds the realtime face to an existing voice agent. If you are wiring session orchestration from your backend, the REST API and Python SDK cover creation and management of avatars and sessions. The docs are the right place to check exact request fields, supported options, and current integration details: docs.protoface.com.


Conclusion


A realtime avatar in a phone support workflow is mostly an architecture problem, not a UI flourish. Keep the voice agent, avatar session, and web client responsibilities separated; stream audio and animation together; and ensure the browser only ever sees short-lived access. If you already have a LiveKit voice agent, the shortest path is to add the avatar at the agent layer rather than bolting it onto the frontend later.


If you want to implement this, start with your existing LiveKit support flow, then add the avatar in the server-side session lifecycle. For API and SDK details, check the docs, and for integration examples, use the relevant quickstart or plugin repository as a reference point.

Add a face to your AI.

No credit card needed.

Add a face to your AI.

No credit card needed.

Add a face to your AI.

No credit card needed.