Node.js / TypeScript

Secure Your OpenAI API Key in Node.js (Realtime API)

OpenAI Realtime requires a client-side WebSocket or WebRTC connection — there is no way to make that connection without an API credential in the browser. Hardcoding your sk-proj- key in JavaScript means any visitor can extract it and drain your account. Ephemeral session tokens expire in 60 seconds, are scoped to a single session, and cannot be reused.

The Problem

This is what most Node.js (Realtime API) codebases look like today. The OPENAI_API_KEY key (sk-proj-...) lives directly in the process environment. If your repo, your CI logs, or your deployment config leaks — so does your key.

TypeScript
// ❌ Real API key sent to the browser — extracted by any visitor
const pc = new RTCPeerConnection();
const dc = pc.createDataChannel("oai-events");

// key sent in the Sec-WebSocket-Protocol header, visible in DevTools
const session = await fetch("https://api.openai.com/v1/realtime/sessions", {
  method: "POST",
  headers: {
    "Authorization": "Bearer sk-proj-YOUR_REAL_KEY",   // ← exposed to browser
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ model: "gpt-4o-realtime-preview-2024-12-17" }),
}).then(r => r.json());

The Fix

Send the request to the KeyVault Edge proxy at https://proxy.keyvaultedge.com with the provider's usual path, your kvt_ token in X-KV-Token and the provider's base URL in X-KV-Target. The proxy refuses requests whose Origin isn't on the token's allowed hosts, then injects your real OPENAI_API_KEY, so it never sits in your app's environment.

TypeScript
// ✅ Server exchanges the kvt_ token for a 60-second ephemeral credential
// backend route — your real key is injected here at the Worker, never sent to the browser
const { session } = await fetch("https://proxy.keyvaultedge.com/v1/ephemeral", {
  method: "POST",
  headers: {
    "X-KV-Token": process.env.OPENAI_KVT_TOKEN!, // kvt_... token
    // Server-side calls must send an Origin on the token's allowlist
    Origin: process.env.APP_ORIGIN!,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    model: "gpt-4o-realtime-preview-2024-12-17",
    voice: "alloy",
  }),
}).then(r => r.json());

// Forward only the ephemeral client_secret to the browser — expires in 60 s
res.json({ ephemeralKey: session.client_secret.value, expiresAt: session.client_secret.expires_at });

Setup in 5 steps

1

Create a sanitized token for OpenAI

In the KeyVault Edge dashboard, add your OpenAI API key under Providers, then go to Tokens → New Token and select the OpenAI key. Set the allowed host to your backend's domain (at least one is required) and a short expiry.

2

Call POST /v1/ephemeral from your backend

On your server, send the sanitized kvt_ token in X-KV-Token along with the session config (model, voice, instructions). The Worker validates the token and forwards the request to OpenAI's /v1/realtime/sessions with your real API key.

TypeScript
const kvtToken = process.env.OPENAI_KVT_TOKEN!; // never send to browser

const { provider, session, expires_at } = await fetch(
  "https://proxy.keyvaultedge.com/v1/ephemeral",
  {
    method: "POST",
    headers: {
      "X-KV-Token": kvtToken,
      Origin: process.env.APP_ORIGIN!, // one of the token's allowed hosts
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      model: "gpt-4o-realtime-preview-2024-12-17",
      voice: "alloy",
    }),
  }
).then(r => r.json());

// session.client_secret.value is a ~60-second ephemeral key
console.log("Ephemeral key expires at:", expires_at);
3

Forward only the ephemeral key to the browser

Return only session.client_secret.value to the client. This key is useless after ~60 seconds and is scoped to a single Realtime session — even if extracted from the browser, an attacker cannot reuse it.

TypeScript
// Next.js Route Handler
export async function POST() {
  const { session, expires_at } = await getEphemeralSession();
  return Response.json({
    ephemeralKey: session.client_secret.value,
    expiresAt: expires_at,
  });
}
4

Connect with the ephemeral key in the browser

Use session.client_secret.value as the API credential for your WebRTC or WebSocket Realtime connection. The OpenAI SDK's createRealtimeSession helper accepts this directly.

TypeScript
// browser code — only the short-lived key, never the real sk-proj-...
const { ephemeralKey } = await fetch("/api/realtime-session", { method: "POST" }).then(r => r.json());

const pc = new RTCPeerConnection();
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);

const sdpResponse = await fetch("https://api.openai.com/v1/realtime?model=gpt-4o-realtime-preview-2024-12-17", {
  method: "POST",
  body: offer.sdp,
  headers: {
    Authorization: `Bearer ${ephemeralKey}`,  // ← 60-second ephemeral key only
    "Content-Type": "application/sdp",
  },
});
5

Enable rate limiting and cost caps on the token

Set requests_per_hour on the sanitized token to limit how many Realtime sessions your backend can issue per hour. Add a monthly_cost_cap_usd to prevent runaway Realtime API spend even if your backend is compromised.

What this protects, and what it doesn't

  • Your real OPENAI_API_KEY stays at the edge, so it can't leak from your repo, logs, bundle or server environment.
  • The kvt_ token is still a credential. A copied token can be replayed by anyone who can send the right headers, including a fake Origin from a server. The protection is that each token has its own rate limits, monthly cost cap and endpoint scope, and you can revoke it in one click without rotating the provider key.
  • The cost cap is estimated from the usage the provider reports, so a request already in flight can overshoot it slightly.

More on layered defences in how to protect your OpenAI API key. Plans and limits are on the pricing page.

Start protecting your keys

Your OpenAI key is one leaked .env file away from being exploited. With KeyVault Edge the real key stays at the edge, and the token that replaces it is capped, scoped and revocable in one click.