Updated on 2026-10-30 GMT+08:00

Database Webhooks

Scenarios

When data changes in a database, external systems often need to be notified immediately. Examples include notifying a marketing platform of product price changes, synchronizing new user registrations with a CRM system, and triggering a third-party fulfillment workflow when an order is inserted. If change-triggered outbound requests are left to the application to initiate manually after every data write, requests may be missed, and the same outbound request logic can become scattered across multiple places, making changes in one place necessitate changes in others. Built on database triggers, Database Webhooks automatically send HTTP requests (POST or GET, with a JSON payload) to an external endpoint whenever table data changes, delivering the change without requiring application-side involvement.

Webhooks share the same foundation as triggers: A Webhook is essentially a trigger whose execution logic is fixed to asynchronously sending HTTP requests through the pg_net extension. During configuration, you only need to specify the table, event, and external endpoint. The platform automatically creates the underlying trigger and constructs the request payload from the table records, eliminating the need to write networking code. pg_net sends requests asynchronously after the transaction commits, so write operations are not blocked. This makes Webhooks suitable for invocation in trigger contexts. They are well suited to cross-system integration, including:

  • Change notifications, such as notifying a marketing platform of price changes and synchronizing new users with a CRM system.
  • External workflow triggers: Initiate third-party workflows in response to data changes, such as sending emails or creating tickets.
  • Cross-system synchronization: Push changes to downstream services in real time.

Outbound requests are handled at the database layer rather than the application layer because data changes themselves are the source of these requests. Responding at the source enables timely notifications and reduces the risk of missed requests. If you need to coordinate operations entirely within the database, use triggers. If you need to refresh a frontend interface in real time as data changes, use Real-Time Subscriptions (Realtime). Realtime uses WebSockets to push changes to connected clients. Webhooks and Realtime serve different purposes: Webhooks send outbound requests to external endpoints, whereas Realtime pushes changes to connected clients.

The examples in this section continue to use the products and product_pricings tables introduced in Database Data APIs. You can refer to that section for details.

Related Concepts

  • Definition of Database Webhooks

    As described above, Webhooks are built on triggers and automatically send HTTP requests to an external endpoint when table data changes, delivering the changes to external systems. The underlying trigger calls the http_request function in the supabase_functions schema, which asynchronously sends HTTP requests using the pg_net extension. All events fire after a row changes (AFTER), so Webhooks can only notify external systems after a change occurs. They cannot modify or cancel a change before it is written. That is the role of BEFORE triggers. For details, see Database Triggers.

  • Relationship Between Webhooks and Triggers

    A Webhook is essentially a convenient wrapper around a trigger. Both share the same underlying mechanism. The difference lies in whether their execution logic is fixed.

    Table 1 Relationship between Webhooks and triggers

    Aspect

    Trigger

    Webhook

    Nature

    A database event-driven mechanism.

    A convenient wrapper around a trigger that uses pg_net to send HTTP requests externally.

    Execution logic

    SQL statements executed within the database (any logic can be implemented).

    Fixed to sending HTTP requests to an external endpoint.

    Payload

    Constructed manually within the function.

    Automatically generated by the platform from table records (including type, table, record, and old_record).

    Whether writes are blocked

    Executes synchronously. Complex logic may extend the write transaction.

    Sends requests asynchronously through pg_net, avoiding prolonged blocking of write operations.

    Configuration method

    Write a trigger function and execute CREATE TRIGGER.

    Configure through the console or a single SQL statement. The platform automatically creates the trigger.

    pg_net is an asynchronous networking extension for PostgreSQL. HTTP requests are sent after the transaction commits, so database changes are not blocked. This makes Webhooks suitable for use in trigger contexts, where synchronous network calls could delay write operations. However, response data is retained for only 6 hours by default, only JSON-based POST and GET requests are supported, and PUT and PATCH are not supported.

Use Cases

Webhooks send changes to external systems, making them suitable for cross-system integration. Like triggers and Realtime, Webhooks are driven by data changes, but they differ in who receives the changes and how they are delivered.

Table 2 Comparison of three data change mechanisms

Mechanism

Receiver

Delivery Method

Writes to the Database?

Typical Use Cases

Trigger

The database itself

Automatically executes SQL within the database

Yes (It can write data to other tables.)

