By Sagar Shankaran, Founder of CallSphere
Use Supabase Edge Functions (Deno runtime) for long-running WebSocket voice bridges. Twilio integration, Postgres for sessions, RLS for tenant isolation, deploy with one CLI command.
Key takeaways
TL;DR — Supabase Edge Functions run Deno on Cloudflare-style edge with full WebSocket support, including long-lived connections via
Deno.serveupgrade. Pair with Twilio Media Streams, OpenAI Realtime, and Postgres + RLS for tenant-isolated voice agents.supabase functions deployships in 30 seconds.
A Supabase Edge Function twilio-voice that:
call_turns table with RLS by tenant_idbrew install supabase/tap/supabase).OPENAI_API_KEY set as a Supabase secret.flowchart LR
C[Caller] --> T[Twilio]
T -->|HTTP TwiML| EF[/functions/v1/twilio-voice]
T -->|wss media| EF
EF -->|wss| OAI[OpenAI Realtime]
EF -->|insert| PG[(Postgres call_turns)]
PG -->|RLS| TENANT[tenant_id row filter]
```bash supabase functions new twilio-voice ```
```ts
// supabase/functions/twilio-voice/index.ts
Deno.serve(async (req) => {
const url = new URL(req.url);
if (req.headers.get("upgrade") === "websocket") return handleWs(req);
if (req.method === "POST") {
const host = req.headers.get("host");
return new Response(
`
```ts function handleWs(req: Request) { const { socket: twilio, response } = Deno.upgradeWebSocket(req); const ai = new WebSocket("wss://api.openai.com/v1/realtime?model=gpt-realtime", { headers: { Authorization: `Bearer ${Deno.env.get("OPENAI_API_KEY")}`, "OpenAI-Beta": "realtime=v1" } } as any);
let streamSid = ""; ai.onopen = () => ai.send(JSON.stringify({ type: "session.update", session: { instructions: "You are a friendly receptionist. Keep replies short.", voice: "marin", input_audio_format: "g711_ulaw", output_audio_format: "g711_ulaw", turn_detection: { type: "server_vad" } } }));
Hear it before you finish reading
Talk to a live CallSphere AI voice agent in your browser — 60 seconds, no signup.
twilio.onmessage = (e) => { const ev = JSON.parse(e.data); if (ev.event === "start") streamSid = ev.streamSid; if (ev.event === "media") ai.send(JSON.stringify({ type: "input_audio_buffer.append", audio: ev.media.payload })); };
ai.onmessage = (e) => { const ev = JSON.parse(e.data); if (ev.type === "response.audio.delta") { twilio.send(JSON.stringify({ event: "media", streamSid, media: { payload: ev.delta } })); } };
twilio.onclose = () => ai.close(); return response; } ```
Create the table:
```sql create table call_turns ( id uuid default gen_random_uuid() primary key, tenant_id uuid not null, call_sid text, role text check (role in ('user','assistant')), text text, created_at timestamptz default now() ); alter table call_turns enable row level security; create policy tenant_isolation on call_turns for all using (tenant_id = current_setting('request.jwt.claim.tenant_id')::uuid); ```
In the function, on response.done event, insert a row using the service role key (bypasses RLS for system writes) but tag with tenant_id from the Twilio number lookup.
```ts import { createClient } from "https://esm.sh/@supabase/supabase-js@2"; const sb = createClient(Deno.env.get("SUPABASE_URL")!, Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!); await sb.from("call_turns").insert({ tenant_id, call_sid: streamSid, role: "assistant", text }); ```
```bash supabase functions deploy twilio-voice --no-verify-jwt supabase secrets set OPENAI_API_KEY=sk-... ```
Still reading? Stop comparing — try CallSphere live.
CallSphere ships complete AI voice agents per industry — 14 tools for healthcare, 10 agents for real estate, 4 specialists for salons. See how it actually handles a call before you book a demo.
--no-verify-jwt because Twilio doesn't send a Supabase JWT. Validate Twilio signatures manually.
In Twilio → Phone Numbers → your number → Voice Webhook → https://YOUR-PROJECT.supabase.co/functions/v1/twilio-voice. Method POST.
```ts import { hmac } from "https://deno.land/x/hmac@v2.0.1/mod.ts"; function verifyTwilio(req: Request, body: string): boolean { const sig = req.headers.get("x-twilio-signature"); const url = req.url; const params = Object.fromEntries(new URLSearchParams(body)); const data = url + Object.keys(params).sort().map(k => k + params[k]).join(""); const expected = btoa(String.fromCharCode(...hmac("sha1", Deno.env.get("TWILIO_AUTH_TOKEN")!, data, "utf8", "uint8"))); return sig === expected; } ```
fetch doesn't support trailers — some streaming LLM APIs choke; use Deno.connect for raw TCP if needed.vercel-style pinning (Pro plan).CallSphere's main voice path runs on FastAPI :8084 (k3s), not Supabase Edge. We use Postgres on the same private network for our 115+ tables. Supabase Edge Functions are great for greenfield projects that want managed Postgres + RLS + Edge in one product; we needed deeper tool/policy control for our 90+ tools and 6 verticals. 37 agents, $149/$499/$1499, 14-day trial, 22% affiliate.
Q: Edge Functions vs running my own Deno? Edge Functions remove ops; for under ~50 concurrent voice calls per region the cost is hard to beat.
Q: Can I scale to 10k concurrent calls?
Pro plan supports high concurrency per function but at that scale you'll want a dedicated runtime — Edge Functions aren't right.
Q: Latency? ~700ms voice-to-voice on the closest edge POP.
Q: Auth for the agent's user-facing calls? Use Supabase Auth on the human-facing app; voice path is system-only and writes via service role.
Q: Cost? Free tier: 500k invocations/mo. Pro: $25/mo + $2/M invocations. WS active-connection time is metered separately on Pro.
Written by
Sagar Shankaran· Founder, CallSphere
Sagar Shankaran is the founder of CallSphere, where he builds production AI voice and chat agents deployed across healthcare, hospitality, real estate, and home services. He writes about agentic AI, LLM engineering, and shipping voice agents that handle real calls in production.
See how AI voice agents work for your industry. Live demo available -- no signup required.
Haystack 2.7's Agent component plus an Ollama-served Llama 3.2 gives you tool-calling RAG with citations. Here's a complete pipeline against your own document store.
Run STT, LLM, and TTS entirely on Cloudflare's edge — no OpenAI, no ElevenLabs. Real working code with Whisper, Llama 3.3 70B, and Deepgram Aura.
On May 4 2026 OpenAI published its Realtime stack rebuild — split-relay plus transceiver edge. Here is what changed and what it means for production voice agents.
Version your prompts in git, run a 50-case eval suite on every PR, block merges below threshold, and ship a new agent prompt with confidence — full GitHub Actions tutorial.
Replace expensive outbound SDR tooling with a self-hosted dialer that runs OpenAI Realtime agents at 100 concurrent calls. Full architecture and code.
HVAC companies miss 40–60% of inbound. Build a 4-agent dispatch (intake, scheduling, parts, emergency) that integrates with ServiceTitan in 600 lines.
© 2026 CallSphere LLC. All rights reserved.
Watch how CallSphere handles real customer calls, schedules appointments, and processes payments — live.
Try Live DemoBook a DemoCalculate Your ROI