跳到主要内容

回调通知

回调说明

回调通知都是 POST 请求,JSON格式("Content-Type":"application/json"):

(如果得到的是GET请求,请确认回调地址是否存在http到https的转发)

回调通知内容可以自定义是否,可以使用签名机制对内容进行验证;

回调通知 Url 要求:POST 请求能够正常返回 200;

如果配置了回调 Url地址,但是没有收到回调通知,请确认下回调地址提供是否正确,建议日志记录一下接收到的请求,方便排查问题!

回调加密与通知校验

回调消息加解密

新建应用号时,如果配置了“回调加密CallbackKey(可选参数)”,

电子签的回调请求体会进行加密处理,需要客户使用配置的CallbackKey进行解密得到结构体。

注意:配置了CallbackKey即会开启消息加密,去掉即为关闭。建议固定了之后不要变动。

解密步骤为

  1. 对收到的数据进行 Base64 解码得到密文。
  2. 对密文进行对称解密,算法为 AES-256-CBC,密钥为腾讯电子签提供的 CallbackUrlKey,IV 取 CallbackUrlKey 值的前16位,数据采用 PKCS#7 填充。
  3. 解密得到的数据为输入参数的 Json 格式。

    解密代码可以参考本页底部代码

回调通知校验

新建应用号时,如果配置了“回调签名验证TOKEN(可选参数)”,

电子签的回调请求会在header中附带签名参数[Content-Signature],客户方用来验证请求来源确实电子签,保证安全性。

原理可参考:https://docs.github.com/en/developers/webhooks-and-events/webhooks/securing-your-webhooks

当接收到回调时:

  1. 取出header [Content-Signature]

  2. 验证签名

def verify_signature(payload_body)

signature = 'sha256=' + OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha256'), ENV['SECRET_TOKEN'], payload_body)

return halt 500, "Signatures didn't match!" unless Rack::Utils.secure_compare(signature, request.env['Content-Signature'])

end
  1. 如果验证通过,继续处理。如果不通过,忽略该请求

回调通用结构体

未加密/解密后的结构体(Json)

参数名称参数类型参数描述
MsgIdstring消息唯一Id
MsgTypestring消息类型
MsgVersionstring消息版本,第三方应用集成固定为 thirdPartyApp
MsgDatajson消息数据

加密的结构体(Json)

参数名称参数类型参数描述
encryptstring加密后的消息体(通过CallbackKey解密后得到上面的结构体)

回调通知列表

通知类型以 MsgType 做区分,按照回调通知类型主要分为:

​ ● 企业/员工认证与授权回调;

​ ● 印章相关回调;

​ ● 模板相关回调;

​ ● 合同发起与签署相关回调;

​ ● 文件资源相关回调;

​ ● 签署二维码相关回调;

​ ● 静默签功能授权回调;

企业与员工相关回调

● 平台企业授权电子签通知 - OrgAuth

触发时机:企业激活流程第一步,企业点击授权

{

"MsgId":"12345",

"MsgType":"OrgAuth",

"MsgData":{

"ApplicationId":"c17bdf9c2a7bdcb32611f4d0200fef3d", //应用Id

"ProxyOrganizationOpenId":"00498cc8500be9cxxxxxxx3aff766cac", //子客企业Id

"ProxyOperatorOpenId":"d7c13a8b81340cce9e3968c0ee248f04", //子客企业经办人Id

"AuthSuccess":true //是否授权

}

}

● 授权书审核结果回调 - OrgCertify

触发时机:企业通过上传授权书方式认证,电子签对授权书进行审核后触发

{

"MsgId":"yDRBJUUgygqwl721UuO4zjECcJHV2RAi",

"MsgType":"OrgCertify",

"MsgData":{

"ApplicationId":"15edb41f2ee412f5ff533ac0185ebb0b",//应用Id

"ProxyOrganizationOpenId":"sxxxxxxx-testxxx-paylxxx",//子客企业Id

"OperateSuccess":true,//是否审核通过,true通过,false未通过

"CertifyReason":"",//失败原因

"OperateTime":"2022-07-04 19:05:09"//操作时间

}

}

