@vafast/webhook
v0.1.9
Published
Webhook dispatch middleware for Vafast framework - automatically trigger webhooks based on route configuration
Maintainers
Readme
@vafast/webhook
Vafast 声明式 Webhook 分发中间件:路由声明 webhook,在 HTTP 2xx + JSON 响应后异步向订阅方推送事件。
完整文档:Webhook
先搞清几个概念
- eventKey:事件键。可显式配置,或由路径派生(可配合
pathPrefix)。 - Storage:查订阅(
findSubscriptions)+ 写日志(saveLog)。 - Dispatcher(可选):真正投递。不传则本地
fetchPOST;传入则走dispatcher.dispatch。 - 字段处理顺序:敏感字段 →
include→exclude→transform→ 追加clientIp/userAgent/timestamp。
安装
npm install @vafast/webhook快速开始
import { Server, defineRoute, defineRoutes, serve } from 'vafast'
import { webhook, defineWebhooks } from '@vafast/webhook'
const routes = defineRoutes([
defineRoute({
method: 'POST',
path: '/users',
name: '创建用户',
description: '注册成功后通知订阅方',
webhook: {
eventKey: 'user.created',
exclude: ['password'],
},
handler: ({ body }) => ({ id: '123', ...body }),
}),
])
const storage = defineWebhooks([
{
eventKey: 'user.created',
url: 'https://example.com/webhook',
secret: process.env.WEBHOOK_SECRET!,
},
])
const server = new Server(routes)
server.use(webhook({ storage }))
serve({ fetch: server.fetch, port: 3000 })简写:webhook: true 或 webhook: {}。
用法
路由配置
webhook: true
// 或
webhook: {
eventKey?: string
include?: string[] // 白名单
exclude?: string[] // 黑名单
condition?: (data) => boolean
transform?: (data, req) => Record<string, unknown>
}事件展示名 / 描述来自路由的 name / description,不是 webhook 对象内字段。
触发条件
- 响应
ok(2xx) Content-Type含application/json- 路由注册了
webhook isSuccess(data)为真(默认:JSON 对象体)condition(若配置)为真
分发在 setImmediate 中异步执行,不阻塞响应。
Storage vs Dispatcher
| 角色 | 职责 |
|------|------|
| Storage | findSubscriptions / saveLog |
| Dispatcher | 可选代发;不传则本地 fetch + HMAC |
defineWebhooks([...]) // 内存订阅(支持 auth.* 通配)
createWebhookStorage({...}) // MongoDB
createHttpStorage({...}) // HTTP 远端存储
createHttpDispatcher({...}) // HTTP 代发 /internal/dispatchEvent订阅字段(WebhookSubscription)
| 字段 | 说明 |
|------|------|
| id | 订阅 ID |
| appId | 可选;多租户匹配 |
| eventKey | 事件键(内存存储支持 xxx.*) |
| endpointUrl | 投递 URL |
| secret | 本地投递 HMAC-SHA256 用 |
| signSecret | 透传给 dispatcher / webhook-server |
| deliveryType | 如 generic / feishu / dingtalk / wecom / slack |
| status | enabled / disabled |
| sourceService | 来源服务(Mongo/HTTP storage 过滤用) |
| name / type | 展示 / 分类 |
HMAC
订阅有 secret 且走本地投递时:
X-Webhook-Signature: HMAC-SHA256(bodyString, secret) 的 hex重试(retry)
总尝试次数 = (count ?? 0) + 1。失败后等待 min(delay * backoff^i, maxDelay)。
| 字段 | 默认 | 说明 |
|------|------|------|
| count | 0 | 失败后再试次数 |
| delay | 1000 | 初始间隔 ms |
| backoff | 2 | 指数倍数 |
| maxDelay | 30000 | 间隔上限 ms |
手动分发
签名为 (storage, logger, options):
dispatchWebhook(storage, logger, {
appId,
eventKey: 'auth.oauth',
data: { userId, provider },
req,
})API
webhook(config) 主要参数
| 参数 | 默认 | 说明 |
|------|------|------|
| storage | — | 必填 |
| dispatcher | 本地 fetch | 可选 |
| logger | console | — |
| pathPrefix | '' | 生成 eventKey 时去掉的前缀 |
| sourceService | — | 中间件当前未读取;请在 storage 工厂上配置 |
| getAppId | 不传 | 多租户;默认不读 app-id |
| isSuccess / getData | 默认函数 | 成功判定 / 取载荷 |
| timeout | 30000 | 本地投递超时 ms |
| sensitiveFields | 见下表 | 始终剔除 |
| retry | 不重试 | 见上 |
| concurrency | 10 | 同事件并发上限 |
默认 sensitiveFields
password、token、jwtToken、refreshToken、secret、accessToken、apiKey
外发请求头
| Header | 说明 |
|--------|------|
| X-Webhook-Event | 事件键 |
| X-Webhook-Event-Id | 幂等 ID |
| X-Webhook-Timestamp | ISO 时间 |
| X-Webhook-Signature | 有 secret 时的 HMAC |
最佳实践
- 生产用持久化 Storage;开发可用
defineWebhooks。 - 订阅配
secret,接收方校验签名;用eventId做幂等。 - 微服务:
createHttpStorage+createHttpDispatcher。
注意事项
- 只处理 2xx + JSON;redirect / HTML / 非对象 JSON(默认)不自动触发。
getAppId默认不是读app-id。WebhookMiddlewareConfig.sourceService中间件未使用。- 分发失败不影响原 HTTP 响应。
dispatchWebhook不走路由级include/exclude/sensitiveFields。
相关链接
License
MIT
