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.com and https://www.acme.com are 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.

POST /api/public/deployments/{publicId}/token (request body fields)JSON
{
  "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 endUserId server-side. Pull it from the signed-in session on your backend; don't trust the browser-supplied value.
  • Whitelist sessionConfig keys. 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:

JavaScript
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.turn event with voice_session_id + your endUserId. That's your billing / cost-per-user audit trail.
  • Alert on platform.stt_degraded / platform.tts_degraded events at severity: open. They are leading indicators of upstream failures.
  • Track response.status: 'failed' on response.done events. 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."