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.

4 min readSume
All posts

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.

Table design for the progress row (read 2026-10-03)
ColumnWritten byPurpose
idYour server on submitSubscription filter id=eq.value
user_idYour serverRLS policy: user_id = auth.uid()
sume_job_idYour server, after the 202Join key for webhook events
statusWebhook receiverqueued, running, completed or failed
result_urlWebhook receiver, on job.completedWhat 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

All Developers posts

Written by Sume