● 电子签开通通知 - OrgOpenTsignBiz

触发时机:企业完成认证激活,点击授权电子签之后。注意这里需要接收合作企业的ProxyAppId


{

"MsgId":"12345",

"MsgType":"OrgOpenTsignBiz",

"MsgData":{

"ApplicationId":"c17bdf9c2a7bdcb32611f4d0200fef3d", //应用Id

"ProxyOrganizationOpenId":"00498cc8500be9cxxxxxxx3aff766cac ",//子客企业Id

"ProxyOperatorOpenId":" d7c13a8b81340cce9e3968c0ee248f04",//子客企业经办人Id

"ProxyAppId": "c17bdf9c2a7bdcb32611f4d0200fef3d", //子客企业应用Id

"OpenSuccess":true,//是否开通

"OrganizationName":"公司名", //公司名

"USCC":"社会统一信用代码", //社会统一信用代码

"LegalName":"法人姓名" //法人姓名

}

}

● 员工加入企业通知 - VerifyStaffInfo


{

"MsgId": "12345",

"MsgType": "VerifyStaffInfo",

"MsgData": {

"ApplicationId": "xxxxxxxx6759ef51c25ebeba4fb2",//应用Id

"ProxyOrganizationOpenId": "xxxxx",//子客企业Id

"ProxyOperatorOpenId": "操作人openId"//子客企业经办人Id

}

}

● 经办人授权通知 - OperatorAuth

触发时机:企业在控制台,首次给经办人授予角色的时候(经办人需要实名)


{

"MsgId":"12345",

"MsgType":"OperatorAuth",

"MsgData":{

"ApplicationId":"c17bdf9c2a7bdcb32611f4d0200fef3d", //应用Id

"ProxyOrganizationOpenId":"00498cc8500be9cxxxxxxx3aff766cac ",//子客企业Id

"ProxyOperatorOpenId":" d7c13a8b81340cce9e3968c0ee248f04",//子客企业经办人Id

"FirstAuth":true //是否首次授权

}

}

● 超管变更通知 - SuperAdminChange

触发时机:企业在控制台,完成变更超级管理员时;


{

"MsgId":"12345",

"MsgType":"SuperAdminChange",

"MsgData":{

"ApplicationId":"c17bdf9c2a7bdcb32611f4d0200fef3d", //应用Id

"ProxyOrganizationOpenId":"00498cc8500be9cxxxxxxx3aff766cac ",//子客企业OpenId

"ChangeToUserOpenId":" d7c13a8b81340cce9e3968c0ee248f04",//子客企业员工/经办人OpenId

"ChangeToUserName":"张三" //新超管姓名

}

}

● 员工变更角色通知 - RolesChange

触发时机:企业在控制台,变更经办人角色时;


{

"MsgId":"xxxxxxxx",

"MsgType":"RolesChange",

"MsgData":{

"ApplicationId":"xxxxxxxx", //应用Id

"ProxyOrganizationOpenId":"open_org", //子客企业OpenId

"ProxyOperatorOpenId":"employee_open_id", //子客企业员工/经办人OpenId

"BeforeRoleNames":[ //变更前的角色列表

"普通经办员",

"业务管理员"

],

"AfterRoleNames":[ //变更后的角色列表

"普通经办员"

]

}

}

● 企业基础信息修改通知 - ModifyOrganizationBaseInfo

触发时机:企业在控制台,变更经办人角色时;


{

"MsgId":"xxxxxxxx",

"MsgType":"ModifyOrganizationBaseInfo",

"MsgData":{

"OrganizationOpenId":"open_org", //子客企业OpenId

"OrganizationName":"org_name", //子客原企业名

"OperatorName":"employee_name", //操作人姓名

"OperateTime":1683545268, //操作时间戳

"OrganizationChangeBaseInfo":{ //修改后的企业基本信息

"OrganizationNameNew":"new_org_name", //新企业名

"LegalNameNew":"new_legal_name", //新法人姓名

"RegionNew":"new_region", //新地区

"AddressNew":"new_address" //新地址

}

}

}

