回调通知
回调说明
回调通知都是 POST 请求,JSON格式("Content-Type":"application/json"):
(如果得到的是GET请求,请确认回调地址是否存在http到https的转发)
回调通知内容可以自定义是否,可以使用签名机制对内容进行验证;
回调通知 Url 要求:POST 请求能够正常返回 200;
如果配置了回调 Url地址,但是没有收到回调通知,请确认下回调地址提供是否正确,建议日志记录一下接收到的请求,方便排查问题!
回调加密与通知校验
回调消息加解密
新建应用号时,如果配置了“回调加密CallbackKey(可选参数)”,
电子签的回调请求体会进行加密处理,需要客户使用配置的CallbackKey进行解密得到结构体。
注意:配置了CallbackKey即会开启消息加密,去掉即为关闭。建议固定了之后不要变动。
解密步骤为
- 对收到的数据进行 Base64 解码得到密文。
- 对密文进行对称解密,算法为 AES-256-CBC,密钥为腾讯电子签提供的 CallbackUrlKey,IV 取 CallbackUrlKey 值的前16位,数据采用 PKCS#7 填充。
- 解密得到的数据为输入参数的 Json 格式。
解密代码可以参考本页底部代码
回调通知校验
新建应用号时,如果配置了“回调签名验证TOKEN(可选参数)”,
电子签的回调请求会在header中附带签名参数[Content-Signature],客户方用来验证请求来源确实电子签,保证安全性。
原理可参考:https://docs.github.com/en/developers/webhooks-and-events/webhooks/securing-your-webhooks
当接收到回调时:
取出header [Content-Signature]
验证签名
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
- 如果验证通过,继续处理。如果不通过,忽略该请求
回调通用结构体
未加密/解密后的结构体(Json)
参数名称 | 参数类型 | 参数描述 |
---|---|---|
MsgId | string | 消息唯一Id |
MsgType | string | 消息类型 |
MsgVersion | string | 消息版本,第三方应用集成固定为 thirdPartyApp |
MsgData | json | 消息数据 |
加密的结构体(Json)
参数名称 | 参数类型 | 参数描述 |
---|---|---|
encrypt | string | 加密后的消息体(通过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":"印章不清晰" // 审核原因
}
}
印章的生命周期说明:
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);
}
}
}