艾思科蓝开放平台

回调说明

任务完结后主动推送结果到您的回调地址,免轮询实时接收。

当任务到达终态(成功或失败)后,平台以 POST 方式向提交时或机构配置的 callbackUrl 推送结果报文,并附签名头。机构用主 appSecret 验签确认来源,返回 HTTP 2xx 表示已收到。

请求方式与签名头

平台以 POST、Content-Type: application/json 向 callbackUrl 推送,请求头携带与开放 API 请求侧同名的四个签名头:X-App-Id、X-Timestamp、X-Nonce、X-Signature。

各签名头含义与请求侧完全一致,详见 签名机制

验签

机构用主 appSecret 做 HMAC-SHA256 验签,签名内容与请求签名同构(五段,以换行分隔):

Plain
POST
{callbackUrl 的 path}
{X-Timestamp}
{X-Nonce}
{SHA-256(body 原始字节) 的十六进制}

其中 path 取 callbackUrl 的 path 段,末段为回调报文原始字节的 SHA-256 十六进制。验签通过即可信任报文来源与完整性;X-Timestamp、X-Nonce 参与签名,可用于防重放。注意回调仅用主密钥签名,轮换后请以新的主密钥验签。

回调报文

报文为 JSON,业务字段明文(机密性由 TLS 保证),仅报告下载地址 downloadUrlEnc 做字段级加密。字段顺序固定如下:

JSON
{
"taskNo": "KEO2607180001",
"outTradeNo": "OT-20260718-001",
"checkType": 2,
"status": "success",
"score": 12.30,
"message": null,
"finishTime": 1784358834000,
"downloadUrlEnc": "K2sQ3f...Base64URL...9vA"
}

响应字段

名称类型说明
taskNostring平台任务号
outTradeNostring机构提交时的业务订单号
checkTypeint数字检测类型码:1=AI率 / 2=相似度 / 3=参考文献
statusstring终态:success 成功 / failed 失败
scorenumber检测分值(失败时可能为空)
messagestring|null附加说明(明文),失败时可能为空
finishTimelong完结时间戳(epoch 毫秒)
downloadUrlEncstring离线报告 PDF 下载地址密文(AES-256-GCM,密钥为主 appSecret;解出后有效期 10 分钟)。无报告可下时不返回此字段。

其中 downloadUrlEnc 为加密字段,解密方法与示例代码见 解密 downloadUrlEnc

响应与重试

机构处理成功须返回 HTTP 2xx。返回非 2xx、超时或连接失败均视为推送失败。

推送失败按阶梯退避重试:1 分钟 → 5 分钟 → 30 分钟 → 2 小时 → 6 小时,最多 6 次;重试耗尽后平台记录告警并停止推送,此时请改用查询接口兜底核对。

注意事项

  • callbackUrl 长度不得超过 255 字符(含查询串),超长的提交会被直接拒绝——若您的地址靠 query 传业务参数,请留意这个上限。地址还必须为公网可达;指向内网、环回或云元数据(如 169.254.169.254)的地址会被安全策略拒绝、不予推送。
  • 同一任务可能收到多次推送,请按 taskNo 幂等去重,并以报文中的 finishTime 为准取最新一条——不要固定保留首次。失败任务经平台重新处理后可能转为成功,此时同一 taskNo 会先后收到 failed 与 success 两条,只认首次会让这类任务在您侧永远停留在失败。

模拟回调自检

不必等一个真任务跑完才验证接收端:在机构后台「应用管理」保存回调地址后点「模拟测试」,平台会立即推一条与线上同构的模拟报文——同样的四个签名头、同样的签名口径、同样带加密的 downloadUrlEnc,只是任务数据是假的。

模拟报文可据此识别:taskNo 以 TEST 开头、outTradeNo 固定为 CALLBACK-TEST、message 写明「模拟测试回调」。请在接收端据此短路掉入库与后续业务处理,只做验签与解密的自检。

其中 downloadUrlEnc 解出来正是本文档解密一节的地址(https://open.ais.cn/docs/callback#decrypt)。解得出这一串,说明您的验签与解密实现都对了;解不出请先核对用的是不是当前主密钥。 解密 downloadUrlEnc

解密 downloadUrlEnc

downloadUrlEnc 是离线报告 PDF 的预签名下载地址密文,用您的主 appSecret 以 AES-256-GCM 加密:密钥为 appSecret(Base64)解码后的 32 字节原始值;密文为 Base64URL(IV ‖ 密文 ‖ 认证标签),其中 IV 取前 12 字节、认证标签为末 16 字节。

单独加密它,是因为这个地址本身就是取报告的凭据——任何拿到它的人无需密钥即可下载全文报告。加密后即使经由网关日志、APM 采样或消息队列留痕,留下的也只是密文。

Java
// 回调报文 downloadUrlEnc 解密示例(Java 11+,无第三方依赖)。
// 用法:java DecryptDownloadUrl.java <appSecret> <downloadUrlEnc>
//
// 口径:AES-256-GCM。密钥 = appSecret(Base64)解码后的 32 字节原始值;
// 密文 = Base64URL(IV ‖ 密文 ‖ 认证标签),IV 取前 12 字节、标签为末 16 字节
// (Java 的 GCM 实现要求标签跟在密文之后,故此处直接整段传入,无需自行拆出标签)。
import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Base64;
public class DecryptDownloadUrl {
private static final int IV_LEN = 12;
private static final int TAG_BITS = 128;
public static String decrypt(String downloadUrlEnc, String appSecret) throws Exception {
byte[] key = Base64.getDecoder().decode(appSecret);
byte[] raw = Base64.getUrlDecoder().decode(downloadUrlEnc);
Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(key, "AES"),
new GCMParameterSpec(TAG_BITS, Arrays.copyOfRange(raw, 0, IV_LEN)));
byte[] plain = cipher.doFinal(raw, IV_LEN, raw.length - IV_LEN);
return new String(plain, StandardCharsets.UTF_8);
}
public static void main(String[] args) throws Exception {
if (args.length < 2) {
System.err.println("用法: java DecryptDownloadUrl.java <appSecret> <downloadUrlEnc>");
System.exit(1);
}
// 解密失败会抛 AEADBadTagException:密钥不对或报文被篡改,此时不要重试,按可疑请求处理。
System.out.println(decrypt(args[1], args[0]));
}
}

解出的地址有效期 10 分钟,请立即下载或转存;过期后可用查询接口换取新地址。任务失败、报告未生成或已过 10 天保留期时,报文不含该字段——请按「有此键才解密」处理,不要假定它一定存在。