闪联 WebRTC 语音平台对接文档
| 项目 | 内容 |
|---|---|
| 文档版本 | V1.0 |
| 发布日期 | 2026 年 9 月 2 日 |
| 目标读者 | 后端 / 前端开发者 |
| 前置知识 | HTTP API 、 WebSocket 、 SIP 与 WebRTC 基本概念 |
- 八、附录
一、概述
第三方系统通过「号码池 API + SIP/WebRTC 坐席 +事件 Webhook」三个接口,即可将闪联的网页通话能力集成进自己的业务系统。
闪联提供基于 WebRTC 的网页软电话能力:坐席无需安装任何客户端,在浏览器(含微信内置浏览器)里即可注册上线、拨打和接听 PSTN 电话,全程支持通话录音、事件推送与话单回传。
对接总流程
二、一句话集成(网页组件)
「一句话集成」是闪联推荐的接入方式:客户在自己的网页里加一行 <script>,即可获得完整的网页通话能力(通话按钮、弹窗、录音、埋点),无需自建服务端、也无需接触任何 API Key。
2.1 集成代码
<script src="https://shanlian.bj-yzqt.com/widget/sl.js" data-site="SL-xxxxx" async></script>
data-site 为闪联分配的站点标识,全局唯一,由闪联在开通时提供。
2.2 组件配置项
| 属性 | 必填 | 默认值 | 说明 |
|---|---|---|---|
| data-site | 是 | — | 站点标识,闪联分配(全局唯一) |
| data-api | 否 | https://api.bj-yzqt.com | 通话 API 地址 |
| data-position | 否 | bottom-right | 按钮位置:bottom-right / bottom-left / inline |
| data-theme | 否 | #6c63ff | 按钮主色 |
| data-text | 否 | 免费通话 | inline 模式按钮文案 |
| data-tip | 否 | true | 是否显示「不会获取手机号」气泡 |
2.3 事件回调
组件挂载全局对象 ShanLian,可用 ShanLian.on 订阅通话事件:
<script>
ShanLian.on('call:connected', function (e) { /* 已接通 */ });
ShanLian.on('call:ended', function (e) { console.log('通话时长', e.duration, '秒'); });
</script>
| 事件 | 触发时机 | 字段 |
|---|---|---|
| ready | 组件就绪 | site, title |
| call:start | 开始呼叫 | — |
| call:connected | 接通 | — |
| call:ended | 挂断 | duration(秒) |
| call:failed | 失败 | cause |
| error | 组件错误 | code, msg |
2.4 安全机制
- siteId 为公开标识,泄露无害;真正的安全边界是「域名白名单 + 短时效临时凭证 wt」。
- wt 有效期 600 秒,每次呼叫前由组件自动刷新,不会过期。
- API Key 永不下发到浏览器,服务端校验域名白名单后才签发 wt。
- 组件用 Shadow DOM 隔离样式,不与宿主页面冲突;jssip 点击时才懒加载,不影响宿主首屏。
2.5 通话体验
- 通话接通前明示「本次通话将被录音」,符合合规要求。
- 坐席忙或非工作时间,AI 客服自动兜底接听,7×24 必被接住。
- 已实测兼容微信 / 抖音 / 百度内置浏览器(iOS 与 Android 双端)。
三、对接准备
本章列出正式联调前需要向闪联申请的账号凭证、平台侧配置项,以及坐席侧的网络放行要求。
3.1 账号与凭证
对接前由闪联分配:
| 项目 | 说明 | 示例 |
|---|---|---|
| accountNo | 9 位账号,号码池按此前缀分配坐席号 | 860100000 |
| API Key | 调用号码池 API 的 Bearer 令牌 | sk-9d8c7b6a5g4c3a2b1a0f9e8d7c6b5b4e(仅示例,真实需向闪联申请) |
| 事件推送 URL | 贵方接收呼叫事件 / 话单的 HTTPS 接口 | https://your-domain.com/api/callevent |
| 推送 Authorization (可选) | 平台推送事件时携带的鉴权头,无需鉴权可留空 | Bearer your-token |
3.2 网络端口要求
| 端口 | 协议 | 方向 | 用途 |
|---|---|---|---|
| 8059 | TCP/HTTP | 业务系统 → 平台 | 号码池与凭证 API |
| 8089 | TCP/WSS | 浏览器 → 平台 | SIP over WebSocket (注册与呼叫信令) |
| 13478 | UDP | 浏览器 → 平台 | STUN/TURN (媒体中继) |
| 15060 | UDP | SIP 终端 → 平台 | SIP (非 WebRTC 终端接入用) |
| 18098 | TCP/HTTP | 业务系统 → 外呼平台 | 外呼 API |
四、接口一:号码池与凭证 API
Base URL:http://shanlian:8059(仅示例)
4.1 认证
所有接口必须携带 Bearer 令牌,令牌即账号的 API Key:
Authorization: Bearer sk-8c7b6a5f4e3d2c1b0a9f8e7d6c5d4c3a(仅示例,真实需向闪联申请)
| HTTP 状态 | body | 含义 |
|---|---|---|
| 401 | {"code":-4,"ret":"Missing Authorization Header"} | 缺少 Authorization 头 |
| 400 | {"code":-5,"ret":"Invalid Authorization Scheme, expected Bearer"} | 不是 Bearer 格式 |
| 403 | {"code":-1,"ret":"Forbidden"} | 令牌与账号不匹配 |
4.2 取号(分配坐席号码)
GET /np/account/{accountNo}?action=fetch
从该账号的号码池中取一个空闲且在线的 SIP 号码并标记为占用。成功响应:
{
"code": 0,
"peers": "86010000001",
"stun_server": "stun:shanlian.com:13478",
"turn_server": "turn:shanlian.com:13478",
"ws_server": "wss://shanlian.com:8089/ws",
"peer_server": "shanlian.com:15060"
}
| 字段 | 说明 |
|---|---|
| peers | 分到的坐席号码( SIP 用户名 = 密码 = 此号码)。池空时为空串,需重试 |
| stun_server / turn_server | ICE 服务器地址,直接填入 WebRTC iceServers |
| ws_server | SIP over WSS 接入地址 |
| peer_server | SIP 域(构造 SIP URI 用) |
注意事项:
- 占用有效期 60 秒:取号后 60 秒内未完成注册/呼叫,号码自动回收;通话结束应主动调 release 放号。
- 取号失败(并发打满、号码离线)建议间隔 1 秒重试,最多 3 次。
- 响应中的四个 server 字段请动态使用,不要在前端硬编码,平台迁移时前端零改动。
4.3 放号(释放坐席号码)
GET /np/peers/{peer}?action=release
{peer} 为 fetch 分到的完整号码。响应:
{"code": 0, "ret": "success"}
4.4 获取 TURN 临时凭证
GET /np/account/{accountNo}?action=get_turn_credentials
{"username": "1788310476", "password": "aBcDeF...="}
- 采用 TURN REST API 模式:username 为过期时间戳(有效期 86400 秒),password 为 base64(HMAC-SHA1(username, shared_secret))。
- 把两个字段原样填入 WebRTC iceServers 的 username / credential 即可。
五、接口二:SIP/WebRTC 坐席接入
坐席侧通过 jssip 在浏览器内完成 SIP 注册与呼叫,本章给出接入参数与可直接复用的代码示例。
5.1 接入参数
| 参数 | 取值 | 来源 |
|---|---|---|
| WSS 地址 | wss://shanlian.com:8089/ws | fetch 响应 ws_server |
| SIP URI | sip:{peers}@{peer_server} | 拼接 |
| 用户名 / 密码 | 均为 fetch 分到的 peers 号码 | fetch 响应 |
| ICE 服务器 | turn:{turn_server} + 临时凭证 | fetch + get_turn_credentials |
5.2 注册上线(jssip 3.x 示例)
const socket = new JsSIP.WebSocketInterface(ws_server);
const ua = new JsSIP.UA({
uri: `sip:${peers}@${peer_server}`,
authorization_user: peers,
password: peers, // 密码与用户名相同
sockets: [socket],
session_timers: false,
register: true
});
ua.on('registered', () => console.log('已注册上线'));
ua.on('registrationFailed', e => console.log('注册失败', e.cause));
ua.start();
5.3 主叫(坐席拨出)
注册成功后向目标号码发 INVITE。平台接入号规则为 90 + 被叫号码(固话省略区号前导 0,具体路由以平台 dialplan 为准,对接时请与闪联确认号段):
const pcConfig = {
iceTransportPolicy: 'relay', // 只用 TURN 中继,起呼快、行为确定
iceServers: [{ urls: turn_server, username: cred.username, credential: cred.password }]
};
const session = ua.call('01012345678', {
mediaConstraints: { audio: true, video: false },
pcConfig,
eventHandlers: {
icecandidate: (e) => {
if (e.candidate && e.candidate.type === 'relay') e.ready();
}
}
});
5.4 被叫(接听平台回呼)
平台外呼或 PSTN 呼入时,SipServer 会向坐席号码发 INVITE。该版本 jssip 的 answer() 不处理 eventHandlers 选项,被叫侧必须在 newRTCSession 回调里提前挂 icecandidate:
ua.on('newRTCSession', (e) => {
if (e.originator !== 'remote') return;
const s = e.session;
s.on('icecandidate', (ev) => {
if (ev.candidate && ev.candidate.type === 'relay') ev.ready();
});
// 弹出接听界面;用户点击接听时:
// s.answer({ mediaConstraints: {audio:true, video:false}, pcConfig });
});
5.5 播放远端声音与移动端兼容
session.connection.addEventListener('track', (e) => {
remoteAudio.srcObject = e.streams[0];
remoteAudio.play().catch(() => {}); // iOS 微信需用户交互后才能出声
});
iOS 微信浏览器有自动播放限制,需在首次点击事件中做一次 play()+pause() 预热(参考实现 call.js 已内置)。
5.6 参考实现
平台提供两个参考文件,可直接作为对接起点:
- call.js:完整的浏览器软电话实现(注册、主叫、被叫、计时、放号)。
- call.php:服务端代理示例——演示业务系统如何用 Bearer 令牌调用 fetch / release / get_turn_credentials 并转发外呼请求。
六、接口三:事件推送 Webhook
通话生命周期中的关键事件(建立、振铃、接听、话单)由平台主动推送到贵方配置的 URL,本章定义推送方式与报文字段。
6.1 推送方式
平台向账号配置的推送 URL 发起 HTTP POST:
- URL 上附加 ?timestamp={毫秒时间戳} 查询参数
- 请求头 Content-Type: application/json;若账号配置了推送 Authorization 则一并携带
- 平台侧超时 2 秒;贵方接口应尽快返回 200,重逻辑异步处理
6.2 公共字段
| 字段 | 说明 |
|---|---|
| caller | 主叫号码 |
| callee | 被叫号码 |
| event_name | 事件名,见 5.3 |
| event_time | 事件时间,毫秒时间戳 |
| call_id | 呼叫 UUID ,同一通电话所有事件一致 |
| request_id | 外呼 API 传入的 requestid (坐席主叫时为空) |
| task_id | 任务 ID (预留,通常为空) |
| pay_load | 事件负载,仅 cdr 事件携带 |
| account_no | 归属账号 |
6.3 事件清单
| 事件 | 触发时机 |
|---|---|
| callIn | 坐席侧呼叫建立( WebRTC 腿接通) |
| secondRinging | 第二腿( PSTN 侧)开始振铃 |
| secondAnswer | 第二腿接听,双方正式通话 |
| cdr | 通话结束,推送完整话单 |
6.4 cdr 事件负载
pay_load 为 CDR 对象数组,一通电话包含两腿(坐席腿 + PSTN 腿):
| 字段 | 说明 |
|---|---|
| caller / callee | 该腿主被叫 |
| start_time / answer_time / end_time | 毫秒时间戳;未应答时 answer_time 为 0 |
| hangup_side | 挂机方: caller / callee |
| call_type | 11 = 坐席呼入腿, 12 = 平台呼出腿 |
| is_record | 1 = 有录音 |
| record_file | 录音文件名( mp3 ),命名规则见第七节 |
| out_call_type | 呼叫方向标记(如 callin ) |
| call_id / request_id | 与公共字段一致 |
6.5 cdr 推送示例
{
"caller": "86010000001",
"callee": "01012345678",
"event_name": "cdr",
"event_time": 1788310400000,
"call_id": "f47ac10b58cc",
"request_id": "1234567890",
"task_id": "",
"account_no": "860100000",
"pay_load": [
{"caller":"86010000001","callee":"01012345678","call_type":"11","answer_time":1788310398000,"end_time":1788310400000,"is_record":1,"record_file":"860100000-20260902-f47ac10b58cc-86010000001.mp3"}
]
}
七、录音
- 通话全程录音,服务端生成 WAV 后转 MP3。
- 命名规则:{accountNo}-{yyyyMMdd}-{callUUID}-{坐席号码}.mp3,与 cdr 事件中的 record_file 一致。
- 录音文件的下载方式(HTTP 路径 / 对象存储)由闪联在开通时提供。
八、附录
错误码速查表,供开发与运维随时翻阅。
错误码速查
| code | HTTP | 含义 |
|---|---|---|
| 0 | 200 | 成功 |
| -1 | 403 | Bearer 令牌与账号不匹配 |
| -4 | 401 | 缺少 Authorization 头 |
| -5 | 400 | Authorization 不是 Bearer 格式 |
| -10 | 404 | 站点(siteId)不存在 |
| -11 | 403 | 域名未授权(不在白名单) |
| -12 | 401 | wt 缺失或签名错误 |
| -13 | 401 | wt 已过期 |
| -14 | 403 | siteId 与 wt 不匹配 |
| -20 | 200 | 号码池空 / 并发打满 |