事件通知与验签规范 (Webhook Events)

HTTP POST 由平台服务端向开发者配置的回调 URL 推送 HMAC-SHA256 签名 阶梯指数退避重试 (最多 5 次)

当电子签署文档或参与人的生命周期状态发生变化(如文档创建发起、签署人完成签署、合同归档完成、过期或撤销)时,Dsign Link 服务端将以 HTTP POST 请求主动向企业在开放平台配置的 Webhook 回调地址推送实时 JSON 格式事件报文。

原始报文验签原则 (Raw Body)
计算和验证签名时,必须直接基于 HTTP 请求原始字节流 (Raw Body)。严禁使用经框架或中间件 JSON 反序列化后再重新序列化的字符串(例如 Node.js 的 JSON.stringify(req.body) 或 PHP 的 json_encode($data)),任何字段排列顺序、空格缩进或转义字符的微小差异均会导致验签失败。
基于 event_id 的消费幂等去重
因瞬时网络超时、调用方服务处理延迟等原因,平台会自动发起失败重试。所有重试通知均保证携带相同的 X-Webhook-Id(与报文中的 event_id 相同)和完全一致的事件报文。接收端在执行业务操作前,必须以 event_id 在数据库中建立唯一约束或原子排重,不能依赖投递次数。

Webhook HTTP 请求头 (Headers)

平台每次发起 Webhook 事件推送时,均会在 HTTP 请求头中携带以下专属通信与安全标头:

Header 名称 类型 是否必传 说明
Content-Type string 必传 固定为 application/json; charset=utf-8
X-Webhook-Id string 必传 事件的全局唯一标识(如 evt_01j7q3b9k4m1w2r5)。接收端必须以该 ID 作为唯一幂等键进行数据库去重。
X-Webhook-Timestamp string 必传 发送通知时的 Unix 毫秒时间戳(如 1789461000000)。建议接收方校验与本地时钟的偏差不超过 300 秒(5分钟),防止重放攻击。
X-Webhook-Signature string 必传 基于企业 Webhook 密钥(Signing Secret)与待签名字符串计算的 HMAC-SHA256 签名(Base64 编码)。
User-Agent string 平台标识 固定标识为 DsignLink-Webhook/1.0

签名计算与验签步骤 (Signature Verification)

接收端收到推送请求后,请按如下标准协议验证签名是否有效:

待签名载荷定义 (WebhookStringToSign)
WebhookStringToSign = X_WEBHOOK_TIMESTAMP + "." + RAW_HTTP_BODY
WebhookSignature    = Base64(HMAC-SHA256(WebhookSigningSecret, UTF8(WebhookStringToSign)))

标准验签执行步骤:

  1. 获取原始报文:从 Web 框架获取未经任何 JSON 解析或修改的原始请求体字节串 (Raw Body);
  2. 校验时间戳窗口:读取 X-Webhook-Timestamp,校验其为合法毫秒整数且与本地当前时间戳偏差在 300 秒以内;
  3. 构建待签载荷:将时间戳字符串与原始 Body 以英文点号 . 拼接;
  4. 计算期望签名:使用在管理后台配置的 Webhook Signing Secret 执行 HMAC-SHA256 运算并进行 Base64 编码;
  5. 安全常量时间比对:使用常量时间字符串比对函数(如 PHP 的 hash_equals、Python 的 hmac.compare_digest、Node.js 的 crypto.timingSafeEqual)与 X-Webhook-Signature 比较,杜绝时序侧信道攻击;
  6. 幂等入库处理:验签通过后,解析 JSON 内容并按 event_id 执行业务幂等落库,最后返回 HTTP 200。
契约自检用例 (Contract Test Vector)
SigningSecret: whsec_test_0123456789abcdef
Timestamp: 1789461000000
RawBody: {"event_id":"evt_01j7q3b9k4m1w2r5","event_type":"document.completed"}

[待签名拼接结果 WebhookStringToSign]
1789461000000.{"event_id":"evt_01j7q3b9k4m1w2r5","event_type":"document.completed"}

[预期计算签名 X-Webhook-Signature]
3pV2UJeXykSz2LGThmsfXcS51TygeCkhNz6ifptia/8=

多语言验签代码实现 (Verification Code)

