跳转到内容

Webhook

VentoStack 的 Webhook 系统由两个包组成:

  • @ventostack/webhook — 出站管理器 + 入站验证器 + 统一事件模型
  • @ventostack/integration — Stripe / GitHub / 钉钉 / Slack / Shopify / 微信支付 / 支付宝 验证器

所有 webhook(入站/出站)统一为 WebhookEvent

interface WebhookEvent {
id: string; // 唯一标识
type: string; // 事件类型 (e.g. "payment.completed")
source: string; // 来源 (provider 名 或 app 名)
payload: unknown; // 事件数据
metadata?: Record<string, unknown>;
timestamp: number; // 事件发生时间
}
import { createOutboundWebhookManager } from "@ventostack/webhook";
const outbound = createOutboundWebhookManager({
maxRetries: 5, // 最大重试次数(默认 5)
retryInterval: 1000, // 初始重试间隔 ms(默认 1000)
maxRetryInterval: 60000, // 最大重试间隔 ms(默认 60000)
deliveryTimeout: 10000, // 投递超时 ms(默认 10000)
signatureAlgorithm: "sha256", // sha256 | sha384 | sha512
signatureHeader: "X-Webhook-Signature",
});
// 注册订阅
const subId = outbound.subscribe({
url: "https://partner.example.com/callback",
secret: "whsec_...",
events: ["order.created", "order.paid"], // 空数组 [] = 订阅所有事件
active: true,
headers: { "Authorization": "Bearer token" },
});
// 查询/移除
outbound.getSubscription(subId);
outbound.listSubscriptions({ event: "order.created", active: true });
outbound.unsubscribe(subId);
// 直接发送
const deliveryIds = await outbound.send("order.created", { orderId: "123" });
// 通过统一事件模型发送(与 EventBus 集成)
await outbound.sendEvent({
id: crypto.randomUUID(),
type: "order.created",
source: "my-app",
payload: { orderId: "123" },
timestamp: Date.now(),
});
outbound.getDelivery(deliveryId);
outbound.listDeliveries({ status: "failed" });
outbound.listDeliveries({ event: "order.created" });
await outbound.retry(); // 重试所有失败投递
outbound.stats(); // { subscriptions, pending, sent, failed, dead }
outbound.sign(payload, secret); // HMAC 签名工具

通用 HMAC(不依赖 integration 包)

Section titled “通用 HMAC(不依赖 integration 包)”
import { createInboundVerifier, createGenericHmacVerifier } from "@ventostack/webhook";
const verifier = createInboundVerifier({
provider: createGenericHmacVerifier("sha256", "x-signature"),
config: { secret: "my-secret" },
});
const event = await verifier.verifyAndConvert(rawBody, headers);
// → WebhookEvent { id, type: "webhook.received", source, payload, timestamp }
import { createInboundVerifier } from "@ventostack/webhook";
import { createStripeVerifier } from "@ventostack/integration";
const verifier = createInboundVerifier({
provider: createStripeVerifier(),
config: { secret: "whsec_..." },
});
// 在 HTTP handler 中使用
app.post("/webhooks/stripe", async (ctx) => {
const body = await ctx.request.text();
const headers = Object.fromEntries(ctx.request.headers.entries());
const event = await verifier.verifyAndConvert(body, headers);
// event.payload → Stripe 事件数据
// event.type → "webhook.received" (或 provider 提供的事件类型)
});
平台导入算法特殊说明
StripecreateStripeVerifier()HMAC-SHA256时间戳 + 密钥轮换,5 分钟容差
GitHubcreateGitHubVerifier()HMAC-SHA256提取 X-GitHub-Event 事件类型
钉钉createDingTalkVerifier()HMAC-SHA256签名在 URL query,通过 config.extra 传入
SlackcreateSlackVerifier()HMAC-SHA256v0: 前缀,5 分钟容差
ShopifycreateShopifyVerifier()HMAC-SHA256Base64 编码
微信支付createWeChatPayVerifier()RSA-SHA256非对称验签,需平台证书公钥
支付宝createAlipayVerifier()RSA2非对称验签,body params 通过 config.extra.bodyParams 传入

钉钉(签名在 URL query params):

createInboundVerifier({
provider: createDingTalkVerifier(),
config: {
secret: "SEC...",
extra: { timestamp: "1700000000000", sign: "encoded_sign_here" },
},
});

微信支付(支持多证书):

createInboundVerifier({
provider: createWeChatPayVerifier(),
config: {
publicKey: "-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----",
// 多证书场景用 certificates 映射
extra: {
certificates: {
"serial-001": "-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----",
},
},
},
});

支付宝(签名在 body params):

createInboundVerifier({
provider: createAlipayVerifier(),
config: {
publicKey: "-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----",
extra: { bodyParams: parsedBodyParams },
},
});

出站管理器通过 sendEvent(WebhookEvent) 与 EventBus 无缝衔接:

import { createEventBus, defineEvent } from "@ventostack/events";
import { createOutboundWebhookManager } from "@ventostack/webhook";
const bus = createEventBus();
const outbound = createOutboundWebhookManager();
outbound.subscribe({ url: "...", secret: "...", events: ["order.created"], active: true });
const OrderCreated = defineEvent<{ orderId: string }>("order.created");
bus.on(OrderCreated, async (payload) => {
await outbound.sendEvent({
id: crypto.randomUUID(),
type: "order.created",
source: "my-app",
payload,
timestamp: Date.now(),
});
});