● 企业注销通知 - CloseOrganization

触发时机:企业在控制台,变更经办人角色时;


{

"MsgId":"xxxxxxxx",

"MsgType":"CloseOrganization",

"MsgData":{

"OrganizationOpenId":"open_org", //子客企业OpenId

"OrganizationName":"org_name", //子客企业名

"OperatorName":"employee_name", ////操作人姓名

"CloseTime":1683545268 //注销的时间戳

}

}

印章相关回调

● 创建、删除、停用、启用、授权/解除授权印章回调 - OperateSeal

触发时机:分别在创建、删除、停用、启用印章时进行回调通知;

Operate 枚举:

​ ● 创建:Create;

​ ● 删除:Delete;

​ ● 停用:Disable;

​ ● 启用:Enable;

​ ● 印章授权:Valid;

​ ● 解除印章授权:Invalid;


{

"MsgId": "yDRIGUUgygs8oey1UuO4zjEC8S6bOcm8",

"MsgType": "OperateSeal",

"MsgData": {

"ApplicationId": "abcd", //应用Id

"ProxyOrganizationOpenId": "open_org", //子客企业OpenId

"SealId": "xxxxxxxxxxxxxxxx", // 印章Id, 文件发起合同时使用

"ProxyOperatorOpenId": "employee_open_id", // 操作人OpenId

// 或 Delete 或 Disable 或 Enable 或 Valid 或 Invalid

"Operate": "Create",

"AuthorizedUsers": [ //授权人

{

"OpenId": "xxxxx1" //授权经办人Openid

},

{

"OpenId": "xxxxx1" //授权经办人Openid

}

]

}

}

● 印章审核结果通知 - AuditSealAuth

触发时机:平台替子客户上传印章后,电子签平台审核后,回调通知审核结果。此功能需要运营申请开通。

ReviewStatus 枚举:

​ ● 审核通过:PASS;

​ ● 审核驳回:REJECT;


{

"MsgId":"12345",

"MsgType":"AuditSealAuth",

"MsgData":{

"ApplicationId":"c17bdf9c2a7bdcb32611f4d0200fef3d", //应用Id

"ProxyOrganizationOpenId":"00498cc8500be9cxxxxxxx3aff766cac", //子客企业Id

"SealId":"s17bdf9c500be9cxxxxd0200fef3c", //印章编号

"SealName":"A公司合同章", //印章名称,

"ReviewStatus":"REJECT", // 审核结果 PASS:审核通过 REJECT:审核驳回

"ReviewReason":"印章不清晰" // 审核原因

}

}

印章的生命周期说明:

img

​ 1. 在控制台/接口上传印章后,会触发【Create事件】

​ 2. 如果是超管上传的模板印章,无需经过电子签审核,直接触发【Enable事件】,此时印章可以正常使用

​ 3. 其他的场景,需要经过电子签后台人员进行审核,审核结果会触发【AuditSealAuth事件】

​ 4. 如果审核通过,会触发【Enable事件】,此时印章可以正常使用

​ 5. 印章可用后,如果进行印章授权/取消授权操作,会触发【Valid/Invalid事件】

​ 6. 印章可用后,如果进行了停用操作,会触发【Disable事件】,此时印章不可用

​ 7. 印章停用后,如果进行了删除操作,会触发【Delete时间】

实际使用中:【Enable】事件可以作为印章可用的标准事件

模板相关回调

● 模板新增通知 - TemplateAdd

触发时机:

① 子客在子客控制台手动新增

② 子客领用模板库的模板

③ 自动领用的模板