Automatic field maintenance, auditing, cascading validation, and derived calculations. For details, see Database Triggers.

Webhook

External systems

Sends outbound HTTP requests.

No (outbound requests only)

Notifying third parties, synchronizing downstream systems, and triggering external workflows.

Realtime

Connected clients

WebSocket push

No (push only)

Real-time frontend updates, collaborative editing, and online presence. For details, see Real-Time Subscriptions (Realtime).

In summary, use triggers for operations within the database, Webhooks to send outbound requests to external systems, and Realtime to push updates to frontends. The key difference between Webhooks and Realtime is that Webhooks allow the database to initiate outbound HTTP requests to external systems, which must provide a receiving endpoint. Realtime, by contrast, allows clients to connect via WebSocket and receive pushed updates.

Use the following table to decide when to use Webhooks or other mechanisms.

Table 3 Scenarios and recommendations

Scenario

Recommended Mechanism

Description

Notify external systems when data changes

Webhook

For example, notify a marketing platform of price changes or synchronize new users with a CRM system.

Trigger external workflows or third-party APIs

Webhook

Initiate external workflows in response to data changes, such as sending emails or creating tickets.

Automatically maintain fields, record audit logs, and calculate derived values

Trigger

Use database triggers to coordinate operations within the database.

Refresh the frontend in real time

Realtime

Realtime subscriptions support this scenario.

Send outbound requests on a schedule

pg_cron

Periodically call pg_net on a cron schedule. This is a scheduled task, not an event-driven mechanism.

Key benefits: Send outbound requests as soon as data changes, integrate systems without polling, and configure Webhooks without writing networking code.

How It Works and Dependencies

  1. How It Works

    The Webhook execution flow is as follows: A table receives an INSERT, UPDATE, or DELETE operation. → The underlying trigger calls supabase_functions.http_request. → The function uses the pg_net extension to send an HTTP request asynchronously (POST or GET, with a JSON payload). → The external endpoint receives and processes the request. Because pg_net operates asynchronously and sends requests only after the transaction commits, Webhooks do not block database writes for an extended period.

  2. Required Extension

    Webhooks use the pg_net extension to send HTTP requests asynchronously, making it a core dependency of Webhooks. When an RDS Supabase application is created, the platform automatically installs pg_net on the managed database instance. You do not need to install or enable it manually and can configure Webhooks directly.

    Installation details (for reference only; no user action required):

    • pg_net is registered in the extensions schema instead of the public schema to avoid exposure and meet security check requirements.
    • The extension creates its own net schema to store its objects. Webhook request and response records are stored in net._http_response and can be queried in SQL Editor for troubleshooting.

Configuring Webhooks

Webhooks make change-triggered outbound requests ready to use through configuration. Configure the table, events, and external endpoint on the console, and the platform automatically creates the underlying trigger. No manual network code is required. You can also create an equivalent trigger directly using SQL statements for more granular control.

  • Method 1: Create a Webhook in the Dashboard
    1. Log in to the RDS Supabase Dashboard.
    2. Choose Database > Webhooks to access the Webhook management page.
      Figure 1 Supabase Dashboard Webhook management page
    3. Click Create a new hook.
      Figure 2 Supabase Dashboard Webhook creation page

      Configure the parameters according to Table 4.

      Table 4 Webhook configuration parameters

      Parameter

      Description

      Example Value

      Name

      Webhook name, which is also the name of the underlying trigger.

      wh_price_change_notify

      Table

      Table to be listened to.

      product_pricings

      Events

      The events that trigger the Webhook. You can select multiple events.

      Insert/Update/Delete

      URL

      The endpoint address of the external system that receives requests.

      https://marketing.example.com/api/price-change

      Method

      HTTP method.

      POST or GET

      Headers

      Request headers (key-value pairs).

      Content-Type: application/json

      Parameters

      Query parameters (appended to the URL for GET requests).

      As needed

      Timeout

      Request timeout (in milliseconds).

      1000 (Default: 2000)

    4. After you save the configuration, the platform automatically creates the underlying trigger, binds it to the selected events on the specified table, and calls supabase_functions.http_request to send the request. No network code needs to be written manually.
  • Method 2: Create a Webhook Using SQL Statements

    You can also use SQL to create an equivalent trigger that works the same way as one generated by the Dashboard. This approach is useful for including the configuration in migration scripts or customizing parameters.

    CREATE TRIGGER "wh_price_change_notify"
      AFTER INSERT OR UPDATE OR DELETE ON "public"."product_pricings"
      FOR EACH ROW
      EXECUTE FUNCTION "supabase_functions"."http_request"(
        'https://marketing.example.com/api/price-change',  -- URL
        'POST',                                            -- Method
        '{"Content-Type":"application/json"}',             -- Request headers
        '{}',                                              -- Query parameters
        '1000'                                             -- Timeout (milliseconds)
      );

    The five parameters of supabase_functions.http_request are the URL, method, request headers, query parameters, and timeout (in milliseconds). The platform automatically generates request payload from the table records, as described in 1. You do not need to construct it manually.

