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.
- 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.
- SWR refreshInterval as a function: poll a Sume job and stop
SWR accepts a function for refreshInterval that receives the latest data. Return Sume's next_poll_after_seconds while running and 0 once terminal is true.
Written by Sume