Ephemeral Sessions
Use the OpenAI Realtime API directly from a browser — without shipping your API key to the client. Your server calls POST /v1/ephemeral, gets a 60-second client_secret, and forwards only that to the browser.
How it works
Your real OpenAI API key is never sent to the browser — not even for a millisecond. The ephemeral key expires after 60 seconds and is scoped to a single Realtime session.
Setup
Create a kvt_ token for the realtime use-case
Create a token from the dashboard or API. Set allowedHosts to your backend domain — the browser never uses the kvt_ token directly.
# Create a token with the openai provider and realtime scopecurl -X POST https://keyvaultedge.com/api/tokens \ -H "Authorization: Bearer <your_kve_api_key>" \ -H "Content-Type: application/json" \ -d '{ "name": "realtime-backend", "providerSlug": "openai", "allowedHosts": ["yourdomain.com"], "rateLimit": { "requests": 60, "window": "1m" } }'# → { "token": "kvt_abc123..." }Add a server route that calls /v1/ephemeral
This route lives on your server. It holds the kvt_ token and calls the KVE proxy, then returns only the ephemeral client_secret to the browser.
// Server route: POST /api/realtime-session// Called by your front-end to get a short-lived client_secret export async function POST(request: Request) { const session = await fetch("https://proxy.keyvaultedge.com/v1/ephemeral", { method: "POST", headers: { "Authorization": "Bearer kvt_abc123...", // your kvt_ token "Content-Type": "application/json", }, body: JSON.stringify({ model: "gpt-4o-realtime-preview-2024-12-17", voice: "alloy", }), }).then(r => r.json()); // session.session.client_secret.value — valid for 60 seconds return Response.json({ client_secret: session.session.client_secret.value });}Use the ephemeral key in the browser
Fetch the session from your server route, then connect to the OpenAI Realtime API using the returned client_secret.
// Browser — connect directly to OpenAI Realtime API// using the ephemeral key (never touches your real API key) const { client_secret } = await fetch("/api/realtime-session", { method: "POST",}).then(r => r.json()); const pc = new RTCPeerConnection();const dc = pc.createDataChannel("oai-events"); const ms = await navigator.mediaDevices.getUserMedia({ audio: true });pc.addTrack(ms.getTracks()[0]); const offer = await pc.createOffer();await pc.setLocalDescription(offer); // Connect to OpenAI Realtime with the ephemeral keyconst answer = await fetch( "https://api.openai.com/v1/realtime?model=gpt-4o-realtime-preview-2024-12-17", { method: "POST", headers: { "Authorization": `Bearer ${client_secret}`, "Content-Type": "application/sdp", }, body: offer.sdp, }).then(r => r.text()); await pc.setRemoteDescription({ type: "answer", sdp: answer }); dc.addEventListener("message", e => { const event = JSON.parse(e.data); console.log("Realtime event:", event);});Response shape
{ "provider": "openai", "session": { "id": "sess_...", "object": "realtime.session", "model": "gpt-4o-realtime-preview-2024-12-17", "client_secret": { "value": "ek_...", // send this to the browser "expires_at": 1751500000 // Unix timestamp — ~60 s from now } }, "expires_at": "2025-07-02T18:06:40.000Z"}The expires_at field is also returned as a top-level ISO-8601 string for convenience. The session object is passed through verbatim from OpenAI.
Rate limiting
The /v1/ephemeral endpoint counts against the same rate limit as normal proxy requests on the token. Set a conservative limit (e.g. 60 req/min) since each call provisions a full Realtime session on OpenAI's side.
Billing note
Realtime sessions are billed by OpenAI based on audio and token usage — not by KeyVault Edge. Each /v1/ephemeral call itself counts as one request against your token's rate limit.
Audit log
Every ephemeral session is logged in the ephemeral_sessions table in your Supabase database with: the token ID, organisation ID, provider, OpenAI session ID, expiry time, requesting IP, and Cloudflare PoP. These records are visible in the Security section of the dashboard and are retained for 90 days.