更新时间:2026-10-21 GMT+08:00
分享

数据库Webhook(Webhook)

场景介绍

数据在数据库中变更之后,常需将这一变化第一时间告知外部系统:产品调价通知营销平台、新用户注册同步CRM、订单入库触发第三方发货流程。这类"变更即外呼"若交由应用在每次写数据后手动发起请求,既容易遗漏,也会令同一套外呼逻辑分散到各处,改一处牵动多处。数据库Webhook建立在触发器之上,表数据变更时即自动向一个外部地址发起HTTP请求(POST或GET,请求体为JSON),将变更作为载荷送出,无需应用参与。

Webhook与触发器同根同源,底层即一个触发器,仅将执行逻辑固定为"经pg_net插件(Extension扩展)异步发起HTTP请求"。因此配置时只需指明表、事件与外部地址,平台自动生成底层触发器并按表记录组装请求体,无需编写网络代码。pg_net异步外发,请求在事务提交后才真正发出,不阻塞写操作,适合放在触发器这类上下文中调用。它适合承接跨系统联动:

  • 变更通知,如调价通知营销平台、新用户同步CRM。
  • 触发外部工作流,如数据变更驱动发邮件、建工单等第三方流程。
  • 跨系统同步,将变更实时推送至下游服务。

将外呼置于数据库而非应用层,是因为数据变更本身就是外呼的源头,在源头响应最为及时、不易遗漏。若诉求是库内联动,应使用触发器;若诉求是前端界面随数据实时刷新,应使用实时订阅(Realtime),后者经WebSocket将变更推送给已连接的客户端,与Webhook一出站一入站、一主动外呼一被动推送的定位不同。

本章示例沿用数据库API(Database Data API)中products(产品表)与product_pricings(产品定价表)两张表,可对照阅读。

相关概念

  • 数据库Webhook的定义

    如引言所述,Webhook建立在触发器之上,表数据变更时即自动向一个外部HTTP地址发请求,把变更送达外部系统。其底层触发器调用supabase_functions模式下的http_request函数,后者经pg_net插件/扩展异步发出HTTP请求。所有事件均在行变更之后触发(即AFTER),因此Webhook只能事后通知,无法在写入前改写或取消变更,那是BEFORE触发器的职责,详见数据库触发器(Trigger)。

  • Webhook与触发器的关系

    Webhook本质是触发器的便捷封装,二者同根同源,差别只在执行逻辑固定与否。

    表1 Webhook与触发器的关系

    维度

    触发器

    Webhook

    本质

    数据库事件驱动机制。

    触发器的便捷封装(调用pg_net外发HTTP)。

    执行逻辑

    库内SQL(任意逻辑)。

    固定为“向外部地址发HTTP请求”。

    载荷

    自行在函数内构造。

    由平台按表记录自动生成(含type/table/record/old_record)。

    是否阻塞写入

    同步执行,复杂逻辑可能延长写事务。

    异步外发(pg_net),不长时间阻塞写入。

    配置方式

    手写触发器函数与CREATE TRIGGER。

    控制台填表或一行SQL,平台自动生成触发器。

    pg_net是异步网络扩展/插件:HTTP请求在事务提交后才真正发出,不阻塞数据库变更。这使Webhook适合在触发器这类"阻塞上下文"中调用,但响应数据默认仅留存6小时,且仅支持JSON格式的POST/GET,不支持PUT/PATCH。

功能场景

Webhook把变更外呼给外部系统,落点在跨系统联动。它和触发器(Trigger)、实时订阅(Realtime)都由数据变化驱动,区别在“谁接收、怎么接收”。

表2 数据变更三类机制对比

机制

接收方

传递方式

是否入库

典型场景

触发器

数据库自身

库内自动执行SQL

是(可写其它表)

自动维护字段、审计、级联校验、派生计算,详见数据库触发器(Trigger)。

Webhook

外部系统

HTTP请求外发

否(仅外呼)

通知第三方、同步下游、触发外部工作流。

Realtime

已连接的客户端

WebSocket推送

否(仅推送)

前端实时刷新、协同编辑、在线状态,详见实时订阅(Realtime)。

概括而言:库内联动用触发器、外呼外部用Webhook、推前端用Realtime。Webhook与Realtime的关键差别在于:Webhook是“数据库主动找外部系统”(出站HTTP,外部系统需提供一个接收端点),Realtime是“客户端主动连上来等推送”(入站WebSocket);Webhook只发一次、不持久化等待,外部系统离线即漏,Realtime仅在线客户端能收。

何时该用Webhook、何时用其它机制,可按下表判断。

表3 场景与选型

场景

选型

说明

变更即通知外部系统

Webhook

调价通知营销平台、新用户同步CRM。

触发外部工作流/第三方API

Webhook

