Webhooks
通过 HTTP 接收实时签名事件,并具有 HMAC 签名的交付、自动重试和从控制台重播安全网。
SHA-256 请求签名
Per-webhook秘密,作为X-Endorsed-Signature发送
投递超时
在窗口内返回2xx否则尝试失败
第一次尝试后重试
60s、5m、15m、1h、然后 6h
来自开发者控制台
检查每一次尝试并重播单个事件
您可以使用webhook事件做什么
每个重要的生命周期转换都会发出一个类型化事件。使用它们来更新您自己的记录而无需轮询。
触发下游动作
当信封完全签名后,更新您的 CRM、释放预配置的资源或在您自己的产品内发送确认信息 - 所有这些都无需轮询 API。
同步文档状态
使您的数据库与签名生命周期保持同步:发送、打开、签名、拒绝、过期、作废。每次转换一个事件,无需轮询循环。
审计和合规性日志记录
将webhook事件馈送到您自己的审核存储中。关联 ID 将每个事件链接回原始的 API 请求,以实现完全可追溯性。
重播失败的交付
耗尽重试阶梯的事件会进入死信队列,您可以在开发者控制台中检查并有意重播。
Webhook事件参考
注册一个webhook,选择您想要的事件,然后EndorsedSign提供包含`event`、`tenantId`、`timestamp`和`data`的JSON负载。事件名称与您在 webhook 创建调用中发送的字符串匹配。
| 事件类型 | 发射时 | 关键`数据`字段 |
|---|---|---|
| document.created | 您通过API上传文档 | 文档 ID、客户 ID、外部文档 ID |
| document.sent | 签名链接发送给第一个收件人 | 文档Id、客户Id、渠道 |
| document.viewed | 签名者打开签名链接 | documentId、signerEmail、viewedAt |
| document.signed | 个人签名者完成他们的步骤 | documentId、signerEmail、signedAt |
| document.completed | 各方均已签字 | documentId、completedAt、downloadUrl |
| document.declined | 签名者明确拒绝 | documentId、signerEmail、原因 |
| document.expired | 文档已到期且未签名 | 文档Id,过期时间 |
| signing.started | 签名会话令牌已兑换 | documentId、sessionId、signerEmail |
| signing.completed | 签名会话结束(签名或拒绝) | 文档Id、会话Id、结果 |
| 客户.创建/.更新/.删除 | 您更改了客户记录 | 客户 ID,外部客户 ID |
| 模板.创建/.更新 | 你改变了模板 | 模板Id,名称 |
完整列表位于后端`WEBHOOK_EVENTS`中。添加或缩小每个webhook的订阅 - 交付管道为每个注册端点分配一个 HTTP 调用。
验证webhook签名 (Node.js)
每个交付都包含一个 `X-Endorsed-Signature` 标头:`t={unix_seconds},v1={hex}`。 `v1` 是 ` 的 HMAC-SHA256{timestamp}.{rawBody}`使用创建webhook时返回的秘密。拒绝 ±5 分钟之外的时间戳。 “X-Webhook-Signature”仍可能作为已弃用的仅正文别名出现在开发人员门户测试交付中 - 验证“X-Endorsed-Signature”。先决条件:您的 webhook 秘密位于环境变量中,这是一条保留原始主体的 Express 路线。预期输出:在任何处理程序运行之前,无法识别或被篡改的有效负载将被拒绝并返回 401。
import crypto from 'node:crypto';
import express from 'express';
const WEBHOOK_SECRET = process.env.ENDORSED_WEBHOOK_SECRET!;
const TOLERANCE_SECONDS = 5 * 60;
export function verifyWebhookSignature(
rawBody: string,
header: string | undefined,
nowSeconds = Math.floor(Date.now() / 1000),
): boolean {
if (!header) return false;
let timestamp: number | null = null;
let v1 = '';
for (const part of header.split(',')) {
const eq = part.indexOf('=');
if (eq === -1) continue;
const key = part.slice(0, eq).trim();
const value = part.slice(eq + 1).trim();
if (key === 't' && /^\d+$/.test(value)) timestamp = Number.parseInt(value, 10);
if (key === 'v1' && /^[a-f0-9]{64}$/i.test(value)) v1 = value.toLowerCase();
}
if (timestamp === null || !v1) return false;
if (Math.abs(nowSeconds - timestamp) > TOLERANCE_SECONDS) return false;
const expected = crypto
.createHmac('sha256', WEBHOOK_SECRET)
.update(timestamp + '.' + rawBody)
.digest('hex');
const a = Buffer.from(expected, 'hex');
const b = Buffer.from(v1, 'hex');
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
app.post(
'/webhooks/endorsed',
express.json({ verify: (req, _res, buf) => { (req as any).rawBody = buf.toString('utf8'); } }),
(req, res) => {
const sig = req.header('X-Endorsed-Signature');
if (!verifyWebhookSignature((req as any).rawBody, sig)) {
return res.status(401).send('Invalid signature');
}
res.sendStatus(200);
},
);30秒内返回2xx,否则标记投递失败。失败的尝试使用上面的梯子重试,并且仍然可以从开发者控制台重播。
如何设置webhook端点
- 1
在您的应用程序中创建端点
添加一条接受 POST 请求并在 10 秒内返回 HTTP 200 的路由。任何 2xx 响应均视为成功交付。
- 2
在租户设置中注册端点
转到管理→设置→Webhook。输入您的端点 URL 并选择您想要的事件类型。每个端点都会生成一个签名密钥。
- 3
验证每个请求的签名
读取`X-Endorsed-Signature`。通过 ` 重新计算 HMAC-SHA256{timestamp}.{rawBody}` 和恒定时间与 `v1` 进行比较(参见上面的代码示例)。拒绝丢失的标头、错误的十六进制值、±5 分钟之外的时间戳或 HMAC 不匹配。 `X-Webhook-Signature` 是某些测试交付中已弃用的别名 - 不要将其视为实时合同。
- 4
快速返回2xx,处理异步
在 10 秒内确认传递 200,并将事件主体排入队列以进行异步处理。开发人员控制台显示每次尝试(标头、正文、状态、计时),并让您重放任何交付 - 无需重新触发源签名流程只是为了测试您的处理程序。
常见问题
真实的webhook有效负载 — 文档已完成
当所有各方都签署后,EndorsedSign将此有效负载发布到您注册的端点。验证 HMAC 签名、排队并使用 200 进行确认。
