Before you ship
Production checklist
The widget and the protocol are the easy half. The other half is the operational surface around them: origin policy, key rotation, token TTLs, abuse protection. Run through these items before pointing real users at a deployment.
1. Lock down allowed origins
The deployment's Allowed origins list is the first authorization layer for the WebSocket. The runtime checks the browser's Origin header against the list; anything missing gets a 1008 policy-violation close.
- Include only the exact origins your site uses.
https://acme.comandhttps://www.acme.comare different origins. - Do not add
*. There's no operational reason in a production deploy. - For staging vs production, keep separate deployments with different keys and different origin lists, so there is no chance of crossing them.
- Origin headers are forgeable outside browsers. They protect against accidental embedding by a third-party site; they don't protect against a targeted attacker who runs their own HTTP client. The token-mint flow is what enforces real auth.
2. Treat the deployment key like a service credential
The deployment key (issued from the console when you create or rotate a deployment) is equivalent to a service API key. Same controls apply.
- Never commit it. Mount via your secrets manager (Vault, AWS Secrets Manager, GCP Secret Manager).
- Never log it. Scrub it from request/response middleware logging.
- Rotate on a schedule. The console exposes a Rotate key action that issues a new key and revokes the old one atomically.
- Rotate immediately on any suspected leak (laptop loss, sloppy commit, CI exposure).
- Per environment: separate keys for staging vs production. Don't share.
3. Mint short-lived tokens
The token your server mints is single-use and time-boxed. Defaults are sensible; tune per workload.
{
"endUserId": "user_42", // optional stable identifier
"metadataJson": { "any": "json" }, // free-form, forwarded to event log
"sessionConfig": { // optional per-call overrides
"voice": {
"voiceId": "af_sarah",
"vad": { "silenceDurationMs": 350 }
},
"prompt": {
"systemPrompt": "You are a friendly support assistant."
}
}
}- Override
endUserIdserver-side. Pull it from the signed-in session on your backend; don't trust the browser-supplied value. - Whitelist
sessionConfigkeys. The runtime applies a built-in allowlist (prompt / voice / VAD), but if you forward user-controlled JSON verbatim you give the caller knobs you may not want them touching. Validate. - Don't cache tokens. They're single-use. A replayed token within the TTL gets a 401 from the runtime. Mint a fresh one per session.
4. Plan for the degraded path
STT and TTS providers occasionally hiccup. The runtime emits platform.stt_degraded and platform.tts_degraded with severity: open when a circuit breaker trips, and severity: recovered when service comes back. Wire your UI to those:
mic.addEventListener('voiceorch:event', (e) => {
if (e.detail.type === 'platform.stt_degraded' && e.detail.severity === 'open') {
showBanner(`Speech recognition is temporarily unavailable. Retrying in
${Math.round(e.detail.retry_after_ms / 1000)}s.`);
}
if (e.detail.type === 'platform.stt_degraded' && e.detail.severity === 'recovered') {
clearBanner();
}
});Don't auto-retry the WebSocket while the breaker is open. The runtime is short-circuiting requests, so your retry would just burn through the cooldown. Wait for the recovered event or for the user to retry manually.
5. Rate-limit your token endpoint
The voice_orch backend rate-limits its own login endpoint, but your /api/voice-token handler is its own DoS surface. A scraper hitting it will burn token-mints (which call into voice_orch) at full request speed.
- Require auth: only signed-in users on your product should be able to mint tokens. That alone reduces the attack surface from "the public internet" to "people with accounts."
- Apply a per-user-per-minute limit (5–10 mints / min is plenty for a real user).
- If your product is fully public-facing (no signup before talking), gate the mint endpoint by IP rate-limit + a CAPTCHA on the first request.
6. Observability you should add
- Log every
platform.usage.turnevent withvoice_session_id+ yourendUserId. That's your billing / cost-per-user audit trail. - Alert on
platform.stt_degraded/platform.tts_degradedevents atseverity: open. They are leading indicators of upstream failures. - Track
response.status: 'failed'onresponse.doneevents. Steady-state failure rate over >1% means something is wrong (often a tool timing out).
7. The security model in one paragraph
The deployment key authorizes minting tokens. The connect token authorizes opening one WebSocket. The WebSocket carries audio and tool calls; tools inherit the workspace's connector credentials (also held server-side, never exposed to the browser). The browser never sees anything more than a 10-minute single-use token. If a session is captured, the worst case is "an attacker can finish that one conversation."