# 数据库Webhook（Webhook）
#### 场景介绍
数据在数据库中变更之后，常需将这一变化第一时间告知外部系统：产品调价通知营销平台、新用户注册同步CRM、订单入库触发第三方发货流程。这类"变更即外呼"若交由应用在每次写数据后手动发起请求，既容易遗漏，也会令同一套外呼逻辑分散到各处，改一处牵动多处。数据库Webhook建立在触发器之上，表数据变更时即自动向一个外部地址发起HTTP请求（POST或GET，请求体为JSON），将变更作为载荷送出，无需应用参与。
Webhook与触发器同根同源，底层即一个触发器，仅将执行逻辑固定为"经pg_net插件（Extension扩展）异步发起HTTP请求"。因此配置时只需指明表、事件与外部地址，平台自动生成底层触发器并按表记录组装请求体，无需编写网络代码。pg_net异步外发，请求在事务提交后才真正发出，不阻塞写操作，适合放在触发器这类上下文中调用。它适合承接跨系统联动：
- 变更通知，如调价通知营销平台、新用户同步CRM。
- 触发外部工作流，如数据变更驱动发邮件、建工单等第三方流程。
- 跨系统同步，将变更实时推送至下游服务。
将外呼置于数据库而非应用层，是因为数据变更本身就是外呼的源头，在源头响应最为及时、不易遗漏。若诉求是库内联动，应使用[触发器](https://support.huaweicloud.com/usermanual-rds-pg/rds_supabase_25.html)；若诉求是前端界面随数据实时刷新，应使用[实时订阅（Realtime）](https://support.huaweicloud.com/usermanual-rds-pg/rds_supabase_29.html)，后者经WebSocket将变更推送给已连接的客户端，与Webhook一出站一入站、一主动外呼一被动推送的定位不同。
本章示例沿用[数据库API（Database Data API）](https://support.huaweicloud.com/usermanual-rds-pg/rds_supabase_23.html)中products（产品表）与product_pricings（产品定价表）两张表，可对照阅读。
#### 相关概念
- **数据库Webhook的定义**
  如引言所述，Webhook建立在触发器之上，表数据变更时即自动向一个外部HTTP地址发请求，把变更送达外部系统。其底层触发器调用supabase_functions模式下的http_request函数，后者经pg_net插件/扩展异步发出HTTP请求。所有事件均在行变更之后触发（即AFTER），因此Webhook只能事后通知，无法在写入前改写或取消变更，那是BEFORE触发器的职责，详见[数据库触发器（Trigger）](https://support.huaweicloud.com/usermanual-rds-pg/rds_supabase_25.html)。
  
- **Webhook与触发器的关系**
  Webhook本质是触发器的便捷封装，二者同根同源，差别只在执行逻辑固定与否。
  表1Webhook与触发器的关系 
  | 维度    | 触发器                     | 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）](https://support.huaweicloud.com/usermanual-rds-pg/rds_supabase_25.html)。 |
| Webhook  | 外部系统    | HTTP请求外发      | 否（仅外呼）  | 通知第三方、同步下游、触发外部工作流。                                                                                               |
| Realtime | 已连接的客户端 | WebSocket推送 | 否（仅推送）   | 前端实时刷新、协同编辑、在线状态，详见[实时订阅（Realtime）](https://support.huaweicloud.com/usermanual-rds-pg/rds_supabase_29.html)。     |
   
概括而言：库内联动用触发器、外呼外部用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管理界面。
     图1Supabase Dashboard Webhook管理页面   
     ![](https://support.huaweicloud.com/usermanual-rds-pg/zh-cn_image_0000002800045359.png "点击放大") 
  
  3. 单击"Create a new hook"，新建一个Webhook。
     图2Supabase Dashboard Webhook创建页面   
     ![](https://support.huaweicloud.com/usermanual-rds-pg/zh-cn_image_0000002770885444.png "点击放大")
     参考[表4]进行参数项配置。
      表4Webhook配置项 
     | 配置项    | 说明                   | 示例                                             |
     |:---|:---|:---|
     | 名称      | 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管外呼。
   
 
#### 参考文档
以下为Supabase开源相关文档，仅供参考。
- [Supabase Database Webhooks](https://supabase.com/docs/guides/database/webhooks)
- [pg_net：异步网络插件](https://supabase.com/docs/guides/database/extensions/pg_net)
- [pg_cron：定时任务](https://supabase.com/docs/guides/database/extensions/pg_cron)
- [Supabase Postgres Triggers](https://supabase.com/docs/guides/database/postgres/triggers)
- [Supabase数据库函数](https://supabase.com/docs/guides/database/functions)
 