Request Payload and Receiver

  1. Webhook Request Payload Structure

    The platform automatically generates the request payload from underlying table records. The fields vary slightly by event type.

    // INSERT request payload
    { type: 'INSERT', table: 'product_pricings', schema: 'public',
      record: { id, product_id, tier, price, ... }, old_record: null }
    
    // UPDATE request payload
    { type: 'UPDATE', table: 'product_pricings', schema: 'public',
      record: { ...new values }, old_record: { ...old values } }
    
    // DELETE request payload
    { type: 'DELETE', table: 'product_pricings', schema: 'public',
      record: null, old_record: { ...deleted row values } }
    • record: the row after the change. It contains a value for INSERT and UPDATE events and is null for DELETE events.
    • old_record: the row before the change. It contains a value for UPDATE and DELETE events and is null for INSERT events.
    • type, table, and schema: Identify the event type and source table, allowing the receiver to route the request accordingly.

    Webhooks use standard row-level triggers. For UPDATE and DELETE events, old_record is provided directly from the trigger's OLD record variable and requires no additional configuration. This differs from Realtime subscriptions, where setting REPLICA IDENTITY FULL is necessary to retrieve old values for UPDATE events. Realtime uses the logical replication channel, where the replica identity determines whether old values are included in change events. Webhooks use the trigger mechanism, where OLD and NEW are always available to row-level triggers.

  2. Implementing a Receiver (Node.js)

    Webhooks send outbound requests, so the external system must provide an endpoint to receive them. The following example uses Node.js (Express) to handle price change notifications for product_pricings. It compares old_record.price with record.price and triggers downstream marketing workflows.

    import express from 'express'
    const app = express()
    app.use(express.json())
    
    app.post('/api/price-change', (req, res) => {
      const { type, table, record, old_record } = req.body
    
      if (type === 'UPDATE') {
        const oldPrice = old_record?.price
        const newPrice = record?.price
        console.log('Product${record.product_id}Price change: ${oldPrice} → ${newPrice}')
        // Integrate with downstream marketing systems here, such as sending emails, adjusting advertising bids, or synchronizing with a CRM.
      } else if (type === 'INSERT') {
        console.log(`New pricing tier: product${record.product_id} ${record.tier}=${record.price}`)
      } else if (type === 'DELETE') {
        console.log(`Deleted pricing tier: product${old_record.product_id} ${old_record.tier}`)
      }
    
      res.status(200).json({ received: true })  // Return 200 promptly to prevent retries by the sender.
    })
    
    app.listen(3000, () => console.log('webhook receiver listening on port 3000'))
  3. Receiver Considerations
    • Respond promptly with a 2xx status code. Otherwise, the sender may retry the request. Return a 200 status code immediately, and then process time-consuming operations asynchronously.
    • The request payload has a fixed structure (see 1). Branch on type to handle insert, update, and delete events.
    • Security: Publicly accessible receivers should validate request sources (for example, by verifying a custom Authorization header or signature), to prevent forged requests. Because Webhook payloads contain actual business data, the receiving endpoint must enforce authentication.

Connectivity and Troubleshooting

