You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Twilio状态回调返回的TwiML未执行,通话中断求助

问题分析与解决方案

核心问题

你当前的实现存在两个关键错误,导致返回的TwiML无法生效:

  1. 错误依赖statusCallback控制通话流程:statusCallback仅用于接收通话状态的通知,当监听completed事件时,通话已经彻底终止,此时返回TwiML无法改变通话状态。
  2. 回调端点重复发送响应:代码中先调用res.send(twimlObject.toString()),又执行res.status(200).send(),会导致Twilio收到重复响应,无法正确解析TwiML。

另外,无人接听属于通话未完成的中间状态,你需要在通话终止前捕获该事件并处理,而非等通话结束后再触发回调。

修复步骤

1. 修改呼叫发起代码,添加action属性控制后续流程

Dial的action属性才是Twilio在拨号操作完成(包括无人接听、忙线等场景)后,用来获取后续TwiML的关键,此时通话尚未终止,可以继续执行播放语音等操作。同时保留statusCallback用于状态记录(如果需要):

await dial.client(
  {
    // 监听需要的状态事件,包括无人接听和通话完成
    statusCallbackEvent: "completed,no-answer",
    statusCallback: `handleFinishedCall?customerId=${customerId}&widgetId=${nextWidget || ""}&caller=${caller}`,
    statusCallbackMethod: "POST",
    // 指定Dial完成后Twilio请求的URL,用于返回后续TwiML
    action: `handleDialResult?customerId=${customerId}&widgetId=${nextWidget || ""}&caller=${caller}`,
    actionMethod: "POST"
  },
  idToCall
);

2. 创建handleDialResult端点处理拨号结果

通过DialStatus参数判断拨号状态,针对无人接听场景返回播放语音的TwiML:

router.post("/handleDialResult", async (req, res) => {
  const dialStatus = req.body.DialStatus;
  const twiml = new VoiceResponse();

  if (dialStatus === "no-answer") {
    // 无人接听时播放提示语音
    twiml.say('无人接听,正在为您播放提示信息');
  } else if (dialStatus === "busy") {
    twiml.say('对方正忙,请稍后再拨');
  } else if (dialStatus === "completed") {
    // 通话正常完成,直接挂断
    twiml.hangup();
  }

  res.type("text/xml");
  res.status(200).send(twiml.toString());
});

3. 修复原handleFinishedCall端点的重复响应问题

如果仍需要保留该端点用于状态记录,确保只发送一次响应:

router.post("/handleFinishedCall", async (req, res) => {
  // 这里仅处理状态通知,比如记录通话状态到数据库
  res.status(200).send("OK");
});

关键说明

  • DialStatus是Twilio返回的拨号结果参数,常见值包括no-answer(无人接听)、completed(通话完成)、busy(忙线)、failed(呼叫失败)。
  • action属性是控制通话后续流程的核心,而statusCallback仅用于被动接收状态通知,无法干预通话。

内容的提问来源于stack exchange,提问作者Supplementing

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 04:56:33