信封签署 (eSign)回调说明回调概览

回调概览

在控制台 AppId 设置页配置 CallbackUrl 后,信封状态发生变更时,服务端将向 CallbackUrl 发送 POST 请求。 > ⚠️ 注意:回调均为 POST 请求。若收到 GET 请求,请确认 CallbackUrl 是否存在 HTTP 到 HTTPS 的 301/302 跳转。

配置步骤

  1. 1. 登录控制台,进入「应用管理」→ 选择目标 AppId
  2. 2. 在「回调配置」区域填写 CallbackUrl(须为公网可达的 HTTPS 地址)
  3. 3. 保存后系统自动生成 32 字符的 CallbackKey(Base62 编码),用于加解密回调数据
  4. 4. 妥善保管 CallbackKey,遗失后需重新生成(旧 Key 立即失效)
⚠️CallbackUrl 必须为 HTTPS 协议
CallbackUrl 必须为公网可达地址(不允许内网 IP、localhost)
每个 AppId 可配置多个 CallbackUrl,各自拥有独立的 CallbackKey
不同 CallbackUrl 使用各自独立的 CallbackKey,互不影响

数据格式与加密

数据格式与加密

{
  "encrypt": "dGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIGV4YW1wbGU..."
}
密钥
32 字节(256 位)
Nonce
12 字节(96 位,随机生成)
密文格式
Base64( nonce[12字节] + GCM_ciphertext + GCM_tag[16字节] )

解密流程

  1. 1. 从请求 Body 中取出 encrypt 字段值
  2. 2. 对该值进行 Base64 标准解码,得到 raw bytes
  3. 3. 前 12 字节为 nonce,剩余部分为 GCM ciphertext(含 16 字节 auth tag)
  4. 4. 使用 CallbackKey(32 字节,UTF-8 编码)构造 AES-256-GCM 解密器
  5. 5. 调用 GCM.Open(nonce, ciphertext) 解密得到原始 JSON 明文
  6. 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 结构

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": ""
    }
  ]
}

最佳实践

💡
  1. 妥善保管 CallbackKey,不要硬编码在客户端代码中,建议使用环境变量或密钥管理服务
  2. 回调处理逻辑应尽量轻量,确保在 5 秒内返回 HTTP 200,复杂业务逻辑建议异步处理
  3. 建议对回调事件做幂等处理,因为在极端情况下同一事件可能被投递多次
  4. 建议记录回调日志,便于排查问题
  5. 建议通过 EnvelopeId + EventType + OccurredAt 组合判断事件唯一性