回调概览
在控制台 AppId 设置页配置 CallbackUrl 后,信封状态发生变更时,服务端将向 CallbackUrl 发送 POST 请求。
> ⚠️ 注意:回调均为 POST 请求。若收到 GET 请求,请确认 CallbackUrl 是否存在 HTTP 到 HTTPS 的 301/302 跳转。
配置步骤
- 1. 登录控制台,进入「应用管理」→ 选择目标 AppId
- 2. 在「回调配置」区域填写 CallbackUrl(须为公网可达的 HTTPS 地址)
- 3. 保存后系统自动生成 32 字符的 CallbackKey(Base62 编码),用于加解密回调数据
- 4. 妥善保管 CallbackKey,遗失后需重新生成(旧 Key 立即失效)
⚠️CallbackUrl 必须为 HTTPS 协议
CallbackUrl 必须为公网可达地址(不允许内网 IP、localhost)
每个 AppId 可配置多个 CallbackUrl,各自拥有独立的 CallbackKey
不同 CallbackUrl 使用各自独立的 CallbackKey,互不影响
CallbackUrl 必须为公网可达地址(不允许内网 IP、localhost)
每个 AppId 可配置多个 CallbackUrl,各自拥有独立的 CallbackKey
不同 CallbackUrl 使用各自独立的 CallbackKey,互不影响
数据格式与加密
数据格式与加密
{
"encrypt": "dGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIGV4YW1wbGU..."
}AES-256-GCM
密钥
32 字节(256 位)
Nonce
12 字节(96 位,随机生成)
密文格式
Base64( nonce[12字节] + GCM_ciphertext + GCM_tag[16字节] )
解密流程
- 1. 从请求 Body 中取出 encrypt 字段值
- 2. 对该值进行 Base64 标准解码,得到 raw bytes
- 3. 前 12 字节为 nonce,剩余部分为 GCM ciphertext(含 16 字节 auth tag)
- 4. 使用 CallbackKey(32 字节,UTF-8 编码)构造 AES-256-GCM 解密器
- 5. 调用 GCM.Open(nonce, ciphertext) 解密得到原始 JSON 明文
- 6. 将 JSON 反序列化为 EnvelopeCallbackPayload 对象
投递策略
HTTP 超时
5 秒(HTTP 请求超时)
成功条件
客户端在 5 秒内返回 HTTP 200 状态码
最大重试
36
重试间隔
1 秒、2 秒、3 秒、4 秒、5 秒 → 10 秒、15 秒、20 秒、25 秒、30 秒、35 秒、40 秒、45 秒、50 秒、55 秒 → 1 分、2 分、3 分、4 分、5 分、6 分、7 分、8 分、9 分、10 分 → 15 分、25 分、35 分、45 分、55 分 → 1 时、2 时、3 时、4 时、5 时、6 时
顺序保证
同一信封 + 同一集成的回调按事件发生时间顺序投递,确保事件顺序一致性
ℹ️重试间隔随次数增加。一旦收到 HTTP 200 响应,平台不再重发该条回调。36 次仍失败则平台视为此消息无法投递并丢弃。
事件类型
envelope_sentevent
信封已发送,签署流程已启动 — 顺序签署模式下,仅首批签署人收到通知;并行签署模式下,所有签署人同时收到通知。
envelope_completedevent
信封已完成,所有签署人均已签署 — 当最后一位签署人完成签署时触发。此时可通过「查询信封详情」接口获取已签署文档。
envelope_voidedevent
信封已作废(主动撤回) — 发件人在控制台或通过 API 主动撤回信封时触发。
envelope_declinedevent
信封已拒签,任一签署人拒签即触发 — 当签署人拒签时触发,信封状态变为 declined。Recipients 中包含拒签的签署人信息及拒签原因。
envelope_expiredevent
信封已过期,截止时间到期仍有签署人未完成签署 — 当设置的签署截止时间到期且仍有签署人未完成签署时自动触发。信封状态变为 voided(自动作废),Recipients 中包含所有未完成签署的签署人。
recipient_signedevent
某位签署人完成签署 — 签署人完成所有签署控件的填写并提交后触发。信封仍在签署中时 CurrentEnvelopeStatus 为 sent。
recipient_viewedevent
某位接收人查看了信封 — 签署人或抄送人首次打开信封查看时触发。同一接收人多次查看只会触发一次(首次查看时)。
Payload 结构
EnvelopeCallbackPayload
EventTypestring
事件类型。envelope_sent=信封已发送,签署流程已启动(顺序签署时仅首批签署人收到通知);envelope_completed=信封已完成(所有签署人均已签署);envelope_voided=信封已作废(主动撤回);envelope_declined=信封已拒签(任一签署人拒签即触发),Recipients 包含该签署人信息;envelope_expired=信封已过期(截止时间到期,仍有签署人未完成签署,信封自动作废,CurrentEnvelopeStatus=voided),Recipients 包含所有未完成的签署人;recipient_signed=某位签署人完成签署;recipient_viewed=某位接收人(签署人或抄送人)查看了信封
envelope_sentenvelope_completedenvelope_voidedenvelope_declinedenvelope_expiredrecipient_signedrecipient_viewed
EnvelopeIdstring
信封 ID
OccurredAtstring
信封状态发生变更的时间,RFC3339 格式,示例:2024-01-15T10:45:00Z
PreviousEnvelopeStatusstring
状态变更前的信封状态。sent=签署中(含已发送/已投递/已查看/部分签署)
draftsentcompletedvoideddeclined
CurrentEnvelopeStatusstring
状态变更后的信封状态。sent=签署中(含已发送/已投递/已查看/部分签署)
draftsentcompletedvoideddeclined
VoidReasonstring
作废原因(envelope_voided / envelope_expired 事件有值)
Recipientsarray<object>
涉及本次事件的接收人列表。recipient_* 事件包含单个接收人;envelope_expired 包含所有未完成签署的签署人;其余 envelope_* 事件为空
RecipientIdstring
接收人 ID
Namestring
接收人姓名
Emailstring
接收人邮箱(脱敏,如 z***@example.com)
RecipientTypestring
接收人类型。signer=签署人;carbonCopy=抄送人
signercarbonCopy
SigningOrderinteger
签署顺序(未设置顺序签署时为 0)
PreviousStatusstring
状态变更前的接收人状态
CurrentStatusstring
状态变更后的接收人状态
OperatedAtstring
操作时间,RFC3339 格式,示例:2024-01-15T11:20:00Z
DeclineReasonstring
拒签原因(仅 envelope_declined 事件有值)
Payload 示例
envelope_sent — 信封已发送,签署流程已启动
{
"EventType": "envelope_sent",
"EnvelopeId": "SGEVa1b2c3d4e5f6",
"OccurredAt": "2024-01-15T10:30:00Z",
"PreviousEnvelopeStatus": "draft",
"CurrentEnvelopeStatus": "sent",
"VoidReason": "",
"Recipients": []
}envelope_completed — 信封已完成,所有签署人均已签署
{
"EventType": "envelope_completed",
"EnvelopeId": "SGEVa1b2c3d4e5f6",
"OccurredAt": "2024-01-15T14:00:00Z",
"PreviousEnvelopeStatus": "sent",
"CurrentEnvelopeStatus": "completed",
"VoidReason": "",
"Recipients": []
}envelope_voided — 信封已作废(主动撤回)
{
"EventType": "envelope_voided",
"EnvelopeId": "SGEVa1b2c3d4e5f6",
"OccurredAt": "2024-01-16T09:00:00Z",
"PreviousEnvelopeStatus": "sent",
"CurrentEnvelopeStatus": "voided",
"VoidReason": "合同条款需要修改",
"Recipients": []
}envelope_declined — 信封已拒签,任一签署人拒签即触发
{
"EventType": "envelope_declined",
"EnvelopeId": "SGEVa1b2c3d4e5f6",
"OccurredAt": "2024-01-15T15:30:00Z",
"PreviousEnvelopeStatus": "sent",
"CurrentEnvelopeStatus": "declined",
"VoidReason": "",
"Recipients": [
{
"RecipientId": "SGRCb1c2d3e4f5",
"Name": "李四",
"Email": "l***@example.com",
"SigningOrder": 2,
"PreviousStatus": "viewed",
"CurrentStatus": "declined",
"OperatedAt": "2024-01-15T15:30:00Z",
"DeclineReason": "合同金额有误,需要修改"
}
]
}envelope_expired — 信封已过期,截止时间到期仍有签署人未完成签署
{
"EventType": "envelope_expired",
"EnvelopeId": "SGEVa1b2c3d4e5f6",
"OccurredAt": "2024-02-15T23:59:59Z",
"PreviousEnvelopeStatus": "sent",
"CurrentEnvelopeStatus": "voided",
"VoidReason": "签署截止时间已到期",
"Recipients": [
{
"RecipientId": "SGRCc2d3e4f5g6",
"Name": "王五",
"Email": "w***@example.com",
"SigningOrder": 2,
"PreviousStatus": "sent",
"CurrentStatus": "voided",
"OperatedAt": "2024-02-15T23:59:59Z",
"DeclineReason": ""
}
]
}recipient_signed — 某位签署人完成签署
{
"EventType": "recipient_signed",
"EnvelopeId": "SGEVa1b2c3d4e5f6",
"OccurredAt": "2024-01-15T10:45:00Z",
"PreviousEnvelopeStatus": "sent",
"CurrentEnvelopeStatus": "sent",
"VoidReason": "",
"Recipients": [
{
"RecipientId": "SGRCb1c2d3e4f5",
"Name": "张三",
"Email": "z***@example.com",
"SigningOrder": 1,
"PreviousStatus": "viewed",
"CurrentStatus": "signed",
"OperatedAt": "2024-01-15T10:45:00Z",
"DeclineReason": ""
}
]
}recipient_viewed — 某位接收人查看了信封
{
"EventType": "recipient_viewed",
"EnvelopeId": "SGEVa1b2c3d4e5f6",
"OccurredAt": "2024-01-15T10:35:00Z",
"PreviousEnvelopeStatus": "sent",
"CurrentEnvelopeStatus": "sent",
"VoidReason": "",
"Recipients": [
{
"RecipientId": "SGRCb1c2d3e4f5",
"Name": "张三",
"Email": "z***@example.com",
"SigningOrder": 1,
"PreviousStatus": "delivered",
"CurrentStatus": "viewed",
"OperatedAt": "2024-01-15T10:35:00Z",
"DeclineReason": ""
}
]
}最佳实践
💡
- 妥善保管 CallbackKey,不要硬编码在客户端代码中,建议使用环境变量或密钥管理服务
- 回调处理逻辑应尽量轻量,确保在 5 秒内返回 HTTP 200,复杂业务逻辑建议异步处理
- 建议对回调事件做幂等处理,因为在极端情况下同一事件可能被投递多次
- 建议记录回调日志,便于排查问题
- 建议通过 EnvelopeId + EventType + OccurredAt 组合判断事件唯一性