Build a Supabase-Backed Voice Agent with RLS and Edge Functions
By Sagar Shankaran, Founder of CallSphere
Use Supabase Auth, RLS-protected Postgres, and Deno Edge Functions to ship a multi-tenant voice agent. Real working SQL policies and TS code.
Key takeaways
TL;DR — Supabase gives you Postgres + RLS + Auth + Deno Edge Functions in one box. Add OpenAI Realtime via the Edge Function as a WebSocket relay, and you have a multi-tenant voice agent in an afternoon.
What you'll build
A Supabase project with two tables (calls, messages), per-tenant RLS, an Edge Function that mints ephemeral OpenAI tokens, and a second Edge Function that relays Realtime traffic. Each call inserts rows that downstream LiveView/React clients see in real time.
Prerequisites
- Supabase project (free tier is enough).
supabaseCLI 1.180+.OPENAI_API_KEYset as a secret:supabase secrets set OPENAI_API_KEY=....- Auth provider configured (we'll use email/magic link).
- Familiarity with SQL and Postgres RLS.
Architecture
flowchart LR
B[Browser w/ Supabase JWT] -- WS --> EF[Edge Fn: voice-relay]
EF -- WS --> O[OpenAI Realtime]
EF -- insert --> PG[(Postgres + RLS)]
PG -- realtime --> B
Step 1 — Schema with RLS
```sql create table calls ( id uuid primary key default gen_random_uuid(), user_id uuid references auth.users (id) not null, started_at timestamptz default now(), ended_at timestamptz );
create table messages ( id bigserial primary key, call_id uuid references calls (id) on delete cascade, role text check (role in ('user','assistant','system')), text text, inserted_at timestamptz default now() );
alter table calls enable row level security; alter table messages enable row level security;
create policy "owner reads calls" on calls for select using (user_id = auth.uid());
create policy "owner inserts calls" on calls for insert with check (user_id = auth.uid());
create policy "owner reads messages" on messages for select using ( exists (select 1 from calls c where c.id = messages.call_id and c.user_id = auth.uid()) ); ```
Hear it before you finish reading
Talk to a live CallSphere AI voice agent for real estate in your browser — 60 seconds, no signup.
Step 2 — Edge Function: mint ephemeral key
supabase/functions/mint-key/index.ts:
```typescript import { createClient } from "jsr:@supabase/supabase-js@2";
Deno.serve(async (req) => { const auth = req.headers.get("Authorization") ?? ""; const sb = createClient( Deno.env.get("SUPABASE_URL")!, Deno.env.get("SUPABASE_ANON_KEY")!, { global: { headers: { Authorization: auth } } } ); const { data: { user } } = await sb.auth.getUser(); if (!user) return new Response("unauthorized", { status: 401 });
const r = await fetch("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 new Response(await r.text(), { headers: { "content-type": "application/json" } }); }); ```
Deploy: supabase functions deploy mint-key.
Step 3 — Edge Function: voice relay with persistence
```typescript import { createClient } from "jsr:@supabase/supabase-js@2";
Deno.serve(async (req) => { if (req.headers.get("upgrade") !== "websocket") return new Response("ws only", { status: 400 }); const { socket, response } = Deno.upgradeWebSocket(req);
const sb = createClient( Deno.env.get("SUPABASE_URL")!, Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")!, );
let callId: string | null = null; const oai = new WebSocket( "wss://api.openai.com/v1/realtime?model=gpt-4o-realtime-preview-2025-06-03", ["realtime", "openai-insecure-api-key." + Deno.env.get("OPENAI_API_KEY"), "openai-beta.realtime-v1"]);
oai.onmessage = async (e) => { socket.send(e.data); const msg = JSON.parse(e.data); if (msg.type === "response.text.done" && callId) { await sb.from("messages").insert({ call_id: callId, role: "assistant", text: msg.text, }); } };
socket.onmessage = (e) => { const m = JSON.parse(e.data); if (m.type === "client.call_start") { sb.from("calls").insert({ user_id: m.user_id }).select().single() .then(({ data }) => { callId = data?.id; }); } else { oai.send(e.data); } }; socket.onclose = () => oai.close(); return response; }); ```
Still reading? Stop comparing — try CallSphere live.
See the real estate AI agent handle a real call — complete, industry-specific, and live in your browser. No signup.
Step 4 — Realtime subscription in the browser
```typescript import { createClient } from "@supabase/supabase-js"; const sb = createClient(URL, ANON);
sb.channel("messages") .on("postgres_changes", { event: "INSERT", schema: "public", table: "messages" }, (p) => render(p.new)) .subscribe(); ```
Because RLS scopes the subscription to the user's own call_id rows, no extra filtering needed.
Step 5 — Set auth.uid() for transactional writes
For multi-statement Edge Function writes that need RLS, you must set the JWT explicitly:
```sql select set_config('request.jwt.claim.sub', '...uuid...', true); ```
Or use the user-scoped client (auth header forwarded), and Supabase does it for you.
Common pitfalls
- Service-role inserts bypass RLS — don't use it for user-tied data without re-checking.
- Edge Function 60s walltime cap — for long calls, return early and let the client reconnect.
- Forgetting
auth.uid()in policies — global table read. - WebSocket subprotocol leaks API key — mint ephemeral and POST it instead.
How CallSphere does this in production
We don't run on Supabase ourselves (we run our own Postgres on a 72.62 box with 115+ tables across 6 verticals), but we recommend Supabase for our affiliate developer-tier customers — under $1k MRR, RLS-protected, deploys in an hour. See /pricing for the equivalent CallSphere plans.
FAQ
Can I run all this without my own server? Yes — Edge Functions cover both the relay and the token issuance.
RLS performance? Negligible — Postgres compiles policies into the query plan.
What about HIPAA? Supabase has a BAA on Pro+; CallSphere has it on Pro+ too.
Can I use pgvector? Yes — Supabase ships pgvector for RAG.
Cost at 10k calls/day? ~$25 Supabase + your OpenAI bill.
Sources

Written by
Sagar Shankaran· Founder, CallSphere
LinkedInSagar 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.
Try CallSphere AI Voice Agents
See how AI voice agents work for your industry. Live demo available -- no signup required.