基于Node.js+React的Twilio通话应用默认转会议需求及问题咨询
基于Twilio的呼叫保持与呼入转接会议解决方案
针对呼叫保持失效、ENQUEUE指令导致呼叫断开的问题,最可靠的方案是将所有呼入呼叫默认接入Twilio会议,坐席通过Twilio Client加入该会议。Twilio会议原生支持呼叫保持、多参与者管理,完全规避队列方案的兼容性问题。
后端Node.js+Express实现
以下是处理呼入Webhook、生成Client Token的核心代码:
const express = require('express'); const twilio = require('twilio'); const VoiceResponse = twilio.twiml.VoiceResponse; const app = express(); // 解析Twilio请求体 app.use(express.urlencoded({ extended: true })); // 处理呼入呼叫的Webhook路由 app.post('/incoming-call', (req, res) => { const twiml = new VoiceResponse(); // 用呼叫SID生成唯一会议ID,避免冲突 const conferenceId = `call-${req.body.CallSid}`; // 配置呼入者进入会议:静音等待、播放等待音乐、坐席进入后启动会议 const conference = twiml.conference({ startConferenceOnEnter: false, endConferenceOnExit: true, waitUrl: 'http://twimlets.com/holdmusic?Bucket=com.twilio.music.ambient', muted: true }); conference.say('请稍等,正在为您连接坐席'); // 可选:发送通知到前端,告知有新呼叫及会议ID(可通过WebSocket/API实现) // 示例逻辑(需自行实现前端接收接口) // fetch('http://your-frontend-service/new-call', { // method: 'POST', // headers: { 'Content-Type': 'application/json' }, // body: JSON.stringify({ conferenceId, caller: req.body.From }) // }); res.type('text/xml'); res.send(twiml.toString()); }); // 生成Twilio Client授权Token(供前端坐席使用) app.get('/token', (req, res) => { const accountSid = 'YOUR_TWILIO_ACCOUNT_SID'; const authToken = 'YOUR_TWILIO_AUTH_TOKEN'; const apiKeySid = 'YOUR_TWILIO_API_KEY_SID'; const apiKeySecret = 'YOUR_TWILIO_API_KEY_SECRET'; const twimlAppSid = 'YOUR_TWILIO_TWIML_APP_SID'; const agentIdentity = 'agent-1'; // 自定义坐席标识 const token = new twilio.jwt.AccessToken( accountSid, apiKeySid, apiKeySecret, { identity: agentIdentity } ); const voiceGrant = new twilio.jwt.AccessToken.VoiceGrant({ outgoingApplicationSid: twimlAppSid, incomingAllow: true }); token.addGrant(voiceGrant); res.send({ token: token.toJwt() }); }); app.listen(3000, () => console.log('Backend running on port 3000'));
前端React.js实现
以下是坐席端接收呼叫、加入会议、切换呼叫保持的核心代码:
import React, { useState, useEffect } from 'react'; import { Device } from '@twilio/voice-sdk'; const AgentConsole = () => { const [twilioDevice, setTwilioDevice] = useState(null); const [activeCall, setActiveCall] = useState(null); const [pendingConference, setPendingConference] = useState(null); // 初始化Twilio Client设备 useEffect(() => { const initDevice = async () => { const tokenRes = await fetch('/token'); const { token } = await tokenRes.json(); const device = new Device(token); device.on('ready', () => console.log('Twilio Client 已就绪')); device.on('error', err => console.error('设备错误:', err)); setTwilioDevice(device); }; initDevice(); }, []); // 监听新呼叫通知(需自行实现后端到前端的通知机制,如WebSocket) useEffect(() => { // 示例:假设通过WebSocket接收新呼叫 // const socket = new WebSocket('ws://your-backend-service'); // socket.onmessage = (event) => { // const { conferenceId, caller } = JSON.parse(event.data); // setPendingConference({ conferenceId, caller }); // }; }, []); // 加入会议(接听呼叫) const answerCall = () => { if (!twilioDevice || !pendingConference) return; const connection = twilioDevice.connect({ params: { To: `conference:${pendingConference.conferenceId}`, StartConferenceOnEnter: true, EndConferenceOnExit: true } }); connection.on('connect', () => { setActiveCall(connection); setPendingConference(null); console.log('已接入会议'); }); connection.on('disconnect', () => { setActiveCall(null); console.log('已结束通话'); }); }; // 切换呼叫保持/恢复 const toggleHold = () => { if (!activeCall) return; const isOnHold = activeCall.isOnHold(); activeCall.hold(!isOnHold); console.log(isOnHold ? '已恢复通话' : '已保持通话'); }; return ( <div className="agent-console"> <h2>坐席控制台</h2> {pendingConference && ( <div className="pending-call"> <p>新呼叫来自: {pendingConference.caller}</p> <button onClick={answerCall}>接听</button> </div> )} {activeCall && ( <button onClick={toggleHold}> {activeCall.isOnHold() ? '恢复通话' : '保持通话'} </button> )} </div> ); }; export default AgentConsole;
替代方案:队列(ENQUEUE)问题排查
如果仍想使用队列方案,需检查以下几点:
- 确保Twilio队列SID配置正确,且队列在控制台中已创建
- ENQUEUE的TwiML格式正确:
<Response> <Enqueue waitUrl="http://twimlets.com/holdmusic?Bucket=com.twilio.music.ambient">support-queue</Enqueue> </Response> - 坐席需通过
<Dial><Queue>support-queue</Queue></Dial>指令接听队列呼叫,而非直接用Client连接 - 查看Twilio控制台的错误日志,定位呼叫断开的具体原因(如权限不足、waitUrl不可达)
内容的提问来源于stack exchange,提问作者Debashis Saha
相关产品推荐
相关产品推荐