{

"MsgId":"12345",

"MsgType":"TemplateAdd",

"MsgData":{

"ApplicationId":"c17bdf9c2a7bdcb32611f4d0200fef3d", //应用Id

"ProxyOrganizationOpenId":"00498cc8500be9cxxxxxxx3aff766cac", //子客企业Id

"ProxyOperatorOpenId":"d7c13a8b81340cce9e3968c0ee248f04", //子客企业经办人Id

"TemplateId":"templateId1", //模版ID

"TemplateName":"xx公司劳务合同模版", //模版名称,

"CreateTime":1626083520, //创建时间

"ChannelTemplateId": "yDRslUUsucxxuUxupTscobRNnhOuC413", // 若为领用模版,此字段为对应第三方平台模版id

"ChannelTemplateName": "空白文档-1111", // 同上

"SaveType": 0, //模板是否自动设置为子客模板,0-需要子客手动领取的模板,1-自动设置子客模板

"TemplateVersion": "20221212001" //模板版本号。默认为空时,初始版本为yyyyMMdd000。全数字字符

}

}

● 模版修改通知 - TemplateUpdate


{

"MsgId":"12345",

"MsgType":"TemplateUpdate",

"MsgData":{

"ApplicationId":"c17bdf9c2a7bdcb32611f4d0200fef3d", //应用Id

"ProxyOrganizationOpenId":"00498cc8500be9cxxxxxxx3aff766cac", //子客企业Id

"ProxyOperatorOpenId":"d7c13a8b81340cce9e3968c0ee248f04", //子客企业经办人Id

"TemplateId":"templateId1", //模版ID

"TemplateName":"xx公司劳务合同模版", //模版名称,

"UpdateTime":1626083520 //修改时间(必须)

}

}

● 模版删除通知 - TemplateDelete

触发时机:企业在控制台删除模版


{

"MsgId":"12345",

"MsgType":"TemplateDelete",

"MsgData":{

"ApplicationId":"c17bdf9c2a7bdcb32611f4d0200fef3d", //应用Id

"ProxyOrganizationOpenId":"00498cc8500be9cxxxxxxx3aff766cac", //子客企业Id

"ProxyOperatorOpenId":"d7c13a8b81340cce9e3968c0ee248f04", //子客企业经办人Id

"TemplateId":"templateId1", //模版ID

"TemplateName":"xx公司劳务合同模版", //模版名称,

"DeleteTime":1626083520 //删除时间(必须)

}

}

合同发起及签署相关回调

● 合同状态通知 - FlowStatusChange

触发时机:在合同的发起,个人签署,企业签署等各个状态变化时;

注意: 合同相关回调地址的优先级: flowInfo.callbackurl > 应用号里面配置的回调地址

合同状态包括:

合同状态对应FlowStatus
合同创建INIT
合同签署中PART
合同签署完成ALL
合同拒签REJECT
合同撤回CANCEL
合同即将过期WILLEXPIRE
合同流签(合同过期)DEADLINE
解除协议(已解除)RELIEVED
合同异常EXCEPTION

签署人状态包括:

签署人状态对应ApproveStatus
待签署PENDING
已签署ACCEPT
拒绝REJECT
过期没人处理DEADLINE
流程已撤回CANCEL
流程已终止STOP
待领取WAITPICKUP
待填写FILLPENDING
填写完成FILLACCEPT
已转他人处理FORWARD
解除协议(已解除)RELIEVED
拒绝填写FILLREJECT
异常EXCEPTION