Huawei Cloud RDS Supabase supports two authentication methods:

  • Connectivity Considerations

    RDS Supabase components (including the database) are deployed as containers on the cloud. Within a database instance, localhost and 127.0.0.1 refer to the instance itself, not to external services. Webhooks send outbound HTTP requests from the database through pg_net. The receiver must therefore be accessible from the database instance at a valid network address. Otherwise, requests may fail to be sent or time out.

    • Do not use loopback addresses: localhost and 127.0.0.1 refer to the database instance itself. Use a valid domain name or IP address for the receiver.
    • Receiver on the public network: Specify a public domain name or IP address (such as https://marketing.example.com/api/price-change). Ensure that outbound rules of the security group associated with the database instance allow traffic on the required port (443 for HTTPS or 80 for HTTP), and that an outbound path to the public network is available, such as through a NAT gateway. Otherwise, the public receiver will be unreachable.
    • Receiver in the same VPC: If the downstream system and RDS Supabase are in the same VPC and subnet, specify the receiver's private IP address directly. No public network egress is required.

    If a trigger cannot connect to a Webhook, check the following three points: whether the receiver address is a loopback address, whether outbound traffic is allowed, and whether the receiver is reachable over the public network.

  • Monitoring and Troubleshooting

    Webhook request and response records are stored in the net schema and can be queried in SQL Editor for troubleshooting.

    -- View recent responses (retained for 6 hours by default).
    SELECT id, status_code, content_type, timed_out, error_msg, created
    FROM net._http_response
    ORDER BY created DESC
    LIMIT 20;
    
    -- Query failed requests (status code ≥ 400 or an error occurred).
    SELECT id, status_code, error_msg, created
    FROM net._http_response
    WHERE status_code >= 400 OR error_msg IS NOT NULL
    ORDER BY created DESC;

    status_code is the HTTP status code returned by the receiver, error_msg indicates a network-layer error, and timed_out indicates whether the request timed out. Use these fields to determine whether the issue is caused by an error at the receiver or a network connectivity problem. If you need more control over the request payload, you can write a custom trigger function that calls net.http_post directly instead of using supabase_functions.http_request. This allows you to construct the request body yourself, but you lose the convenience of automatic payload generation by the platform.

    pg_net stores request and response data in unlogged tables, so the data is not preserved after a crash or an abnormal shutdown. Records are retained for only 6 hours by default, so troubleshoot issues promptly.

Case: Automatically Notifying a Downstream Marketing System of Product Price Changes

Example scenario: After an operations team member updates a price in product_pricings, the downstream marketing system needs to know immediately which product changed and its old and new prices, so it can adjust advertising bids and homepage recommendations. A Database Webhook sends price change events to the downstream system. The application only needs to perform the price update as usual and the outbound request is handled automatically in the background.

  1. Configure the Webhook to send requests to the downstream system.

    Create a Webhook on the console (or use the SQL statements in 2) to listen for UPDATE events on product_pricings and send a POST request to the downstream endpoint at https://marketing.example.com/api/price-change, with a timeout of 1,000 milliseconds. The platform automatically creates the underlying trigger, and the request payload includes old_record.price and record.price.

  2. Implement the receiver.

    Implement the receiver described in 2 in the downstream marketing system. It extracts the old and new prices to drive advertising adjustments and promptly returns 200.

  3. Trigger the outbound request through the SDK.

    The two steps above involve configuration on the database side and implementation of the downstream receiver. No application-side changes are required. When the operations team updates a price through the frontend using the SDK as usual, the Webhook automatically sends the outbound request in the background.

    // The operations team member updates a price in the frontend with a standard SDK update.
    const { error } = await supabase
      .from('product_pricings')
      .update({ price: 128.00 })
      .eq('id', 1)   // Pricing tier id=1
    if (error) { console.error(error); return }
    
    // Behind this update:
    // (1) The data is saved and the price changes from its old value to 128.00.
    // (2) The Webhook's underlying trigger sends a POST request to marketing.example.com with {type:'UPDATE', record:{...}, old_record:{...}}.
    // (3) The downstream receiver extracts the old and new prices, adjusts advertising bids, and returns 200.

    The application performs an ordinary .update() call, and the outbound notification is sent automatically. This is the value of Webhooks: They move outbound requests triggered by data changes into the database layer, so application code does not need to be aware of them or repeatedly orchestrate notification logic at every call site.

    To verify whether an outbound request was delivered, query net._http_response to check its status code and response. For details, see 2.

    If the price update also requires in-database operations (such as validating that the price is non-negative or refreshing the cached products.min_price value), you can configure a trigger alongside the Webhook. They serve different purposes and do not conflict: Triggers handle in-database operations, while Webhooks handle outbound requests.