数据变更驱动外部流程(发邮件、建工单等)。

自动维护字段、审计、派生计算

触发器

库内联动,触发器(Trigger)承载此能力。

前端实时刷新

Realtime

实时订阅(Realtime)支持该功能场景。

定时周期性外呼

pg_cron

按cron定时调用pg_net,属定时任务范畴(非事件驱动)。

核心价值:数据变更即外呼、跨系统集成免轮询、配置即用免写网络代码。

工作原理与依赖

  1. 工作原理

    Webhook的执行链路:表上发生INSERT/UPDATE/DELETE→底层触发器调用supabase_functions.http_request→该函数经pg_net插件异步发出HTTP请求(POST/GET,请求数据为JSON)→外部接收端点收到请求并处理。由于pg_net异步、请求在事务提交后才发出,Webhook不会长时间阻塞数据库写入。

  2. 依赖插件

    Webhook经pg_net插件异步外发HTTP,该插件是Webhook的核心依赖。RDS Supabase应用创建时,平台自动在托管数据库实例上完成pg_net安装,无需用户手动创建或启用,可直接配置Webhook。

    安装细节(了解即可,用户无需操作):

    • pg_net注册于extensions模式而非public,避免暴露在公开模式、满足安全检查。
    • 插件自建net模式存放其对象,Webhook的请求与响应记录在net._http_response,可在SQL Editor查询排查。

配置Webhook

Webhook把“数据变更即外呼”做到配置即用:在控制台配置表、事件与外部地址,平台自动生成底层触发器,无需手写网络代码。也可用SQL直接创建等价的触发器,获得更细的控制。

  • 方式一:通过Dashboard创建Webhook
    1. 登录RDS Supabase Dashboard控制台界面。
    2. 选择“Database > Webhooks”,进入Webhook管理界面。
      图1 Supabase Dashboard Webhook管理页面
    3. 单击“Create a new hook”,新建一个Webhook。
      图2 Supabase Dashboard Webhook创建页面

      参考表4进行参数项配置。

      表4 Webhook配置项

      配置项

      说明

      示例

      名称

      Webhook名,同时也是底层触发器名。

      wh_price_change_notify

      表

      监听哪张表。

      product_pricings

      事件

      触发事件,可多选。

      Insert/Update/Delete

      URL

      外部接收端点地址。

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

      方法

      HTTP方法。

      POST或GET

      Headers

      请求头(键值对)。

      Content-Type: application/json

      参数

      查询参数(GET附加到URL)。

      按需

      超时

      请求超时(毫秒)。

      1000(默认2000)

    4. 配置保存后,平台自动生成底层触发器,绑定到所选表的所选事件,调用supabase_functions.http_request发出请求。无需手写任何网络代码。
  • 方式二:通过SQL创建Webhook

    也可用SQL直接创建等价触发器,与Dashboard生成的效果一致,便于纳入迁移脚本或定制参数。

    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',                                            -- 方法
        '{"Content-Type":"application/json"}',             -- 请求头
        '{}',                                              -- 查询参数
        '1000'                                             -- 超时(毫秒)
      );

    supabase_functions.http_request的五个参数依次为URL、方法、请求头、查询参数、超时(毫秒)。请求数据由平台按表记录自动生成,详见1,无需手工拼接。

请求数据与接收端

  1. Webhook请求数据结构

    请求数据由平台按底层表记录自动生成,不同事件的字段略有差异。

    // INSERT请求数据
    { type: 'INSERT', table: 'product_pricings', schema: 'public',
      record: { id, product_id, tier, price, ... }, old_record: null }
    
    // UPDATE请求数据
    { type: 'UPDATE', table: 'product_pricings', schema: 'public',
      record: { ...新值 }, old_record: { ...旧值 } }
    
    // DELETE请求数据
    { type: 'DELETE', table: 'product_pricings', schema: 'public',
      record: null, old_record: { ...被删行的值 } }
    • record:变更后的行(INSERT/UPDATE有值,DELETE为null)。
    • old_record:变更前的行(UPDATE/DELETE有值,INSERT为null)。
    • type/table/schema:标明事件类型与来源表,接收端据此分发处理。

    Webhook底层是常规行级触发器,UPDATE/DELETE的old_record由触发器的OLD记录变量直接提供,无需额外配置。这与实时订阅中监听UPDATE取旧值须设REPLICA IDENTITY FULL不同:后者走逻辑复制通道,副本标识决定旧值是否随变更发出;Webhook走触发器通道,OLD/NEW对行级触发器始终可用。

  2. 接收端编码(Node.js)

    Webhook是出站请求,需外部系统提供一个接收端点。以下用Node.js(Express)实现一个接收端,处理product_pricings的价格变动通知,取出old_record.price与record.price对比,触发下游营销流程。

    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(`产品${record.product_id}调价: ${oldPrice} → ${newPrice}`)
        // 此处接入下游营销系统:发邮件、改投放出价、同步CRM等
      } else if (type === 'INSERT') {
        console.log(`新增定价档位: 产品${record.product_id} ${record.tier}=${record.price}`)
      } else if (type === 'DELETE') {
        console.log(`删除定价档位: 产品${old_record.product_id} ${old_record.tier}`)
      }
    
      res.status(200).json({ received: true })  // 及时回200,避免发送方重试
    })
    
    app.listen(3000, () => console.log('webhook接收端监听3000'))
  3. 接收端要点
    • 接收端须及时返回2xx,否则发送方可能重试;耗时操作异步化,先回200再处理。
    • 请求数据结构固定(详见1),按type分支处理增/改/删。
    • 安全:公网接收端应校验来源(如约定Authorization头或签名),防伪造;Webhook请求数据含真实业务数据,接收端点须鉴权。