{

"MsgId":"12345",

"MsgType":"FlowStatusChange",

"MsgData":{

"ApplicationId":"123124123123123123",//APPID

"ProxyOrganizationOpenId":"openorg1234",//子客企业ID

"CustomerData":"data",//业务信息

"FlowId":"111111295e544f68973bafdfd317633f", //合同Id

"FlowName":"合同名称", //合同名称

"FlowType":"劳务合同", //合同类型

"FlowStatus":"REJECT", // 合同状态,见合同状态列表

"FlowMessage":"用户A拒绝",//

"CreateOn":1563968167,//发起时间

"Deadline":1563968167,//截止时间

"CcInfo": [], // 抄送人

"FlowApproverInfo":[ //参与人信息

{

"ProxyOperatorOpenId":"operator",//子客经办人Id

"PhoneNumber":"13112345678",//手机号码

"ProxyOrganizationName":"企业A",//子客企业名称

"ProxyOrganizationOpenId": "zk_open_org", // 子客企业Openid

"SignOrder":1, //签署顺序

"recipientId": "ids", // 电子签定义的参与人ID

"ApproveName":"name1",//参与者姓名

"ApproveStatus":"ACCEPT",// 签署人状态,见签署人状态列表

"ApproveMessage":"已签署", //签署相关信息

"ApproveTime":1563968167, //签署时间戳

"CaSign": "", // 特定签署场景返回的证书信息

},

{

"ProxyOperatorOpenId":"",//子客经办人Id, 个人用户为空

"PhoneNumber":"13112345678", // 签署人手机号

"ProxyOrganizationName":"", //子客企业名,个人用户为空

"ProxyOrganizationOpenId": "", // 子客企业Openid,个人用户为空

"SignOrder":2, //签署顺序

"recipientId": "ids", // 电子签定义的参与人ID

"ApproveName":"name2", //签署人姓名

"ApproveStatus":"PENDING", //签署状态,见签署人状态列表

"ApproveMessage":"待签署", //签署相关信息

"ApproveTime":1563968167, //签署时间戳

"CaSign": "", // 特定签署场景返回的证书信息

}

],

"FlowGroupMessage": { // 合同组信息

"FlowGroupId": "111111295e544f68973xxxx317633f", // 合同组id

"FlowGroupName": "合同名称", // 合同组名称

"FlowsInfo": [ // 子合同信息,数组

{

"FlowId": "111111295e544f68973qqqq317633f" // 子合同Id

}

],

"CreateOn": 1563968167329 // 合同组创建时间

},

"FlowSignSeal": { // 签署印章列表结构

"CurrentIndex": 0, // 签署顺序

"SignSealInfo": { // 签署印章结构

"ApproverName": "name1", // 签署人姓名

"ApproverType": 1, // 签署人类型

"SealComponent": { // 印章控件结构

"ComponentInfos": [ // 控件信息结构
{
"ComponentIds": [ // 控件id列表

"ComponentId_19" // 控件id
],
"DocumentId": "doc id" // 文档id
}
],
"SealContent": "base64 of pic", // 具体印章图文,base64
"SealId": "seal id" // 印章id
}
},
"TotalCount": 2 // 签署印章总数
},

"OccurTime":1563968167 //回调发起时间

}

}

● 经办人转交签署任务 - ForwardFLow

触发场景:合同经办人将合同转交给同企业其他经办人时触发;


{

"MsgId":"yDRIGUUgygs8aey1UuO4zjEuM18ffkaK",

"MsgType":"ForwardFLow",

"MsgData":{

"ApplicationId":"abcd", //应用Id

"ProxyOrganizationOpenId":"open_org", //子客企业OpenId

"ProxyOperatorOpenId":"employee_open_id", //子客经办人OpenId

"FlowId":"abcccccccccccccc", //合同Id

"ForwardedOpenId":"test3" //转交给对应经办人的OpenId

}

}

● 合同发起扣费通知 - FLowCost

触发场景:合同发起成功时触发;


{

"MsgId":"yDRspUUgyg17u3j8Ux9XTPkBnnXZyvdT",

"MsgType":"FlowCost",

"MsgData":{

"ApplicationId": "应用Id", // 应用Id

"ProxyOrganizationOpenId": "子客企业Id", // 子客企业Id

"ProxyOperatorOpenId": "子客企业经办人Id", // 子客企业经办人Id

"Cost":1, //消耗份数,

"CostChannel":"企业版", // 对应计费版本

"FlowId":"yDRspUUgyg17u3jwUx9XTPkysXSTG8jO", // 合同Id

"IsResell":false //是否分销,一般为false

}

}

● 合同撤销扣费返还通知 - FLowCost

触发场景:合同撤销时触发;


