Supabase Realtime for Sume render progress, with RLS per subscriber
Add the job table to the supabase_realtime publication and subscribe with a row filter. Realtime checks RLS for every subscriber, so keep the table lean.

To show Sume render progress in a browser without polling, write the job status into a Postgres row from your webhook receiver and let Supabase Realtime push the row change. Add the table to the supabase_realtime publication, subscribe with a filter on the row id, and keep the browser away from the Sume API key and the Sume endpoints altogether.
The pattern splits cleanly: Sume talks to your server through the signed job webhook, your server updates a renders row, and the client only ever sees that row.
What Realtime does with each change
The Postgres Changes guide says you enable it per table with alter publication supabase_realtime add table renders;, filter with column=eq.value, and that each change is authorized against every subscriber under row level security. It also notes that changes are processed on a single thread, so throughput is bounded and depends on how many subscribers must be checked; the figure it gives is around 3,000 messages per second on a Micro instance with RLS. A DELETE filter needs the table's replica identity set to full.
For a render tracker that is plenty. A user opens one page, subscribes to one id, and sees a few updates per job: queued, in progress, completed or failed. The guidance that matters is to write updates sparingly. Do not stream every Sume status check into the table; write on transitions.
| Column | Written by | Purpose |
|---|---|---|
| id | Your server on submit | Subscription filter id=eq.value |
| user_id | Your server | RLS policy: user_id = auth.uid() |
| sume_job_id | Your server, after the 202 | Join key for webhook events |
| status | Webhook receiver | queued, running, completed or failed |
| result_url | Webhook receiver, on job.completed | What the page shows |
SQL and client
The first block enables the stream and the policy; the second is the browser subscription.
-- run once
alter publication supabase_realtime add table renders;
alter table renders enable row level security;
create policy "own renders" on renders for select using (user_id = auth.uid());
// browser
const channel = supabase
.channel(`render-${renderId}`)
.on("postgres_changes",
{ event: "UPDATE", schema: "public", table: "renders", filter: `id=eq.${renderId}` },
(payload) => setRender(payload.new))
.subscribe();
// later: supabase.removeChannel(channel)Keep the secret server-side
The webhook receiver, not the browser, writes status and result_url, using a server-side credential that bypasses RLS. Verify the Sume signature before touching the row (see the Webhooks guide) and match on sume_job_id so a replayed event rewrites the same values. The page should also fetch the row once on load, since a subscription only reports changes made after it connects.
Sources
Related posts
More in Developers
- SvelteKit +server.js endpoint to verify a Sume webhook signature
A SvelteKit +server.js POST handler gets a Fetch Request, so request.text() gives the raw body Sume signs. Verify the HMAC, then handle job.completed.
- Swap the TTS engine, keep the voice: Sonic 3.5 to 3.6 on Sume
Cartesia treats the TTS model and the voice as separate things. On Sume the model id and voice id are separate fields, so you can A/B 3.5 and 3.6 on one voice.
- Swift 6.4 CryptoKit: verify a Sume webhook signature
Verify x-sume-webhook-signature in Swift with CryptoKit HMAC<SHA256>: timestamp window, comma-separated entries, constant-time compare, empty secret refused.
- Swift 6.4 URLSession: poll a Sume job with async/await
Swift 6.4 shipped on 15 September 2026. An async URLSession loop for GET /v1/jobs/:id/status that honors next_poll_after_seconds and a 20-minute deadline.
Written by Sume