Header Logo

How to Keep API Keys Out of the Browser When Embedding a Realtime AI Shopping Avatar

How to Keep API Keys Out of the Browser When Embedding a Realtime AI Shopping Avatar

Keep API keys server-side for realtime AI shopping avatars with short-lived session tokens, scoped embeds, and LiveKit-safe patterns.

Introduction


If you’re embedding a realtime AI shopping avatar in a browser, the first security problem is usually not the model or the video stream. It’s the API key. Anything shipped to the client can be read, copied, and reused. That includes static JavaScript bundles, source maps, network traffic, localStorage, and DOM state. If your architecture depends on a secret living in the browser, assume it will leak.


The right pattern is simple: keep privileged API calls on the server, mint short-lived, scoped credentials or session artifacts there, and let the browser connect only to what it needs for the live session. By the end of this post, you should be able to design a browser-safe avatar integration, understand the main token and session boundaries, and choose the right integration style for your application.


What actually needs to stay server-side


For a realtime shopping avatar, the browser typically needs three things:


  • A way to display the video face and audio playback.

  • A live transport to receive updates from the agent.

  • Some application context, such as product metadata or user intent.


What it does not need is your long-lived API key for provisioning avatars, starting sessions, or managing usage. Those actions are privileged. They should be performed by your backend, which can then return a narrowly scoped, time-limited artifact to the client.


This is the same basic principle you use for S3 pre-signed URLs, payment session tokens, or OAuth access tokens: the browser gets only the minimum capability required for the specific user interaction.


Why browser-exposed API keys are a bad trade-off


It’s tempting to place a secret in the frontend during a prototype. For an embedded avatar, that usually turns into one of three failure modes:


  1. Key exfiltration: the key is visible in bundle output, devtools, or logs.

  2. Unbounded reuse: if the key can create sessions, anyone with it can do the same until you rotate it.

  3. Blast radius expansion: a key intended for one widget ends up authorizing management operations across environments.


There’s also a subtler issue: realtime systems encourage experimentation. Developers add debug output, inspect WebRTC handshakes, or expose intermediate state to diagnose latency and lip-sync. If the secret is in the browser, those debugging paths become attack surfaces.


The fix is not “hide the key better.” The fix is “don’t send it.”


The safer architecture: server-minted session artifacts


In practice, the flow should look like this:


  1. The browser requests an avatar session from your backend.

  2. Your backend authenticates the user and checks product-specific policy.

  3. Your backend calls the avatar platform’s REST API with its own API key.

  4. The platform returns whatever the browser needs to join the session.

  5. The browser uses that session artifact to connect to the live avatar experience.


The exact shape of the session artifact depends on the integration surface, but the security boundary is the same: the server holds the secret, the client holds a constrained, ephemeral capability.


For example, a backend endpoint might create a session and return a signed token or an iframe URL. The browser never sees sk_live_....


Minimal backend pattern with the REST API


If you’re using the REST API directly, your backend should be the only component that talks to api.protoface.com with an API key. The browser calls your server, not the avatar API.


import os

})
import os

})
import os

})


The important part is not the field names. It’s the boundary: the browser gets only what it needs to join the session, and your server remains the only place where the long-lived key exists.


In a production deployment, add normal controls around that endpoint: authentication, rate limiting, CSRF protection if it is cookie-authenticated, and logging that omits secrets. If the avatar is tied to a purchase flow or logged-in user, enforce that on the server before minting the session.


Session scope, lifetime, and revocation


Once you stop treating the browser as a trusted environment, a few design choices become obvious:


  • Scope: a browser session should authorize only one avatar experience or one tenant context.

  • Lifetime: keep join tokens short-lived so leaked artifacts expire quickly.

  • Revocation: make it possible to kill an active session server-side if abuse is detected.


For shopping avatars, this matters because the interaction often sits on top of mutable business logic. A user might request product recommendations, ask about pricing, or interact with a promotion. You want the browser to express intent, not administer your platform.


A good rule of thumb: if a credential can create, delete, or enumerate resources, it does not belong in the browser. If it only lets the browser participate in a single live session for a short period, it might be acceptable.


Embedding without backend exposure


There is one integration style that avoids the API-key problem entirely: a managed <iframe> embed. In this model, you add the avatar to your site without shipping any secret or writing backend code for the avatar lifecycle. The browser loads a customer-managed embed that handles the realtime session internally.


This is useful when you want a low-friction implementation with controlled access. The embed can enforce parent-origin allowlisting, per-embed voice and custom instructions, and rate limits by IP and duration. That gives you a tighter perimeter than “put a key in JavaScript and hope for the best.”


For teams that just need a realtime avatar on a marketing page, support page, or lightweight shopping flow, this is often the cleanest answer. It reduces integration surface area and removes an entire class of secret-management mistakes.


Where the LiveKit plugin fits


If your avatar is part of a voice agent built on LiveKit, the browser-secret problem usually shifts one layer down: your agent backend creates or joins the live session, and the avatar is attached there as a synchronized video face. The browser still should not hold privileged credentials.


With the LiveKit Agents plugin, the agent process can drive speech while the avatar renders the synchronized talking face. A minimal agent-side setup looks like this:


from livekit.agents import JobContext

await avatar.attach(ctx)  # illustrative: see plugin docs for the exact API
from livekit.agents import JobContext

await avatar.attach(ctx)  # illustrative: see plugin docs for the exact API
from livekit.agents import JobContext

await avatar.attach(ctx)  # illustrative: see plugin docs for the exact API


That belongs in the backend or agent worker, not in frontend code. If you want the avatar shown on a website, the browser should receive a live session it can consume, not the credentials used to create it. If you’re using the LiveKit path, the relevant examples live in the plugin repository and the broader integration docs on docs.protoface.com.


Operational gotchas


A few implementation details are worth getting right early:


  • Don’t log Authorization headers. This is how “temporary” secrets become permanent incidents.

  • Separate environments. Use different keys for development, staging, and production.

  • Minimize client-side config. Product IDs, UI flags, and instructions are fine; management credentials are not.

  • Assume replay is possible. If a browser token is intercepted, short expiry and server-side checks are your main defenses.

  • Watch the quality tier. For realtime media, cost tends to follow usage. Keep session creation behind your server so you can enforce policy and budget controls consistently.


Also be careful with “just one request” endpoints. A browser that can create a session can usually create many sessions. If you need per-user limits, put them on your server, not in frontend logic.


Conclusion


Keeping API keys out of the browser is not a hardening trick; it’s an architectural boundary. Put session creation and platform calls behind your backend, return only short-lived client artifacts, and treat the browser as untrusted by default. For cases where you want no backend at all, a managed iframe embed avoids secret exposure entirely.


If you’re implementing this now, start with the docs at docs.protoface.com, then pick the integration that matches your stack: direct REST calls from your server, a Python service, a LiveKit agent, or a managed iframe. If you want a quick end-to-end reference, the quickstarts linked from the GitHub repository are a good place to validate the flow before wiring it into your app.

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.