{

"MsgId":"yDRspUUgyg17u3j8Ux9XTPkBnnXZyvdT",

"MsgType":"FlowCost",

"MsgData":{

"ApplicationId": "应用Id", // 应用Id

"ProxyOrganizationOpenId": "子客企业Id", // 子客企业Id

"Cost":-1, // 返还份数,是负数

"CostChannel":"企业版", // 计费版本

"FlowId":"yDRspUUgyg17u3jwUx9XTPkysXSTG8jO", // 合同Id

"IsResell":false //是否分销,一般为false

}

}

● 合同审核通知 - CreateFlowReview

触发场景:对需要审核的合同进行审核操作;

其中Operate包括 CreateFlowReviewStart:提交发起审核 CreateFlowReviewPass:发起审核通过 CreateFlowReviewReject:发起审核拒绝


{

"MsgId":"yDwFhUUckps95bm2UElNO1L1rQ8x9p0Q",

"MsgType":"CreateFlowReview",

"MsgData":{

"ApplicationId": "应用Id", // 应用Id

"ProxyOrganizationOpenId": "子客企业Id", // 子客企业Id

"ProxyOperatorOpenId":"经办人的OpenId", // 经办人的OpenId

"DocumentId": "文档Id", // 文档Id

// 操作类型:CreateFlowReviewStart:提交发起审核, CreateFlowReviewPass:发起审核通过, CreateFlowReviewReject:发起审核拒绝
"Operate":"CreateFlowReviewStart",

"FlowId":"合同Id", // 合同Id

"FlowName":"合同名称" // 合同名称

}

}

文件资源相关回调

● 合作企业授权下载合同通知 - DownloadOpenAuth

触发场景:合作企业授权下载合同人脸通过


{

"MsgId": "12345",

"MsgType": "DownloadOpenAuth",

"MsgData": {

"ApplicationId": "应用Id", //应用id

"ProxyOrganizationOpenId": "子客企业Id", //子客企业Id

"ProxyOperatorOpenId": "子客企业经办人Id", //子客企业经办人Id

"AuthSuccess": true //认证结果,固定为true

}

}

● 合作企业取消授权下载合同通知 - DownloadCloseAuth

触发场景:合作企业取消授权下载合同人脸通过


{

"MsgId": "12345",

"MsgType": "DownloadCloseAuth",

"MsgData": {

"ApplicationId": "应用Id", //应用Id

"ProxyOrganizationOpenId": "子客企业Id", //子客企业Id

"ProxyOperatorOpenId": "子客企业经办人Id", //子客企业经办人Id

"AuthSuccess": false //处理结果,固定为false

}

}

● 文档合成完成后回调通知 - DocumentFill

触发场景:当发起的合同中存在高耗时合成任务时,合成完成后进行回调;

如:含有动态表格的文档合成;


{

"MsgId": "123456",

"MsgType": "DocumentFill",

"MsgData": {

"ApplicationId": "应用Id", //应用Id

"ProxyOrganizationOpenId": "子客企业Id", //子客企业Id

"TaskId": "合成任务Id", //合成任务Id

"DocumentFileStatus": "SUCCESS", //处理状态,一般是成功-SUCCESS

"ResourceUrl": "PDF文档资源链接" //PDF文档资源链接

}

}

可以使用 TaskId 通过 ChannelGetTaskResultApi 接口获取文档任务状态;

签署二维码相关回调

● 二维码发起合同失败通知 - CreateFlowByQrCode

触发场景:用户通过签署二维码发起合同时,企业额度不足导致失败


{

"MsgId": "12345",

"MsgType": "CreateFlowByQrCode",

"MsgData": {

"ApplicationId": "应用Id", //应用Id

"ProxyOrganizationOpenId": "子客企业Id", //子客企业Id

"ProxyOperatorOpenId": "子客企业经办人Id", //子客企业经办人Id

"TemplateId": "模版ID", //模版ID

"QrCodeId": "二维码ID", //二维码ID

"FlowName": "合同名称", //合同名称

"CreateResult": false, //创建结果,固定为失败-false

"Reason": "合同份额为0" //失败原因,一般是 合同份额为0

}

}

静默签功能授权回调

● 他方静默签授权通知 - PartnerServerSignAuthorization

