---
title: "Build a Deno Voice Agent with OpenAI Realtime at the Edge"
description: "Deno Deploy ships TypeScript voice bridges to 35 edge regions in seconds. Real working code for Deno.serve, WebSocket subprotocol auth, and global low-latency."
canonical: https://callsphere.ai/blog/vw2h-build-deno-voice-agent-openai-realtime-edge
category: "AI Infrastructure"
tags: ["Tutorial", "Build", "Deno", "Edge", "OpenAI Realtime"]
author: "CallSphere Team"
published: 2026-04-05T00:00:00.000Z
updated: 2026-05-07T09:27:40.608Z
---

# Build a Deno Voice Agent with OpenAI Realtime at the Edge

> Deno Deploy ships TypeScript voice bridges to 35 edge regions in seconds. Real working code for Deno.serve, WebSocket subprotocol auth, and global low-latency.

> **TL;DR** — Deno's standards-based runtime + 35-region Deploy edge = sub-200ms cold start voice bridges anywhere. Same WebSocket API as the browser; ship the same code to backend and edge.

## What you'll build

A Deno HTTP server that upgrades browser sockets, opens a paired Realtime socket using the WebSocket subprotocol auth pattern (browser-like envs can't set headers), and runs on Deno Deploy's global edge with zero config.

## Prerequisites

1. Deno 1.46+ (or 2.x) installed.
2. Deno Deploy account (or run locally on `:8000`).
3. `OPENAI_API_KEY` saved as a Deno Deploy env var.
4. A frontend that POSTs base64 audio over WebSocket text frames.
5. `deno_kv` if you want session persistence (built-in).

## Architecture

```mermaid
flowchart LR
  B[Browser anywhere] -- ws --> E[Deno Deploy edge nearest user]
  E -- ws --> O[OpenAI Realtime]
```

## Step 1 — `Deno.serve` with WebSocket upgrade

```typescript
Deno.serve({ port: 8000 }, (req) => {
  const url = new URL(req.url);
  if (url.pathname !== "/voice") return new Response("nf", { status: 404 });
  if (req.headers.get("upgrade") !== "websocket") {
    return new Response("expected websocket", { status: 400 });
  }
  const { socket, response } = Deno.upgradeWebSocket(req);
  attach(socket);
  return response;
});
```

## Step 2 — Open the OpenAI socket using subprotocol auth

In Deno (and browsers), you can't set arbitrary headers on a WebSocket. OpenAI accepts auth via the **subprotocol** array:

```typescript
function openOAI() {
  const key = Deno.env.get("OPENAI_API_KEY")!;
  return new WebSocket(
    "wss://api.openai.com/v1/realtime?model=gpt-4o-realtime-preview-2025-06-03",
    [
      "realtime",
      `openai-insecure-api-key.${key}`,
      "openai-beta.realtime-v1",
    ],
  );
}
```

(Note: in production, prefer minting an ephemeral key from a secured handler instead of putting the API key in the subprotocol — but the subprotocol pattern is what unlocks Deno/edge for Realtime.)

## Step 3 — Wire the bridge

```typescript
function attach(client: WebSocket) {
  const oai = openOAI();
  let oaiOpen = false;
  const queue: string[] = [];

oai.onopen = () => {
    oaiOpen = true;
    oai.send(JSON.stringify({
      type: "session.update",
      session: {
        instructions: "You are CallSphere's edge agent. Reply in 1-2 sentences.",
        voice: "alloy",
        turn_detection: { type: "server_vad", threshold: 0.5 }
      }
    }));
    while (queue.length) oai.send(queue.shift()!);
  };

oai.onmessage = (e) => client.readyState === 1 && client.send(e.data as string);
  oai.onclose = () => client.close();

client.onmessage = (e) => {
    const t = typeof e.data === "string" ? e.data : "";
    if (!t) return;
    if (oaiOpen) oai.send(t); else queue.push(t);
  };
  client.onclose = () => oai.close();
}
```

## Step 4 — Mint ephemeral keys (production-grade)

Move auth out of the subprotocol entirely:

```typescript
async function mint() {
  const r = await fetch("[https://api.openai.com/v1/realtime/sessions](https://api.openai.com/v1/realtime/sessions)", {
    method: "POST",
    headers: {
      "Authorization": "Bearer " + Deno.env.get("OPENAI_API_KEY"),
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      model: "gpt-4o-realtime-preview-2025-06-03",
      voice: "alloy",
    }),
  });
  return (await r.json()).client_secret.value as string;
}
```

Hand that key to the browser; the browser then connects WebRTC directly to OpenAI. Your edge function only does auth + token issuance.

## Step 5 — Deploy to global edge

```bash
deno deploy --project=callsphere-voice-edge ./server.ts
```

Deno Deploy automatically replicates to 35 regions; users get the nearest pop. Cold start is typically <50ms.

## Step 6 — Use Deno KV for per-session state

```typescript
const kv = await Deno.openKv();
async function bumpUsage(userId: string) {
  await kv.atomic()
    .sum(["usage", userId, new Date().toISOString().slice(0, 10)], 1n)
    .commit();
}
```

## Common pitfalls

- **Using `headers` on WebSocket** — Deno honors them only outside Deploy; on edge use subprotocol or ephemeral keys.
- **Long-running async without WebSocket** — Deploy kills idle non-WS workers at 30s.
- **Stuffing API key in subprotocol in prod** — leaks in browser dev tools.
- **Forgetting onclose cleanup** — orphaned OAI sockets bill you.

## How CallSphere does this in production

CallSphere's edge marketing experiments (lead-gen voice landing pages — see [/affiliate](/affiliate) for our 22% partner program) run on Deno Deploy in front of our healthcare FastAPI :8084 cluster. 37 agents, 6 verticals — and the edge layer adds <40ms.

## FAQ

**Why Deno over Node?** First-class TypeScript, no node_modules, web-standard APIs.

**Does Deno KV scale?** Built on FoundationDB; tens of millions of ops/day, fine for session state.

**Can I run Pipecat on Deno?** Pipecat is Python; pair Deno frontend with Modal Python backend.

**Cold start?** ~30ms typical, <100ms p99.

**Pricing?** Free tier covers ~1M req/mo; paid is usage-based.

## Sources

- [Deno.serve docs](https://docs.deno.com/runtime/manual/runtime/http_server_apis/)
- [OpenAI Realtime WebSocket](https://developers.openai.com/api/docs/guides/realtime-websocket)
- [m1guelpf/openai-realtime-proxy](https://github.com/m1guelpf/openai-realtime-proxy)
- [Supabase WebSockets in Edge Functions](https://supabase.com/docs/guides/functions/websockets)

---

Source: https://callsphere.ai/blog/vw2h-build-deno-voice-agent-openai-realtime-edge
