七鱼开发文档中心
总览服务端 APIWebiOSAndroid鸿蒙Uniapp微信小程序插件三方接入
呼入机器人

引导/加签/加解密

网易云商呼入机器人 引导/加签/加解密。

调用说明

支持的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,非特殊场景,建议业务方异步处理逻辑,提前返回。
  • 一般情况下系统不会对回调业务方接口的响应做处理,如有特殊场景,机器人需要处理业务方返回,返回结构参考统一返回格式。

回调数据结构

参数数值类型参数说明
requestIdString请求的标识ID(不是请求的唯一ID,不要幂等处理),排查问题时带上此ID
dataTypeString回调类型
dataObject、Array普通对象:{},数组[{}]

支持回调接口类型:

回调接口描述dataType补充说明
呼入进线事件CALL_START有进线就会回调,无论是否接通
呼入结束事件CALL_END

权限校验

请求公共参数

公共参数(HTTP Header)是用于接口签名的参数,如非必要,在每个接口单独的接口文档中不再对这些参数进行说明,但每次请求均需要携带这些参数,才能正常发起请求。

公共参数需要统一放到 HTTP Header 请求头部中。

参数名称类型是否必选描述
X-YS-APIKEYString是用户的appkey,可以在外呼系统管理后台获取
X-YS-TIMEString是当前的时间戳(毫秒:System.currentTimeMillis()+"") 注意:如果与服务器时间相差超过5分钟,会引起签名过期错误。
X-YS-SIGNATUREString是用户计算的签名串
X-YS-APPTYPEString否如果是外部短信渠道则必传,传入渠道标签(渠道标签需要申请)

加/验签规则

在第一次使用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格式

参数数值类型参数说明
requestIdString请求的唯一ID
codeInteger错误码。200表示设置成功。
msgString请求错误时,填错误提示信息
dataObject、Arraycode为200时,返回值有效。(注意:普通对象:{},数组:[{},{}])

通用错误码列表

错误码含义
200OK
90000调用未知异常,联系开发处理
90001租户未开通
90002签名校验错误
90003请求头中时间戳错误
90004请求格式校验错误
90005错误参数:xxxx
90006导入超限
90007无效的请求地址

其他:标记去除线参数后续计划支持

这篇文档有帮助吗?

目录