触发时机:分别在授权待审核、授权通过、授权取消、授权驳回时进行回调通知;

AuthorizedStatus 枚举:

​ ● 待审核:Reviewing;

​ ● 审核通过:Authorized;

​ ● 授权取消:Cancel;

​ ● 授权驳回:Deny;


{

"MsgId": "12345",

"MsgType": "PartnerServerSignAuthorization",

"MsgData": {

"AuthorizedOrganizationId": "demo",//被授权企业id

"AuthorizationOrganizationId": "demo",//授权方企业id

"AuthorizedStatus": "Reviewing", // 授权状态

"OperateTime": 1666263716, //授权状态变更时间戳

"AuthorizationApplicationId": "demo"//授权方applicationId

}

}

回调 FAQ

回调地址是否支持同时配置多个?

支持,回调地址一个应用号下只能有一个个,根据您的需求不同的地址可以配置相同或者不同的 CallbackUrlKey、token。

回调地址是否支持更改或删除?

支持,您可以在控制台开发着中心中进行回调地址的配置。

回调地址配置后多长时间生效呢?

配置完成后立即生效。

为什么客户收到 FlowCallbackStatus 为4(已签署)的回调通知后,又收到了 FlowCallbackStatus 为1(待签署)的通知?

以单方签署的合同为例,FlowCallbackStatus 状态变化一般是由1变为4。少量回调可能因状态变化间隔比较短、重发、或者网络传输等原因,小几率出现到达顺序不一致,建议开发者从代码层面进行适当控制,例如状态更新为4后不能再更新为1。

电子签发送回调时超时时间是多久?

超时时间为5秒。

电子签发送回调失败后,回调最大重试次数是多少?重试机制是怎么样的呢?

回调的最大重试次数是36次;回调重试间隔随次数倍数增加,1s、2s、3s、4s、5s、10s、15s、20s...6h。

回调解密 demo

Java

import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;


public class CallbackAes {

public static byte[] pkcs7Padding(byte[] ciphertext, int blockSize) {
int padding = blockSize - ciphertext.length % blockSize;
byte[] padtext = repeat((byte) padding, padding);
ciphertext = append(ciphertext, padtext);
return ciphertext;
}

public static byte[] repeat(byte val, int count) {
byte[] result = new byte[count];
for (int i = 0; i < count; i++) {
result[i] = val;
}
return result;
}

public static byte[] append(byte[] a, byte[] b) {
byte[] result = new byte[a.length + b.length];
System.arraycopy(a, 0, result, 0, a.length);
System.arraycopy(b, 0, result, a.length, b.length);
return result;
}

public static byte[] pkcs7UnPadding(byte[] origData) {
int length = origData.length;
int unpadding = origData[length - 1];
byte[] result = new byte[length - unpadding];
System.arraycopy(origData, 0, result, 0, result.length);
return result;
}

public static byte[] aesEncrypt(byte[] origData, byte[] key) throws Exception {
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
int blockSize = cipher.getBlockSize();
origData = pkcs7Padding(origData, blockSize);
SecretKeySpec keyspec = new SecretKeySpec(key, "AES");
byte[] iv = new byte[blockSize];
System.arraycopy(key, 0, iv, 0, iv.length);
IvParameterSpec ivspec = new IvParameterSpec(iv);
cipher.init(Cipher.ENCRYPT_MODE, keyspec, ivspec);
byte[] encrypted = cipher.doFinal(origData);
return Base64.getEncoder().encode(encrypted);
}

public static byte[] aesDecrypt(byte[] crypted, byte[] key) throws Exception {
byte[] decoded = Base64.getDecoder().decode(crypted);
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
int blockSize = cipher.getBlockSize();
SecretKeySpec keyspec = new SecretKeySpec(key, "AES");
byte[] iv = new byte[blockSize];
System.arraycopy(key, 0, iv, 0, iv.length);
IvParameterSpec ivspec = new IvParameterSpec(iv);
cipher.init(Cipher.DECRYPT_MODE, keyspec, ivspec);
byte[] origData = cipher.doFinal(decoded);
return pkcs7UnPadding(origData);
}

public static void main(String[] args) throws Exception {
// 传入CallbackUrlKey
byte[] key = "***************".getBytes();
// 传入密文
byte[] origData = aesDecrypt("****************".getBytes(StandardCharsets.UTF_8), key);
// 打印解密后的内容,格式为json
System.out.println(new String(origData, StandardCharsets.UTF_8));
}
}

