引导/加签/加解密
网易云商呼入机器人 引导/加签/加解密。
调用说明
支持的Https请求方法:POST/GET
支持的Content-Type类型:application/json
字符编码:UTF-8
通用参数说明:
- +(加号)代表上个参数的子集,支持多层。
- 参数中有时间戳字段(以xxxxTime结尾)均为毫秒级别时间戳。
主动调用
- 请求URL示例:https://b.163.com/open/api/deepvox/{resource}/{function}
- {resource} 资源名,通常对应一类API
- {function} 该资源提供的操作具体方法
比如生产环境查询当前任务列表完整请求ulr: https://b.163.com/open/api/deepvox/callRecord/getList
回调通知
呼入机器人使用过程中,当如下图配置的开关打开时,系统通过配置地址向三方系统推送数据。

如通话记录回调:每次机器人通话结束后,推送相关通话记录信息。使用回调接口接收通知时请注意:
- 请您在对接接口前,务必配置好回调地址,否则回调将无法成功(系统设置-API接口)。
- 呼入向第三方接口请求信息时,采用密钥安全认证机制保证第三方系统的信息安全,接口鉴权参考上文。
- 所有回调接口采用POST请求发起,请求Content-Type类型为:application/json;charset=utf-8,返回类型为JSON,同时进行UTF-8编码;
- 回调极少概率可能出现重复回调,接口中建议做幂等处理。
- 回调通知公网接口,一般设置超时时间5s,非特殊场景,建议业务方异步处理逻辑,提前返回。
- 一般情况下系统不会对回调业务方接口的响应做处理,如有特殊场景,机器人需要处理业务方返回,返回结构参考统一返回格式。
回调数据结构
| 参数 | 数值类型 | 参数说明 |
|---|---|---|
| requestId | String | 请求的标识ID(不是请求的唯一ID,不要幂等处理),排查问题时带上此ID |
| dataType | String | 回调类型 |
| data | Object、Array | 普通对象:{},数组[{}] |
支持回调接口类型:
| 回调接口描述 | dataType | 补充说明 |
|---|---|---|
| 呼入进线事件 | CALL_START | 有进线就会回调,无论是否接通 |
| 呼入结束事件 | CALL_END |
权限校验
请求公共参数
公共参数(HTTP Header)是用于接口签名的参数,如非必要,在每个接口单独的接口文档中不再对这些参数进行说明,但每次请求均需要携带这些参数,才能正常发起请求。
公共参数需要统一放到 HTTP Header 请求头部中。
| 参数名称 | 类型 | 是否必选 | 描述 |
|---|---|---|---|
| X-YS-APIKEY | String | 是 | 用户的appkey,可以在外呼系统管理后台获取 |
| X-YS-TIME | String | 是 | 当前的时间戳(毫秒:System.currentTimeMillis()+"") 注意:如果与服务器时间相差超过5分钟,会引起签名过期错误。 |
| X-YS-SIGNATURE | String | 是 | 用户计算的签名串 |
| X-YS-APPTYPE | String | 否 | 如果是外部短信渠道则必传,传入渠道标签(渠道标签需要申请) |
加/验签规则
在第一次使用API 之前,请登录呼入机器人系统(系统设置-API接口 )获取安全凭证。 安全凭证包括 appKey 和 appSecret:
- appKey 用于标识 API 调用者身份
- appSecret 用于加密签名字符串和服务器端验证签名字符串的密钥。
- 重要提示: 本文档中提供的所有接口均面向开发者服务器端调用,用于计算 X-YS-SIGNATURE 的 AppSecret 开发者应妥善保管,可在应用的服务器端存储和使用,但不应存储或传递到客户端,也不应在网页等前端代码中嵌入。
待加签的字符串 = 时间+请求串+请求体,获得待鉴权的字符串。然后对字符串进行签名运算,获得签名字符
加签的字符串(时间+请求串+请求体)各个部分的详细解释:
- 时间:表示发起请求时的时间戳,UNIX时间戳,以毫秒为单位。
- 请求串:仅针对GET请求使用,指HTTP请求的查询参数部分;POST请求下该内容为空
- 请求体:仅适用于POST请求,表示HTTP请求的消息体;GET请求下该内容为空
加签的算法的解释,参见下面demo的中文注释部分,如果还不好理解,可以将全部代码贴到任意大模型(如豆包,千问等)翻译。
java demo 见附录JAVA Demo
public static String getRequestQueryStr(HttpServletRequest request) {
// 对 Query 参数按照字典对 Key 进行排序后,按照value1+value2方法拼接
// 转换一下数据类型并排序
List<String> reqList = new ArrayList<>();
Enumeration<String> reqEnu = request.getParameterNames();
while (reqEnu.hasMoreElements()) {
reqList.add(reqEnu.nextElement());
}
Collections.sort(reqList);
StringBuilder requestQuery = new StringBuilder();
for (String key : reqList) {
String value = request.getParameter(key);
requestQuery.append(value == null ? "" : value);
}
return requestQuery.toString();
}
//获取请求体内容
public static String readContent(HttpServletRequest request) {
try {
byte[] byteContent = IOUtils.toByteArray(request.getInputStream());
if (byteContent != null) {
return new String(byteContent, StandardCharsets.UTF_8);
}
} catch (IOException e) {
logger.error("readcontent fail");
}
return "";
}
/***
* 计算请求签名值
*
* @param message 待签名字符串
* @param secret 密钥APP KEY
* @return HmacSHA256计算后摘要值的16进制字符串
* @throws Exception 加密过程中的异常信息
*/
public static String getSignature(String message, String secret) throws Exception {
String algorithm = "HmacSHA256";
Mac hmacSha256;
try {
hmacSha256 = Mac.getInstance(algorithm);
byte[] keyBytes = secret.getBytes("UTF-8");
byte[] messageBytes = message.getBytes("UTF-8");
hmacSha256.init(new SecretKeySpec(keyBytes, 0, keyBytes.length, algorithm));
// 使用HmacSHA256对二进制数据消息Bytes计算摘要
byte[] digestBytes = hmacSha256.doFinal(messageBytes);
return byte2hex(digestBytes);
} catch (NoSuchAlgorithmException e) {
String msg = MessageFormat.format("不支持此算法: {0}", e.getMessage());
Exception ex = new Exception(msg);
ex.initCause(e);
throw ex;
} catch (UnsupportedEncodingException e) {
String msg = MessageFormat.format("不支持的字符编码: {0}", e.getMessage());
Exception ex = new Exception(msg);
ex.initCause(e);
throw ex;
} catch (InvalidKeyException e) {
String msg = MessageFormat.format("无效的密钥规范: {0}", e.getMessage());
Exception ex = new Exception(msg);
ex.initCause(e);
throw ex;
}
}
/***
* 将byte[]转成16进制字符串
*
* @param data
*
* @return 16进制字符串
*/
public static String byte2hex(byte[] data) {
StringBuilder hash = new StringBuilder();
String stmp;
for (int n = 0; data != null && n < data.length; n++) {
stmp = Integer.toHexString(data[n] & 0XFF);
if (stmp.length() == 1)
hash.append('0');
hash.append(stmp);
}
return hash.toString();
}
}
加签python部分参考代码(@阿志(刘荣志)提供)
class WecallAuth(AuthBase):
@classmethod
def _get_request_query_str(cls, request):
values = []
query = urlparse(request.url).query
query = urllib.parse.parse_qsl(query)
for k, v in sorted(query):
values.append(str(v))
return ''.join(values)
@classmethod
def _get_request_body(cls, request):
return request.body.decode() if request.body else ''
@classmethod
def _signature(cls, message, secret):
signature = hmac.new(
secret.encode('utf-8'), message.encode('utf8'), digestmod=hashlib.sha256
).hexdigest()
return signature
@classmethod
def _get_signature(cls, request, ts):
message = f'{ts}{cls._get_request_query_str(request)}{cls._get_request_body(request)}'
return cls._signature(message, YS_APP_SECRET)
def __call__(self, request):
ts = timer.now_ts(unit='ms')
request.headers.update({
'X-YS-APIKEY': YS_APP_KEY,
'X-YS-TIME': ts,
'X-YS-SIGNATURE': self._get_signature(request, ts)
})
return request加签部分php参考代码
<?php
$appSecret = "xxxxxxxx";
$timestamp = 毫秒时间戳;
//$timestamp = "123456789";
// 对Query参数按照字典对Key进行排序后,按照value1+value2方法拼接
$requestQuery = "value1value2value3";
// 请求体内容
$content = "payload";
// 得到签名值
$needSign = $timestamp. $requestQuery. $content;
$sign = hash_hmac('sha256', $needSign, $appSecret);
print $sign;
?>
<?php
function get_millisecond_timestamp() {
$microtime = microtime(true);
$timestamp = floor($microtime * 1000);
return $timestamp;
}
// 示例用法
echo get_millisecond_timestamp(); // 输出毫秒时间戳
?>敏感字段加密
开放接口将对呼入号码加密传输(需在安全设置打开开关,并且生成密钥),解密参考方法:
/**
* 解密
*
* @param cipherData 密文
* @param password 安全设置中的主密钥
* @return
* @throws Exception
*/
public static String decrypt(String cipherData, String password) throws Exception {
byte[] decode = Base64.getDecoder().decode(cipherData.getBytes(StandardCharsets.UTF_8));
byte[] decrypted = decryptByAesKey(decode, password);
return new String(decrypted, StandardCharsets.UTF_8);
}
private static Key generateKey(String password) throws NoSuchAlgorithmException {
KeyGenerator kgen = KeyGenerator.getInstance("AES");
SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
secureRandom.setSeed(password.getBytes());
kgen.init(128, secureRandom);
SecretKey secretKey = kgen.generateKey();
return new SecretKeySpec(secretKey.getEncoded(), "AES");
}
private static byte[] decryptByAesKey(byte[] encryptedData, String password)
throws IllegalBlockSizeException, BadPaddingException, NoSuchAlgorithmException,
NoSuchPaddingException, InvalidKeyException {
Key key = generateKey(password);
Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
cipher.init(2, key);
return cipher.doFinal(encryptedData);
}接口限流
每个的单接口的调用频率限制为:
统一返回
接口返回数据为JSON格式
| 参数 | 数值类型 | 参数说明 |
|---|---|---|
| requestId | String | 请求的唯一ID |
| code | Integer | 错误码。200表示设置成功。 |
| msg | String | 请求错误时,填错误提示信息 |
| data | Object、Array | code为200时,返回值有效。(注意:普通对象:{},数组:[{},{}]) |
通用错误码列表
| 错误码 | 含义 |
|---|---|
| 200 | OK |
| 90000 | 调用未知异常,联系开发处理 |
| 90001 | 租户未开通 |
| 90002 | 签名校验错误 |
| 90003 | 请求头中时间戳错误 |
| 90004 | 请求格式校验错误 |
| 90005 | 错误参数:xxxx |
| 90006 | 导入超限 |
| 90007 | 无效的请求地址 |
其他:标记去除线参数后续计划支持
这篇文档有帮助吗?
查询未知问题
查询未知问题。 查询指定机器人在时间范围内的未知问题。type 可选 cluster(聚类问题)或 all(全部未知问题);时间范围最长 90 天。 请求字段限制: - robotId:必填,机器人 ID - status:选填,处理状态:pending-待处理,ignored-已忽略,可选值:pending/ignored - startTime:选填,开始时间,毫秒时间戳 - endTime:选填,结束时间,毫秒时间戳;查询范围最长 90 天 - pageSize:选填,每页条数,默认 20,最大 200,最大值 200,默认值:20 - curPage:选填,页码,默认 1,默认值:1 - type:选填,未知问题类型:cluster-聚类问题,all-全部未知问题;查询相似未采纳时不使用,可选值:cluster/all
业务枚举定义
网易云商呼入机器人 业务枚举定义。