您可以在各主流后端语言中参考以下实现范例集成 Webhook 验签:

function verifyWebhook(
    string $secret,
    string $timestampMs,
    string $rawBody,
    string $receivedSignature
): bool {
    // 1. 校验时间戳是否为正整数及偏差在 300 秒以内
    if (!ctype_digit($timestampMs)) {
        return false;
    }
    $nowMs = (int)(microtime(true) * 1000);
    if (abs($nowMs - (int)$timestampMs) > 300000) {
        return false;
    }

    // 2. 拼接待签字符串并使用 HMAC-SHA256 计算签名
    $stringToSign = $timestampMs . '.' . $rawBody;
    $expected = base64_encode(
        hash_hmac('sha256', $stringToSign, $secret, true)
    );

    // 3. 常量时间安全比对
    return hash_equals($expected, $receivedSignature);
}

支持的事件类型 (Event Types)

开放平台当前支持订阅的核心业务生命周期事件如下:

事件标识 (event_type) 触发时机 事件业务负载 (data) 说明
document.created 合同创建并发起签署 包含合同基础信息、状态 signing、当前待签署人列表及流转信息。
signer.completed 某一签署方完成签署 包含该完成签署人的身份标识、完成时间戳、当前合同整体进度及下一顺位待签方。
document.completed 全部签署方签署完毕 (归档) 所有签署方均已完成签署且合同最终归档件生成完毕,状态置为 completed。可凭此通知触发后续归档下载流程。
document.expired 合同已超过签署截止时间失效 签署流程到达截止时间但仍有签署人未完成签署,合同自动置为失效状态 expired
document.revoked 发起人主动撤销合同签署 发起人调用撤销接口终止了签署流程,包含撤销原因、撤销执行人及操作时间戳,合同状态置为 voided

统一事件报文格式 (Payload Structure)

所有事件推送通知均采用顶层统一的 JSON 报文格式包装:

Webhook JSON Payload 示例
{
  "event_id": "evt_01j7q3b9k4m1w2r5",
  "event_type": "document.completed",
  "event_time": "2026-09-16T12:00:00+08:00",
  "enterprise_id": "org_live_891048b94103",
  "data": {
    "document_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
    "title": "年度技术开发与服务框架协议",
    "status": "completed",
    "created_at": "2026-09-16T10:00:00+08:00",
    "completed_at": "2026-09-16T12:00:00+08:00"
  }
}
字段名 类型 说明
event_id string 全局唯一的事件标识符。平台每次尝试推送相同事件时均保持该 ID 不变,接收方必须用此字段做幂等去重。
event_type string 事件分类名称,如 document.completedsigner.completed 等。
event_time string 标准 ISO 8601 格式事件产生时间戳。
enterprise_id string 归属的企业标识 ID。
data object 具体的业务事件数据对象(包含文档 ID、状态、操作人等)。

投递机制与重试策略 (Delivery & Retry Policy)

  • 成功确认标准:开发者服务器收到通知后,只要返回任意 HTTP 2xx 状态码(推荐 200 OK),平台即判定此次投递成功。响应体 Body 内容不影响结果判定。
  • 失败判定条件:如果请求发生网络连接异常、连接超时(单次请求超时时间为 10 秒),或者接收端响应状态码为非 2xx(如 404、500、502、504 等),均被判定为投递失败。
  • 阶梯退避重试:投递失败后,系统将自动进入异步重试队列,共最多重试 5 次。重试间隔策略如下:
    • 第 1 次失败后:立即重试(防偶发网络抖动)
    • 第 2 次重试:5 分钟后
    • 第 3 次重试:30 分钟后
    • 第 4 次重试:2 小时后
    • 第 5 次重试:6 小时后
  • 安全防护与合规约束
    • 回调 URL 必须为公网可访问的 HTTPS 地址,保障数据链路传输安全;
    • 平台在投递请求前会对目标域名进行严格的 DNS 解析检测与 SSRF(服务端请求伪造)拦截,严禁回调私有私有局域网 IP(如 127.0.0.1、10.0.0.0/8、192.168.0.0/16 等)
    • 平台严禁跟随 HTTP 301/302 重定向,防止认证凭证被第三方劫持。