PHP

<?php
require_once __DIR__.'/../../../vendor/autoload.php';

class Aes
{
public $key = '';
public $iv = '';

public function __construct($config)
{
foreach($config as $k => $v){
$this->$k = $v;
}
}

//解密
public function aesDe($data){
return openssl_decrypt(base64_decode($data), $this->method, $this->key, OPENSSL_RAW_DATA, $this->key);
}
}

$config = [
'key' => '********************', // 此处填入CallbackUrlKey
'method' => 'AES-256-CBC' //加密方式
];

$obj = new Aes($config);

// 此处填入收到的密文
$data = '*****************************';

echo $obj->aesDe($data);//解密

Golang

package v20201111

import (
"crypto/aes"
"crypto/cipher"
"encoding/base64"
"fmt"
"testing"
)

func AesDecrypt(crypted, key []byte) ([]byte, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, err
}
blockSize := block.BlockSize()
blockMode := cipher.NewCBCDecrypter(block, key[:blockSize])
origData := make([]byte, len(crypted))
blockMode.CryptBlocks(origData, crypted)
origData = PKCS7UnPadding(origData)
return origData, nil
}

// PKCS7UnPadding 去除填充
func PKCS7UnPadding(origData []byte) []byte {
length := len(origData)
unPadding := int(origData[length-1])
return origData[:(length - unPadding)]
}

func TestDecrypt(t *testing.T) {
// 传入CallbackUrlKey
key := "***********"
// 传入密文
content := "***********"

// base64解密
crypted, err := base64.StdEncoding.DecodeString(content)
if err != nil {
fmt.Printf("base64 DecodeString returned: %s", err)
return
}

origData, err := AesDecrypt(crypted, []byte(key))
if err != nil {
fmt.Printf("AesDecrypt returned: %s", err)
return
}
fmt.Printf("%s", string(origData))
}

::: ::: Python

# -*- coding: utf-8 -*-
import base64

from Cryptodome.Cipher import AES


def decode_aes256(data, encryption_key):
iv = encryption_key[0:16]
aes = AES.new(encryption_key, AES.MODE_CBC, iv)
d = aes.decrypt(data)
unpad = lambda s: s[0:-ord(d[-1:])]
return unpad(d)

# 此处传入密文
data = '**************************************************'
data = base64.b64decode(data)
# 此处传入CallbackUrlKey
e = decode_aes256(data, bytes('**************************************************', encoding="utf8"))
print(type(e))
print(str(e, encoding="utf8"))

C#

using System;
using System.Security.Cryptography;
using System.Text;
namespace TencentCloudExamples
{

class EssCallback
{

static void Main1(string[] args)
{
try
{
// 传入CallbackUrlKey
String key = "*************";
// 传入密文
String content = ""*************";";

String plaintext = AESDecrypt(content, Encoding.ASCII.GetBytes(key));

Console.WriteLine(plaintext);
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
Console.Read();

}

public static string AESDecrypt(string encryptStr, byte[] key)
{
byte[] toEncryptArray = Convert.FromBase64String(encryptStr);
RijndaelManaged rDel = new RijndaelManaged();
rDel.Key = key;
byte[] iv = new byte[16];
Array.Copy(key, iv, iv.Length);
rDel.IV = iv;
rDel.Mode = CipherMode.CBC;
rDel.Padding = PaddingMode.PKCS7;
ICryptoTransform cTransform = rDel.CreateDecryptor();
byte[] resultArray = cTransform.TransformFinalBlock(toEncryptArray, 0, toEncryptArray.Length);
return UTF8Encoding.UTF8.GetString(resultArray);
}

}

}