Supabase Database Webhooks are fundamentally a pub-sub system where the "publisher" is your PostgreSQL database’s logical replication stream and the "subscriber" is any HTTP endpoint you configure.
Let’s see this in action. Imagine you have a todos table. When a new todo is added, you want to notify an external service.
First, you need to enable logical replication for your Supabase project. This is done in the Supabase dashboard under "Database" -> "Replication" -> "Logical replication" and clicking "Enable logical replication". This makes your database’s write-ahead log (WAL) available for external consumption.
Next, create a webhook in Supabase. Go to "Database" -> "Webhooks" -> "New webhook".
- URL:
https://my-external-service.com/new-todo(This is where Supabase will send the change data). - Table:
todos - Events:
INSERT(We only care about new todos for this example). - Secret:
supersecretkey123(Optional, but highly recommended for security. Your service should validate this).
Now, when you insert a row into your todos table:
INSERT INTO todos (task, completed) VALUES ('Buy groceries', false);
Supabase’s replication system detects this INSERT operation. It then formats the change data into a JSON payload and sends an HTTP POST request to https://my-external-service.com/new-todo.
The payload will look something like this:
{
"type": "INSERT",
"table": "todos",
"schema": "public",
"new": {
"id": 1,
"task": "Buy groceries",
"completed": false,
"created_at": "2023-10-27T10:00:00.000Z",
"updated_at": "2023-10-27T10:00:00.000Z"
},
"old": null,
"commit_timestamp": "2023-10-27T10:00:01.123Z",
"event_id": "a1b2c3d4-e5f6-7890-1234-567890abcdef"
}
Your external service receives this POST request. It can then process the new object, perhaps by updating a dashboard, sending a notification, or triggering another workflow. If you configured a Secret, your service should verify the x-webhook-secret header matches supersecretkey123 to ensure the request is genuinely from your Supabase project.
The core problem Supabase Webhooks solve is decoupling your database from your application logic. Instead of your application polling the database for changes or having complex triggers directly within SQL that call external APIs (which can be brittle and hard to manage), you declare the desired change notifications at the database level. Supabase handles the complexity of reading the WAL, formatting the data, and making HTTP requests reliably. It’s an event-driven architecture powered by your database.
The event_id is crucial for idempotency. If your external service receives the same event_id twice, it means Supabase retried a delivery. Your service should be designed to handle these retries gracefully, ensuring that processing the same event multiple times doesn’t cause duplicate actions.
The commit_timestamp indicates when the transaction was committed to the database. This can be useful for ordering events or for implementing time-based logic in your consuming service.
The type field can be INSERT, UPDATE, DELETE, or TRUNCATE, allowing you to react to different kinds of data modifications. For UPDATE events, both new and old fields will be populated, showing the state of the row before and after the change. For DELETE events, only the old field will contain the row data.
A common pattern is to use Supabase Functions to process these webhooks. You can create a serverless function that receives the webhook payload, validates the secret, and then performs actions. For example, a Function could receive the INSERT event for a new todos item and then send a push notification to a mobile app via a push notification service.
The real magic is that the replication slot used by the webhook is managed by Supabase. If your external service is down, Supabase will queue up the events and retry. This retry mechanism is configurable to a degree within Supabase’s infrastructure, ensuring you don’t lose critical data changes.
The next step is often to explore how to filter events more granularly, perhaps by specific column changes during an UPDATE, or by implementing multiple webhooks for different tables and event types.