连通性与调试

华为云RDS Supabase支持以下两种认证方式:

  • 连通性注意事项

    RDS Supabase各组件(含数据库)以容器形式部署在云端,数据库实例内的localhost、127.0.0.1指向实例自身而非外部服务。Webhook由数据库经pg_net外发HTTP,接收端地址须为数据库实例网络可达的真实地址,否则请求发不出或超时:

    • 勿用回环地址:localhost、127.0.0.1指向数据库实例自身,接收端须用真实域名或IP。
    • 接收端在公网:填公网域名或IP(如https://marketing.example.com/api/price-change),并确保数据库实例所属安全组的出方向规则放通对应端口(HTTPS为443、HTTP为80),且具备至公网的出口路径(如经NAT网关),否则公网接收端不可达。
    • 接收端在同VPC:若下游系统与RDS Supabase同VPC同子网,直接填其内网地址即可,无需公网出口。

    连不上Webhook时,据此三项排查:接收端地址是否回环、出方向是否放通、公网接收端是否可达。

  • 监控与调试

    Webhook的请求与响应记录在net模式下,可在SQL Editor中查询排查:

    -- 查看最近的响应(默认留存6小时)
    SELECT id, status_code, content_type, timed_out, error_msg, created
    FROM net._http_response
    ORDER BY created DESC
    LIMIT 20;
    
    -- 查失败的请求(状态码>=400或出错)
    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为接收端返回的HTTP状态码、error_msg为网络层错误、timed_out标识是否超时。据此定位是接收端报错还是网络不通。若需更细的自定义请求数据,可不用supabase_functions.http_request而自建触发器函数直接调net.http_post,自行构造请求体,但失去平台自动生成请求数据的便利。

    pg_net的请求与响应存于unlogged表,崩溃或非正常关机不保留;默认仅留存6小时,排查宜及时。

综合案例:产品价格变动自动通知下游营销系统

示例场景:运营小张在product_pricings调整价格后,下游营销系统需第一时间获知“哪个产品、从多少钱调到多少钱”,据此调整投放出价与首页推荐。用数据库Webhook把调价事件外呼给下游,应用侧只管正常调价,外呼在背后自动完成。

  1. 配置Webhook外呼下游。

    在控制台创建Webhook(或用2中的SQL),监听product_pricings的Update,POST到下游https://marketing.example.com/api/price-change,超时1000毫秒。平台自动生成底层触发器,请求数据含old_record.price与record.price。

  2. 实现接收端。

    下游营销系统实现2中的接收端,取出新旧价格驱动投放调整,及时返回200。

  3. SDK触发外呼。

    以上两步一处是数据库侧配置、一处是下游接收端,应用侧无需改动。运营在前端用SDK正常调价,Webhook在背后自动外呼:

    // 运营小张在前端调价,一次普通的SDK update
    const { error } = await supabase
      .from('product_pricings')
      .update({ price: 128.00 })
      .eq('id', 1)   // 定价档位id=1
    if (error) { console.error(error); return }
    
    // 这次update背后:
    // 1) 数据落库,price由旧值变为128.00
    // 2) Webhook底层触发器向marketing.example.com POST {type:'UPDATE', record:{...}, old_record:{...}}
    // 3) 下游接收端取新旧价格,调整投放,回200

    应用侧只是一次寻常的.update(),外呼通知全自动完成。这正是Webhook的价值:把"数据变更后的外呼"下沉到数据库,应用代码不必感知,也不必为每个调用点重复编排通知逻辑。

    想确认外呼是否送达,查net._http_response即可看到这条请求的状态码与响应,详见2。

    若调价还需库内联动(如校验价格非负、刷新products.min_price缓存),可同时配上触发器,与Webhook各司其职、互不冲突:触发器管库内,Webhook管外呼。

相关文档