数据库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)都由数据变化驱动,区别在“谁接收、怎么接收”。
| 机制 | 接收方 | 传递方式 | 是否入库 | 典型场景 |
|---|---|---|---|---|
| 触发器 | 数据库自身 | 库内自动执行SQL | 是(可写其它表) | 自动维护字段、审计、级联校验、派生计算,详见数据库触发器(Trigger)。 |
| Webhook | 外部系统 | HTTP请求外发 | 否(仅外呼) | 通知第三方、同步下游、触发外部工作流。 |
| Realtime | 已连接的客户端 | WebSocket推送 | 否(仅推送) | 前端实时刷新、协同编辑、在线状态,详见实时订阅(Realtime)。 |
概括而言:库内联动用触发器、外呼外部用Webhook、推前端用Realtime。Webhook与Realtime的关键差别在于:Webhook是“数据库主动找外部系统”(出站HTTP,外部系统需提供一个接收端点),Realtime是“客户端主动连上来等推送”(入站WebSocket);Webhook只发一次、不持久化等待,外部系统离线即漏,Realtime仅在线客户端能收。
何时该用Webhook、何时用其它机制,可按下表判断。
| 场景 | 选型 | 说明 |
|---|---|---|
| 变更即通知外部系统 | Webhook | 调价通知营销平台、新用户同步CRM。 |
| 触发外部工作流/第三方API | Webhook | 数据变更驱动外部流程(发邮件、建工单等)。 |
| 自动维护字段、审计、派生计算 | 触发器 | 库内联动,触发器(Trigger)承载此能力。 |
| 前端实时刷新 | Realtime | 实时订阅(Realtime)支持该功能场景。 |
| 定时周期性外呼 | pg_cron | 按cron定时调用pg_net,属定时任务范畴(非事件驱动)。 |
核心价值:数据变更即外呼、跨系统集成免轮询、配置即用免写网络代码。
工作原理与依赖
- 工作原理
Webhook的执行链路:表上发生INSERT/UPDATE/DELETE→底层触发器调用supabase_functions.http_request→该函数经pg_net插件异步发出HTTP请求(POST/GET,请求数据为JSON)→外部接收端点收到请求并处理。由于pg_net异步、请求在事务提交后才发出,Webhook不会长时间阻塞数据库写入。
- 依赖插件
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
- 登录RDS Supabase Dashboard控制台界面。
- 选择“Database > Webhooks”,进入Webhook管理界面。 图1 Supabase Dashboard Webhook管理页面
- 单击“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)
- 配置保存后,平台自动生成底层触发器,绑定到所选表的所选事件,调用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,无需手工拼接。
请求数据与接收端
- 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对行级触发器始终可用。
- 接收端编码(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')) - 接收端要点
- 接收端须及时返回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把调价事件外呼给下游,应用侧只管正常调价,外呼在背后自动完成。
- 配置Webhook外呼下游。
在控制台创建Webhook(或用2中的SQL),监听product_pricings的Update,POST到下游https://marketing.example.com/api/price-change,超时1000毫秒。平台自动生成底层触发器,请求数据含old_record.price与record.price。
- 实现接收端。
下游营销系统实现2中的接收端,取出新旧价格驱动投放调整,及时返回200。
- 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开源相关文档,仅供参考。