Sign in

Supabase + Liveblocks

Supabase provides a Postgres database with a rich set of features. Using webhooks, you can set up one-way synchronization of your Liveblocks data to Supabase for reporting, search, audit logs, or app workflows.

How data sync works

Liveblocks webhooks trigger when certain events happen, such as when a collaborative document updates. Liveblocks can trigger an endpoint in your back end, and from here, you can fetch the latest data and write it to Supabase. Here’s an example of how it works with Liveblocks Storage.

Edits document storageUpdated webhook getStorageDocument API Storage data Upsert row data 200 OK Throttle User Liveblocks app Webhook endpoint Supabase

Which data can be synced?

Various types of Liveblocks data can be synched to Supabase with webhooks.

NameDescriptionRelevant webhookRelevant API
RoomsCreated rooms and metadata.roomUpdatedgetRoom
Active usersCurrently connected users.userEnteredgetActiveUsers
Liveblocks StorageCustom realtime document state.storageUpdatedgetStorageDocument
React FlowFlowchart state.storageUpdatedmutateFlow
YjsY.Doc document state.ydocUpdatedgetYjsDocument
Tiptap/BlockNote/LexicalText editor state.ydocUpdatedgetYjsDocument
ThreadsComments, reactions, more.threadCreatedgetThread
This is a summary

This is not an exhaustive list—around 15 related webhook events are available, along with many Node.js methods, Python functions, and REST API endpoints.

Setup

Quickstart for synching Liveblocks data to Supabase. In this example, we sync Liveblocks Storage data to Supabase, but you can use the same pattern with other APIs and webhooks to sync other types of data.

Step-by-step guide

We have a full step-by-step guide available here, this page provides a quick summary.

  1. Create the Supabase table

    Use one row for each Liveblocks room.

    create table liveblocks_documents (  room_id text primary key,  data jsonb not null,  updated_at timestamptz not null default now());
  2. Create a webhook endpoint

    Add a back end endpoint in your app, for example at /api/liveblocks-webhook.

    export async function POST(request: Request) {  const body = await request.json();  const headers = request.headers;
    // Verify the webhook event, then sync to Supabase // ...
    return new Response(null, { status: 200 });}
  3. Subscribe to Storage updates

    In the Liveblocks dashboard, navigate to the “Webhooks’ page inside a project,
    and create a webhook endpoint for your endpoint URL—this requires you to host your local project. Subscribe to storageUpdated, then copy the webhook secret.

  4. Verify the webhook event

    In your endpoint, using WebhookHandler, verify the webhook event with the webhook secret from the dashboard.

    import { WebhookHandler } from "@liveblocks/node";
    const webhookHandler = new WebhookHandler( process.env.LIVEBLOCKS_WEBHOOK_SECRET!);
    export async function POST(request: Request) { const body = await request.json(); const headers = request.headers;
    // Verify if this is a real webhook request let event; try { event = webhookHandler.verifyRequest({ headers: headers, rawBody: JSON.stringify(body), }); } catch (err) { console.error(err); return new Response("Could not verify webhook call", { status: 400 }); }
    // Sync to Supabase // ...
    return new Response(null, { status: 200 });}
  5. Sync Storage to Supabase

    Set up your Liveblocks and Supabase clients, before fetching the Storage document data with getStorageDocument and upserting the Supabase row with upsert.

    import { Liveblocks, WebhookHandler } from "@liveblocks/node";import { createClient } from "@supabase/supabase-js";
    const liveblocks = new Liveblocks({ secret: process.env.LIVEBLOCKS_SECRET_KEY!,});
    const supabase = createClient( process.env.SUPABASE_URL!, process.env.SUPABASE_SERVICE_ROLE_KEY!);
    const webhookHandler = new WebhookHandler( process.env.LIVEBLOCKS_WEBHOOK_SECRET!);
    export async function POST(request: Request) { const body = await request.json(); const headers = request.headers;
    // Verify if this is a real webhook request let event; try { event = webhookHandler.verifyRequest({ headers: headers, rawBody: JSON.stringify(body), }); } catch (err) { console.error(err); return new Response("Could not verify webhook call", { status: 400 }); }
    if (event.type === "storageUpdated") { const { roomId } = event.data;
    // Get Storage document data const data = await liveblocks.getStorageDocument(roomId, "json");
    // Upsert into Supabase const { error } = await supabase.from("liveblocks_documents").upsert({ room_id: roomId, data, updated_at: new Date().toISOString(), }); }
    return new Response(null, { status: 200 });}
  6. Data sync is set up!

    Your Liveblocks data is now automatically synched to Supabase when the webhook event is fired.

Limits and troubleshooting

Storage or Yjs data is stale

storageUpdated and ydocUpdated webhooks are throttled because collaborative documents can be modified up to 60 times per second. Treat Supabase as an eventually consistent mirror, not as the live editing channel.

Webhook verification fails

Check that LIVEBLOCKS_WEBHOOK_SECRET is the webhook secret for the webhook endpoint that sent the event. Also make sure your endpoint passes the same raw body string to verifyRequest that it received from Liveblocks.

Supabase writes fail

Keep the Supabase service role key on the server. If Row Level Security is enabled on the mirror table, make sure your server client has permission to write to it.

Duplicate writes happen

Webhook deliveries can be retried. Use upsert with a stable primary key such as room_id, thread_id, or comment_id so repeated deliveries update the same row.

Liveblocks REST requests fail

Check that LIVEBLOCKS_SECRET_KEY is a secret key from the same Liveblocks project as the room. If the request still fails, return a non-2xx response so the webhook can be retried.

Related docs