事件通知与验签规范 (Webhook Events)
当电子签署文档或参与人的生命周期状态发生变化(如文档创建发起、签署人完成签署、合同归档完成、过期或撤销)时,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)
标准验签执行步骤:
- 获取原始报文:从 Web 框架获取未经任何 JSON 解析或修改的原始请求体字节串 (Raw Body);
- 校验时间戳窗口:读取
X-Webhook-Timestamp,校验其为合法毫秒整数且与本地当前时间戳偏差在 300 秒以内; - 构建待签载荷:将时间戳字符串与原始 Body 以英文点号
.拼接; - 计算期望签名:使用在管理后台配置的 Webhook Signing Secret 执行 HMAC-SHA256 运算并进行 Base64 编码;
- 安全常量时间比对:使用常量时间字符串比对函数(如 PHP 的
hash_equals、Python 的hmac.compare_digest、Node.js 的crypto.timingSafeEqual)与X-Webhook-Signature比较,杜绝时序侧信道攻击; - 幂等入库处理:验签通过后,解析 JSON 内容并按
event_id执行业务幂等落库,最后返回 HTTP 200。
契约自检用例 (Contract Test Vector)
多语言验签代码实现 (Verification Code)
您可以在各主流后端语言中参考以下实现范例集成 Webhook 验签:
支持的事件类型 (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 | string | 全局唯一的事件标识符。平台每次尝试推送相同事件时均保持该 ID 不变,接收方必须用此字段做幂等去重。 |
| event_type | string | 事件分类名称,如 document.completed、signer.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 重定向,防止认证凭证被第三方劫持。