Vox平台-API接入文档V1.0.4
1、修订记录
| 版本 | 修订内容 | 修订人 | 修订时间 |
|---|---|---|---|
| V1.0.1 | 编写初稿 | 李欢 | 2026-03-19 |
| V1.0.2 | 完善接口规范 | 李欢 | 2026-03-26 |
| V1.0.3 | 外呼接口增加扩展字段,实现额外扩展功能 | 李欢 | 2026-05-11 |
| V1.0.4 | 外呼接口增加botType和agentProfile配置 | 李欢 | 2026-05-20 |
2、概述
2.1 接入流程
- 商家与泰迪熊移动签订相关合作授权协议。
- 商家通过统一平台注册企业账号,并提交资质认证。
- 商家联系泰迪熊审核账号资质,并分配相关权限。
- 泰迪熊提供企业账号appId和签名秘钥。
2.2 安全机制
2.2.1 通讯安全
- 泰迪熊移动商家开放平台基于https协议搭建,并要求合作商家同样采用https协议
- 泰迪熊移动商家开放平台通过IP白名单机制限制访问来源,非白名单无法访问服务。
- 泰迪熊移动商家开放平台会根据访问商家检测访问量和访问频率,超出预定数值将自动发出预警,必要时会临时切断商家访问,为避免此情况发生,请在申请阶段如实告知预计访问量。
2.2.2 会话安全
- 泰迪熊移动商家开放平台采用签名校验安全机制,通过接口信息和客户唯一secret生成签名。
2.2.3 生成签名方式
将参与生成签名的参数按顺序拼接字符串,按对应加密方式获得签名。
加密方式
hmac-sha256
参数
| 参数 | 类型 | 描述 | 备注 |
|---|---|---|---|
| method | String | 请求方式 | 需转为大写,如:GET/PUT/POST/DELETE/OPTIONS/HEADER等HTTP协议中定义的method |
| path | String | 接口路径 | 例:/vox/api/xx |
| uriParams | String | uri参数 | uri中"?"后的部分,不包括"?",例如:key1=value 1&key2 = value 2 |
| appid | String | 企业账号appId | 由泰迪熊分配 |
| dateGMT | String | 请求时间 | GMT格式的时间,必须与HTTP Header中的HMAC-DATE一致 |
| secret | String | 企业账号的密钥 | 由泰迪熊分配 |
代码示例
public class SignatureHelper {
private static String signatureHmacSHA256(String message, String secret) {
try {
Mac hasher = Mac.getInstance("HmacSHA256");
hasher.init(new SecretKeySpec(secret.getBytes(), "HmacSHA256"));
byte[] hash = hasher.doFinal(message.getBytes("UTF-8"));
// to base64
return DatatypeConverter.printBase64Binary(hash);
} catch (NoSuchAlgorithmException e) {
} catch (InvalidKeyException e) {
}
return null;
}
/**
* @param method HTTP Method,例如:GET/PUT/POST/DELETE/OPTIONS/HEADER等HTTP协议中定义的method,必须是大写。
* @param path 接口路径,例如:/sm/api/statistic/event,必须以"/"开头。
* @param uriParams uri中"?"后的部分,不包括"?",例如:key1=value 1&key2 = value 2,参数字符串要按照各参数名称的字符顺序进行排列。
* @param appid 主账号的appid
* @param dateGMT GMT格式的时间,必须与HTTP Header中的HMAC-DATE一致
* @param secret 主账号的密钥
* @return 生成好的签名
*/
public static String buildSignature(String method, String path, String uriParams, String appid, String dateGMT, String secret) {
StringBuilder s = new StringBuilder();
s.append(method.toUpperCase() + "\n");
if (!path.startsWith("/")) {
path = "/" + path;
}
s.append(path + "\n");
if (uriParams == null) {
uriParams = "";
}
s.append(sortParam(uriParams.split("&")) + "\n");
s.append(appid + "\n");
s.append(dateGMT + "\n");
s.append("HMAC-APPID:" + appid + "\n");
String message = s.toString();
return buildSignature(message, secret);
}
private static String sortParam(String[] params) {
Arrays.sort(params);
StringBuilder builder = new StringBuilder();
for (int i = 0; i < params.length; i++) {
builder.append(params[i]);
if (i < params.length - 1) {
builder.append("&");
}
}
return builder.toString();
}
public static String buildSignature(String message, String secret) {
return signatureHmacSHA256(message, secret);
}
}// 加密Demo
class SignatureHelperTest {
@Test
void buildSignature() {
String appid = "demouser";
String secret = "demosecret";
String method = "POST";
String path = "/sms/api/xx";
String uriParams = "a=b&x=y";
//此处生成的GMT格式时间在发起HTTP请求时会使用到
SimpleDateFormat sdf = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss 'GMT'", Locale.US);
sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
String dateGMT = sdf.format(new Date());
//获取签名
String signature = SignatureHelper.buildSignature(method, path, uriParams, appid, dateGMT, secret);
System.out.println(signature);
//j+DiLfTZoYyviAjSXejL0RyAvPHa8z4YR72n2/YUD4I=
}
}2.3 域名信息
| 环境 | 域名地址 |
|---|---|
| 生产环境 | https://vox.teddymobile.cn |
2.4 术语解释
2.5 公共参数
- HTTP请求头必须添加5个HMAC-xxx参数
| 参数名 | 类型 | 描述 | 备注 |
|---|---|---|---|
| HMAC-APPID | String | appId | 泰迪熊分配的appId。 |
| HMAC-DATE | String | 请求时间 | GMT格式的时间,必须与签名时生成的GMT时间保持一致。务必确保服务器时钟准确,时钟偏移过大会导致身份认证失败。 |
| HMAC-SIGNATURE | String | 签名 | 例:j+DiLfTZoYyviAjSXejL0RyAvPHa8z4YR72n2/YUD4I= |
| HMAC-ALGORITHM | String | 加密算法 | 固定值 hmac-sha256 |
| HMAC-SIGNED-HEADERS | String | 签名头信息 | 固定值 HMAC-APPID |
- 响应体
| 参数名 | 类型 | 是否必填 | 描述 | 备注 |
|---|---|---|---|---|
| code | int | 是 | 状态码 | 成功:0 |
| msg | String | 否 | 返回说明 | |
| data | Object | 否 | 具体业务数据 | |
| time | long | 是 | 响应时间 | 时间戳 |
3、业务接口
3.1 发起外呼
发起一个 outbound call(外呼)请求。
接口地址:/vox/v1/outbound
请求方法:POST
请求参数:
通话类bot请求示例:
{
"appId": "d63dd1e9-3aa3-4f88-8d3e-2be7461dff56",
"botid": "bot_10001",
"callee": "131****1234",
"requestId": "66016fe1fa414ca48596aad35c457106"
}通知类bot请求示例:
{
"appId": "d63dd1e9-3aa3-4f88-8d3e-2be7461dff56",
"botid": "bot_10001",
"callee": "131****1234",
"requestId": "req_20260313144300001",
"extra": "{\"notification\":{\"text\":\"您好,这是一条通知\",\"times\":2}}"
}自定义bot请求示例:
{
"appId": "d63dd1e9-3aa3-4f88-8d3e-2be7461dff56",
"botid": "",
"callee": "131****1234",
"requestId": "66016fe1fa414ca48596aad35c457106",
"botType": "custom",
"extra": "{\"agent_profile\":{\"name\":\"小美\",\"gender\":\"女\",\"age\":25,\"role\":\"销售顾问\",\"communicationStyle\":[\"热情\",\"专业\",\"亲切\"],\"background\":\"春季新品推广活动\",\"goals\":\"了解客户购买意向,促成交易\",\"skills\":\"产品介绍、需求挖掘、促成交易\",\"workflow\":\"问候 -> 了解需求 -> 介绍产品 -> 处理异议 -> 促成合作\",\"constraint\":\"保持礼貌、尊重对方意愿、不强制推销\",\"openingPrompt\":\"您好,我是小美,春季新品推广活动的销售顾问\"}}"
}请求字段说明:
| 字段 | 类型 | 必填 | 说明 | 示例 |
|---|---|---|---|---|
| appId | string | 是 | 身份标识 | d63dd1e9-3aa3-4f88-8d3e-2be7461dff56 |
| botid | string | 是 | 机器人 ID(平台侧生成),当botType为"custom"时非必填 | 10001 |
| callee | string | 是 | 被叫号码(用户手机号) | 13800138000 |
| requestId | string | 是 | 请求 ID(由调用方生成,用于幂等性保证) | 66016fe1fa414ca48596aad35c457106 |
| botType | string | 否 | Bot 类型,当前仅支持 "custom"(自定义Bot) | custom |
| extra | JSONString | 否 | 扩展功能字段 | |
| extra.voiceType | string | 否 | TTS音色类型编码。<br/>可选值及适用场景:<br/>• 0 - 知愈(女):情绪安抚、心理陪伴、售后回访、不知道选什么<br/>• 1 - 安辰(男):给长辈/老人做的任何语音内容<br/>• 2 - 景珩(男):商务沟通、企业合作、科普讲解类内容<br/>• 3 - 知言(女):发通知、公告、正式播报类内容<br/>• 4 - 星苒(女):日常闲聊、陪伴、电商带货、生活服务类内容 | 0 |
| extra.agent_profile | Object | 否 | 自定义Bot配置,当botType为"custom"时必填 | |
| extra.agent_profile.name | string | 是 | Bot名称 | 小美 |
| extra.agent_profile.gender | string | 是 | Bot性别 | 女 |
| extra.agent_profile.age | int | 是 | Bot年龄 | 25 |
| extra.agent_profile.role | string | 是 | Bot角色定位 | 销售顾问 |
| extra.agent_profile.communicationStyle | array | 是 | 沟通风格列表 | ["热情", "专业", "亲切"] |
| extra.agent_profile.background | string | 是 | 业务背景 | 春季新品推广活动 |
| extra.agent_profile.goals | string | 是 | Bot目标 | 了解客户购买意向,促成交易 |
| extra.agent_profile.skills | string | 是 | Bot技能 | 产品介绍、需求挖掘、促成交易 |
| extra.agent_profile.workflow | string | 是 | 工作流程 | 问候 -> 了解需求 -> 介绍产品 -> 处理异议 -> 促成合作 |
| extra.agent_profile.constraint | string | 是 | 约束条件 | 保持礼貌、尊重对方意愿、不强制推销 |
| extra.agent_profile.openingPrompt | string | 是 | 开场白 | 您好,我是小美,春季新品推广活动的销售顾问 |
| extra.notification | Object | 否 | 通知类bot相关的额外配置内容 | |
| extra.notification.text | string | 是 | 通知内容,extra.notification不为空时必填 | 您好,这是一条通知 |
| extra.notification.times | int | 否 | 播放次数,默认为播放1次 | 2 |
响应示例:
{
"code": 0,
"msg": "success",
"data": {
"requestId": "66016fe1fa414ca48596aad35c457106",
"status": "accepted"
}
}响应字段说明:
| 字段 | 类型 | 说明 | |
|---|---|---|---|
| code | int | 响应码,0 表示成功 | |
| msg | string | 响应消息 | |
| data.requestId | string | 请求 ID,与请求中的 requestId 一致 | |
| data.status | string | 任务状态:accepted(已成功接收) \ | failed(失败) |
HTTP 状态码:
202 Accepted- 请求已被接受处理
4、Webhook 回调
4.1 SSE 协议方式实现
Vox通过 HTTP POST + SSE(Server-Sent Events)流式交互协议与第三方开发者系统进行实时双向通信。
4.1.1 协议概述
- 方向:Vox 作为 HTTP Client 主动发起请求,第三方系统作为 HTTP Server 提供 API
- 传输方式:单次 HTTP POST 请求,第三方返回
text/event-stream持续流式响应 - 会话连续性:通过
conversation_uuid维护,而非长连接 - 适用场景:需要流式输出、支持打断的多轮对话场景
4.1.2 接口地址
由第三方开发者自行提供 HTTPS 端点地址,在创建机器人时配置。
4.1.3 请求方法
POST
4.1.4 请求头
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| Content-Type | String | 是 | application/json |
| Accept | String | 是 | text/event-stream |
4.1.5 请求体
{
"turn": 6,
"calltype": "inbound",
"callee": "18600013645",
"caller": "+8652780515543",
"callid": "4e0c4ecf-1ad6-42c3-b343-e1eb90ee18c6",
"requestid": "1ad64ecf-b343-42c3-1ad6-e1eb90eeb343",
"message": "好,请问你是哪里的人?"
}字段说明:
| 字段 | 类型 | 必填 | 说明 | 示例 |
|---|---|---|---|---|
| message | String | 是 | 用户输入文本(电话接通后第一次请求为"") | "我想了解套餐" |
| callid | String | 是 | 通话的唯一id标识 | |
| requestid | String | 是 | 本次请求的唯一id标识 | |
| caller | String | 是 | 主叫号码 | |
| callee | String | 是 | 被叫号码 | |
| calltype | String | 是 | inbound-呼入 outbound-呼出 | |
| turn | int | 是 | 通话轮次,接通后第一次为1 |
4.1.6 SSE 响应协议
Media Type: text/event-stream
响应块类型:
1. 文本流块(持续返回文本片段)
data: {"id":"1ad64ecf-b343-42c3-1ad6-e1eb90eeb343",
"created":1678901234,"message":"你好"}继续:
data: {"id":"1ad64ecf-b343-42c3-1ad6-e1eb90eeb343",
"created":1678901234,"message":",我愿意倾听你。"}2. 完成标记(必须发送)
data: [DONE]响应字段说明:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| id | String | 是 | 消息唯一标识,与请求体中requestid一致,原样返回 |
| created | Long | 是 | 消息创建时间戳 |
| message | String | 是 | 非空文本片段,Vox 按到达顺序拼接并切句 |
| action | JSON | 否 | 扩展功能字段,例:{"cmd": "hangup"}表示需要主动挂断 |
4.1.7 交互流程
1. 通话开始(call.started)
Vox -> 第三方:POST /developer-api
Request:
{
"turn": 1,
"calltype": "inbound",
"callee": "18600013645",
"caller": "+8652780515543",
"callid": "4e0c4ecf-1ad6-42c3-b343-e1eb90ee18c6",
"requestid": "1ad64ecf-b343-42c3-1ad6-e1eb90eeb343",
"message": ""
}
第三方 -> Vox: SSE Stream
data: {"id":"1ad64ecf-b343-42c3-1ad6-e1eb90eeb343",
"created":1678901234,"message":"您好"}
data: {"id":"1ad64ecf-b343-42c3-1ad6-e1eb90eeb343",
"created":1678901234,"message":",我愿意倾听你。"}
data: [DONE]2. 用户说话(asr.transcript)
Vox -> 第三方:POST /developer-api
Request:
{
"turn": 2,
"calltype": "inbound",
"callee": "18600013645",
"caller": "+8652780515543",
"callid": "4e0c4ecf-1ad6-42c3-b343-e1eb90ee18c6",
"requestid": "550e8400-e29b-41d4-a716-446655440000",
"message": "我想办理宽带业务"
}
第三方 -> Vox: SSE Stream
data: {"id":"550e8400-e29b-41d4-a716-446655440000",
"created":1678901234,"message":"好的"}
data: {"id":"550e8400-e29b-41d4-a716-446655440000",
"created":1678901234,"message":",请问您需要办理哪种宽带套餐?"}
data: [DONE]3. 用户打断(barge-in)
当用户打断时:
- Vox 主动关闭当前 SSE 流
- 标记旧 response 为废弃
- 基于新 transcript 发起下一轮 HTTP + SSE 请求
4.1.8 错误处理
| HTTP 状态码 | 含义 | Vox 处理方式 |
|---|---|---|
| 401 | 认证失败 | 结束通话或播放兜底文案 |
| 400 | 请求参数错误 | 记录协议错误并结束通话 |
| 429 | 限流 | 记录并播放兜底文案 |
| 500 | 第三方内部错误 | 记录并播放兜底文案 |
SSE 中断处理:
- 首包前中断:视为本轮失败
- 中途文本流中断且未收到
[DONE]:只播放已切出的内容,结束当前轮
4.1.9 特殊说明
- 打断语义:第三方无需提供 cancel API,Vox 通过关闭当前 SSE 连接完成打断
- 会话管理:Vox 不维护电话级长连接,每轮对话独立发起 HTTP 请求
- 文本切句:Vox 负责对接收到的文本流进行切句和 TTS 播放
5、附录
5.1通用状态码
| http状态码 | 含义 | 备注 |
|---|---|---|
| 401 | 未知用户,身份认证未通过 | 需联系泰迪熊配置开放平台用户信息 |
| 403 | 无接口访问权限,鉴权未通过 | 需联系泰迪熊开通接口权限 |