Skip to content
Database

Database Webhooks

Trigger external payloads on database events.

Database Webhooks allow you to send real-time data from your database to another system whenever a table event occurs.

You can hook into three table events: INSERT, UPDATE, and DELETE. All events are fired after a database row is changed.

Webhooks vs triggers#

Database Webhooks are very similar to triggers, and that's because Database Webhooks are a convenience wrapper around triggers using the pg_net extension. This extension is asynchronous, and therefore will not block your database changes for long-running network requests.

This video demonstrates how you can create a new customer in Stripe each time a row is inserted into a profiles table:

Creating a webhook#

  1. Create a new Database Webhook in the Dashboard.
  2. Give your Webhook a name.
  3. Select the table you want to hook into.
  4. Select one or more events (table inserts, updates, or deletes) you want to hook into.

Since webhooks are database triggers, you can also create one from SQL statement directly.

create trigger "my_webhook" after insert
on "public"."my_table" for each row
execute function "supabase_functions"."http_request"(
'http://host.docker.internal:3000',
'POST',
'{"Content-Type":"application/json"}',
'{}',
'1000'
);

We currently support HTTP webhooks. These can be sent as POST or GET requests with a JSON payload.

Call an Edge Function#

A webhook can call an Edge Function on each table event, and the function receives the payload as its request body. Call the function with your project's secret key, and accept it in the function with auth: 'secret' and verify_jwt = false. For more information, see Service-to-service calls.

From the Supabase Dashboard#

  1. Create a Database Webhook in the Supabase Dashboard.
  2. In Type of webhook, select Supabase Edge Functions, then select your function.
  3. Under HTTP Headers, select Add secret key.

The webhook saves the key in its trigger definition, so the key appears anywhere the trigger's SQL does, such as a database dump.

From a migration#

To keep the secret key out of your migration files, store it in Supabase Vault and read it from a trigger function.

  1. In the SQL Editor, store your project URL and secret key in Vault. Run these statements once for each project, outside your migrations:

    select vault.create_secret('https://project-ref.supabase.co', 'project_url');
    select vault.create_secret('YOUR_SUPABASE_SECRET_KEY', 'secret_key');
  2. In a migration, create a trigger function that reads both values from Vault and calls the Edge Function with pg_net. Then attach it to the table:

    create function public.call_function_name()
    returns trigger
    language plpgsql
    security definer
    set search_path = ''
    as $$
    begin
    perform net.http_post(
    url := (select decrypted_secret from vault.decrypted_secrets where name = 'project_url') || '/functions/v1/function-name',
    headers := jsonb_build_object(
    'Content-Type', 'application/json',
    'apikey', (select decrypted_secret from vault.decrypted_secrets where name = 'secret_key')
    ),
    body := jsonb_build_object(
    'type', tg_op,
    'table', tg_table_name,
    'schema', tg_table_schema,
    'record', to_jsonb(new),
    'old_record', null
    )
    );
    return new;
    end;
    $$;
    create trigger call_function_name
    after insert on public.my_table
    for each row execute function public.call_function_name();

    The trigger function is security definer because the roles that insert rows, such as authenticated, can't read Vault. Without it, their inserts fail with a permission error.

Payload#

The payload is automatically generated from the underlying table record:

type InsertPayload = {
type: 'INSERT'
table: string
schema: string
record: TableRecord<T>
old_record: null
}
type UpdatePayload = {
type: 'UPDATE'
table: string
schema: string
record: TableRecord<T>
old_record: TableRecord<T>
}
type DeletePayload = {
type: 'DELETE'
table: string
schema: string
record: null
old_record: TableRecord<T>
}

Monitoring#

Logging history of webhook calls is available under the net schema of your database. For more info, see the GitHub Repo.

Local development#

When using Database Webhooks on your local Supabase instance, you need to be aware that the Postgres database runs inside a Docker container. This means that localhost or 127.0.0.1 in your webhook URL will refer to the container itself, not your host machine where your application is running.

To target services running on your host machine, use host.docker.internal. If that doesn't work, you may need to use your machine's local IP address instead.

For example, if you want to trigger an edge function when a webhook fires, your webhook URL would be:

http://host.docker.internal:54321/functions/v1/my-function-name

If you're experiencing connection issues with webhooks locally, verify you're using the correct hostname instead of localhost.

Resources#

  • pg_net: an async networking extension for Postgres