Build a Supabase-Backed Voice Agent with RLS and Edge Functions
Use Supabase Auth, RLS-protected Postgres, and Deno Edge Functions to ship a multi-tenant voice agent. Real working SQL policies and TS code.
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 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.
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.
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
Try CallSphere AI Voice Agents
See how AI voice agents work for your industry. Live demo available -- no signup required.