开发者

Webhooks

通过 HTTP 接收实时签名事件,并具有 HMAC 签名的交付、自动重试和从控制台重播安全网。

HMAC

SHA-256 请求签名

Per-webhook秘密,作为X-Endorsed-Signature发送

10s

投递超时

在窗口内返回2xx否则尝试失败

5×

第一次尝试后重试

60s、5m、15m、1h、然后 6h

Replay

来自开发者控制台

检查每一次尝试并重播单个事件

您可以使用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。

typescript
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. 1

    在您的应用程序中创建端点

    添加一条接受 POST 请求并在 10 秒内返回 HTTP 200 的路由。任何 2xx 响应均视为成功交付。

  2. 2

    在租户设置中注册端点

    转到管理→设置→Webhook。输入您的端点 URL 并选择您想要的事件类型。每个端点都会生成一个签名密钥。

  3. 3

    验证每个请求的签名

    读取`X-Endorsed-Signature`。通过 ` 重新计算 HMAC-SHA256{timestamp}.{rawBody}` 和恒定时间与 `v1` 进行比较(参见上面的代码示例)。拒绝丢失的标头、错误的十六进制值、±5 分钟之外的时间戳或 HMAC 不匹配。 `X-Webhook-Signature` 是某些测试交付中已弃用的别名 - 不要将其视为实时合同。

  4. 4

    快速返回2xx,处理异步

    在 10 秒内确认传递 200,并将事件主体排入队列以进行异步处理。开发人员控制台显示每次尝试(标头、正文、状态、计时),并让您重放任何交付 - 无需重新触发源签名流程只是为了测试您的处理程序。

常见问题

真实的webhook有效负载 — 文档已完成

当所有各方都签署后,EndorsedSign将此有效负载发布到您注册的端点。验证 HMAC 签名、排队并使用 200 进行确认。

包含文档生命周期条目的交付事件历史记录
交付事件历史记录 - 带有时间戳和状态结果的签名生命周期事件,用于操作验证。

连接你的第一个webhook

声明一个沙箱,在管理 → 开发者 → Webhooks 中注册端点,并从开发者控制台检查或重放每个交付。