How to Isolate TTS Secrets in a Multi-Tenant Realtime AI Avatar Platform

Multi-tenant realtime AI avatars: isolate TTS secrets server-side, use tenant-scoped sessions, and prevent cross-tenant leakage.
Introduction
When you add a text-to-speech provider to a multi-tenant realtime avatar platform, the obvious risk is not just “someone steals the API key.” The more interesting failure modes are subtler: one tenant’s prompts leaking into another tenant’s audio session, a shared signing key getting reused across customers, or browser-exposed credentials being replayed outside their intended origin. In realtime systems, those mistakes are amplified because sessions are short-lived, concurrent, and often created on demand.
This post walks through a practical way to isolate TTS secrets in that kind of architecture. By the end, you should be able to design a secret boundary that keeps provider credentials out of the browser, avoids cross-tenant prompt or voice leakage, and gives you a sane operational model for rotation, revocation, and auditing.
Start with the actual trust boundaries
In a multi-tenant avatar system, “secret isolation” is not one thing. You need to separate at least four concerns:
Provider credentials: the TTS vendor API key or service account used to synthesize audio.
Tenant configuration: which voice, language, instructions, and quality tier a tenant is allowed to use.
Session state: the per-conversation objects that tie together speech, video, and transport.
Client trust: whether the caller is your backend, a signed iframe, or a browser tab you do not fully trust.
If you collapse those into one “app secret,” you end up with a key that can do too much and is hard to revoke safely. The better pattern is to keep the TTS provider key only in your server-side control plane, and derive short-lived, tenant-scoped session credentials for everything else.
Keep TTS credentials server-side only
The TTS key should never be embedded in a frontend bundle, returned to a browser, or reused as a general-purpose session token. The avatar service can expose a tenant-facing API, but the server that talks to your TTS provider should be the only place that can read the raw secret.
A clean design looks like this:
Your app authenticates the tenant.
Your backend looks up the tenant’s allowed voice configuration.
Your backend creates a short-lived avatar/session object through your platform API.
The runtime that actually synthesizes speech uses the provider secret from a private secret store.
The browser receives only ephemeral session data, never the TTS credential itself.
This separation matters because a browser session is not a secure enclave. Even if you use HTTPS, any secret delivered to the client can be exfiltrated by XSS, browser extensions, or simple copy-paste from devtools. For realtime avatars, the usual answer is to sign a narrow token or issue an ephemeral session identifier, then let the server-side media pipeline handle the TTS call.
Scope secrets by tenant, not by application
In practice, the biggest win is to stop thinking in terms of “one platform key” and instead store a per-tenant configuration object. That object should include only what the runtime actually needs: provider reference, allowed voices, and policy limits. It should not include broad admin privileges or unrelated API keys.
For example, if you run separate voice experiences for support, sales, and internal demos, each tenant can map to a different TTS configuration:
Support tenant: low-latency voice, conservative rate limits, short prompt budget.
Sales tenant: richer voice quality, longer sessions, stricter audit logging.
Internal demo tenant: restricted origin, short-lived sessions, no persistent storage.
That means revocation is localized. If one customer rotates providers or you detect suspicious usage, you can invalidate just that tenant’s TTS mapping without taking down the whole platform.
Use short-lived session credentials for realtime media
Realtime avatar sessions are usually established over WebRTC or a similar streaming transport. The browser or agent process negotiates media with the server, then media flows continuously for the duration of the interaction. That creates a temptation to reuse long-lived API keys for convenience. Don’t.
Instead, generate narrow session credentials with these properties:
Time-bounded: minutes, not hours or days.
Audience-bounded: valid only for the specific avatar/session service.
Tenant-bounded: tied to one customer or workspace.
Action-bounded: can create or join a session, but cannot list all usage or rotate keys.
That is the same principle used in modern realtime voice systems: a frontend should be able to join a narrow session, not impersonate your backend. The backend creates the session, injects the tenant-specific voice policy, and then hands back only enough information for the client or agent to connect.
Prevent cross-tenant prompt and voice leakage
Secrets are only part of the story. In a multi-tenant avatar system, leakage often comes from configuration reuse. For TTS specifically, the failure mode is accidentally applying one tenant’s voice or instruction set to another tenant’s session.
A few guardrails help:
Immutable session config: once a session starts, do not mutate its voice or instruction payload in place.
Explicit tenant IDs: every session record should carry a tenant/workspace identifier and be checked on read/write.
Per-session policy snapshots: copy the effective voice and instruction settings into the session at creation time.
Auditable configuration changes: log when a tenant changes voices, TTS providers, or quality tier.
The snapshot approach is important. If you merely store a pointer to “current voice config,” a mid-session update can change the behavior of an active conversation. That is bad for reproducibility and can become a privacy bug if tenants share infrastructure.
Operational hygiene: storage, rotation, and logs
At the infrastructure level, treat TTS secrets like any other production credential:
Store them in a private secret manager, not environment files checked into deployment artifacts.
Rotate regularly, and support dual-publish windows where old and new keys overlap briefly.
Redact logs at the boundary where request payloads are recorded.
Separate control-plane logs from media-plane logs; the former may need tenant metadata, but never raw provider secrets.
Also be careful with debug tooling. Realtime systems often log the full session object because it is convenient during development. In production, that session object should be scrubbed so it contains identifiers, not credentials. If you are using a queue or pub/sub channel between orchestration and media workers, the same rule applies there too.
How Protoface fits in
This is exactly where Protoface is useful: it gives you a tenant-facing avatar and session API, while keeping the browser-facing surface narrow. For teams embedding an interactive avatar directly on a website, the customer-managed iframe model is especially clean because no backend secret ever needs to reach the browser. You can enforce an origin allowlist, apply per-embed voice and instruction settings, and keep rate limits tied to the embed itself rather than a global application key.
For server-side integrations, the REST API and Python SDK let you create avatars and sessions from your backend, where your TTS secret can stay private. The exact request fields depend on the session shape you use, but the pattern is consistent: authenticate server-to-server, create a session with tenant-specific config, then return only ephemeral connection data to the client.
For voice-agent stacks built on LiveKit, the plugin route keeps the avatar wiring on the agent side instead of the browser side. The key point is the same: your agent process can talk to the avatar service with server-managed credentials, while the end user only sees the video face and hears the synthesized voice.
If you need the precise setup for your stack, the docs are the right place to check before you wire anything into production. The important architectural line is simple: the platform owns the secret, the tenant owns the policy, and the browser gets neither.
Common gotchas
A few mistakes show up repeatedly:
Using one TTS key for all tenants and relying on application logic alone for isolation. That works until it doesn’t.
Returning provider credentials in a session bootstrap payload because it is “only temporary.” Temporary secrets still leak.
Sharing mutable config objects across sessions, which leads to accidental cross-tenant behavior.
Putting voice selection in the client when the server should enforce allowed voices.
Logging full request bodies in a stream-oriented system, which often captures more than you intended.
None of these require exotic attacks. They are just the natural result of treating realtime media like ordinary CRUD. It is not ordinary CRUD.
Conclusion
Isolating TTS secrets in a multi-tenant avatar platform comes down to one rule: keep provider credentials in the control plane, and expose only short-lived, tenant-scoped session data everywhere else. Back that up with immutable per-session config, strict tenant boundaries, redacted logs, and a rotation strategy you can execute without downtime.
If you are implementing this now, start by drawing the trust boundaries in your own system and then move the raw TTS key out of anything that a browser or tenant-controlled process can touch. The rest is mostly disciplined session design. For deeper implementation details, see the documentation at docs.protoface.com.
