Node.js+Twilio自动呼叫状态异常:始终返回complete求助
问题根源分析
核心问题出在Twilio的状态判定逻辑:只有当呼叫振铃超时无人接听,且未执行任何应用层Twiml逻辑时,才会触发no-answer/failed等状态。而你的代码中,不管对方是否接听,Twilio都会直接执行初始的play/redirect操作,这会让Twilio判定呼叫已被"处理完成",因此始终返回completed状态,而非实际的未接听/失败状态。
具体修复方案
1. 先判断呼叫是否被接听,再执行业务逻辑
修改/initiateCall的Twiml生成逻辑,用<Dial>组件先处理呼叫振铃,只有当呼叫被接听后,才进入后续的音频播放/姓名收集流程:
router.post('/initiateCall', async (req, res) => { const { text, to, contactName, pulseId, linkedPulseId } = req.body; console.log('Request:', req.body); if (!text || !to) { return res .status(400) .send('Please provide both "text" and "to" in the request body.'); } try { const twiml = new twilio.twiml.VoiceResponse(); // 使用Dial组件处理呼叫振铃,指定接听后的回调端点 const dial = twiml.dial({ action: `${baseUrl}/twilio/handleCallAnswer?text=${encodeURIComponent(text)}&contactName=${contactName}`, method: 'POST', timeout: 30, // 振铃超时时间,按需调整 }); dial.number(to); const call = await client.calls.create({ from: process.env.TWILIO_PHONE_NUMBER, to: to, twiml: twiml.toString(), statusCallback: `${baseUrl}/twilio/callStatus?pulseId=${pulseId}&linkedPulseId=${linkedPulseId}`, statusCallbackEvent: [ 'initiated', 'ringing', 'answered', 'completed', 'no-answer', 'busy', 'failed', 'canceled', ], statusCallbackMethod: 'POST', // 明确指定POST方法 }); res.send({ message: 'Call initiated successfully', callSid: call.sid }); } catch (error) { console.error('Error during call initiation:', error.message); res.status(500).send('Failed to initiate call.'); } });
新增/handleCallAnswer端点,处理呼叫被接听后的业务逻辑:
router.post('/handleCallAnswer', async (req, res) => { const { DialCallStatus, text, contactName } = req.body; const twiml = new twilio.twiml.VoiceResponse(); // 仅当呼叫被接听时,执行后续音频播放/姓名收集 if (DialCallStatus === 'answered') { const response = await axios.post(`${baseUrl}/googleTTS/synthesize`, { text: decodeURIComponent(text), }); const audioUrl = response.data.url; if (contactName) { twiml.play(audioUrl); twiml.pause({ length: 1 }); } else { twiml.play(audioUrl); twiml.pause({ length: 1 }); twiml.redirect(`${baseUrl}/twilio/askName`); } } else { // 未接听则直接挂断 twiml.hangup(); } res.type('text/xml'); res.send(twiml.toString()); });
2. 简化/askName的状态检查逻辑
因为该端点只会在呼叫被接听后触发,所以可以简化状态判断:
router.post('/askName', async (req, res) => { const twiml = new twilio.twiml.VoiceResponse(); const retry = req.query.retry; if (req.body.CallStatus !== 'in-progress') { twiml.hangup(); res.type('text/xml'); res.send(twiml.toString()); return; } twiml .gather({ input: 'speech', action: '/twilio/confirmName', method: 'POST', speechTimeout: '2', language: 'en-US', }) .play( retry ? `${awsS3Url}/askNameRetry.wav` : `${awsS3Url}/askName.wav` ); twiml.redirect('/twilio/askName?retry=true'); res.type('text/xml'); res.send(twiml.toString()); });
3. 确保/callStatus正确接收状态
在状态回调端点中,直接使用Twilio发送的CallStatus参数,不要自行推断:
router.post('/callStatus', async (req, res) => { const { CallStatus, pulseId, linkedPulseId } = req.body; console.log('Received Call Status:', CallStatus); // 根据实际状态更新Monday.com switch(CallStatus) { case 'no-answer': // 处理未接听逻辑 break; case 'failed': // 处理呼叫失败逻辑 break; case 'completed': // 处理呼叫完成逻辑 break; // 其他状态按需处理 } res.sendStatus(200); // 必须返回200,否则Twilio会重复回调 });
关键注意事项
- 务必通过Twilio控制台的呼叫日志查看实际状态,确认Twilio是否发送了预期的状态值。
- 状态回调端点必须返回200 OK,否则Twilio会重复发送回调请求。
no-answer仅在呼叫振铃超时且未进入任何应用逻辑时触发,一旦Twilio执行了你的Twiml,就会标记为completed。
内容的提问来源于stack exchange,提问作者idiotappowner
相关产品推荐
相关产品推荐

