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

Node.js非Web服务应用中Twilio呼叫获取用户按键输入方案

实现方案

Twilio语音呼叫的按键采集是异步流程,发起呼叫的接口仅负责触发外呼,无法同步返回用户按键结果,必须通过回调接收采集到的DTMF(电话键盘)输入。针对非Web服务类Node.js应用,不需要改造为常驻Web服务,用临时HTTP服务+内网穿透的方式即可最小成本实现需求,全程代码都在业务项目内,不需要额外部署独立服务。

前置依赖

先安装需要的npm包:

npm install twilio express ngrok
  • twilio:Twilio官方Node.js SDK
  • express:用来临时启动极简HTTP回调接收服务
  • ngrok:临时内网穿透工具,给本地服务生成公网可访问的地址,供Twilio推送回调

完整实现代码

首先封装带按键采集的外呼方法,返回Promise可以直接用await接收结果:

const Twilio = require('twilio');
const express = require('express');
const ngrok = require('ngrok');
// 初始化Twilio客户端
const TwilioClient = Twilio(
  process.env.TWILIO_ACCOUNT_SID,
  process.env.TWILIO_AUTH_TOKEN
);

/**
 * 发起外呼,播放TTS并采集用户按键输入
 * @param {string} toNumber 被叫手机号
 * @param {string} ttsText 要播放的TTS内容
 * @returns {Promise<string>} 用户输入的按键值
 */
async function makeCallWithKeypad(toNumber, ttsText) {
  return new Promise(async (resolve, reject) => {
    // 临时启动HTTP服务,仅接收本次呼叫的回调
    const app = express();
    app.use(express.urlencoded({ extended: true }));
    // 监听随机可用端口,不占用固定端口
    const server = app.listen(0);
    const listenPort = server.address().port;

    try {
      // 生成临时公网回调地址
      const publicCallbackHost = await ngrok.connect(listenPort);

      // 配置按键结果接收路由
      app.post('/keypad-callback', (req, res) => {
        // req.body.Digits就是用户输入的按键内容
        const inputDigits = req.body.Digits;
        // 返回挂断指令结束呼叫
        res.type('xml').send('<Response><Hangup/></Response>');
        // 回收资源:关闭隧道、关闭临时HTTP服务
        await ngrok.kill();
        server.close();
        // 返回按键结果
        resolve(inputDigits);
      });

      // 动态生成TwiML指令:播放TTS + 采集按键
      const callTwiml = `
        <Response>
          <Gather 
            numDigits="1" 
            timeout="10" 
            action="${publicCallbackHost}/keypad-callback" 
            method="POST"
          >
            <Say>${ttsText}</Say>
          </Gather>
          <!-- 用户超时未按键直接挂断 -->
          <Hangup/>
        </Response>
      `;

      // 发起外呼
      await TwilioClient.calls.create({
        twiml: callTwiml,
        to: toNumber,
        from: process.env.TWILIO_PHONE_NUMBER
      });
    } catch (err) {
      // 出错时回收资源
      await ngrok.kill();
      server.close();
      reject(err);
    }
  });
}

替换原有业务代码即可直接使用:

const args = message.content.split(' ')
const phNumber = args[1]
const ttsMessage = args.slice(2).join(' ')

if (phNumber) {
  try {
    // 等待呼叫流程完成,拿到用户按键输入
    const userKeypadInput = await makeCallWithKeypad(phNumber, ttsMessage);
    // 这里直接写后续业务逻辑即可,userKeypadInput就是用户按的键值
    console.log('采集到用户按键:', userKeypadInput);
  } catch (err) {
    console.error('外呼流程出错:', err);
  }
}

配置说明

  • 如果你需要采集多位输入(比如6位验证码),修改<Gather>标签的numDigits属性为对应长度即可;如果需要用户按#号确认输入,删掉numDigits属性,添加finishOnKey="#"配置即可
  • timeout属性为用户无输入的超时时间,单位为秒,可根据业务调整
  • 如果TTS内容包含XML特殊字符(比如&、<、>),需要提前做转义,避免TwiML解析出错
  • ngrok首次使用需要注册免费账号配置authtoken,免费版足够支撑低频呼叫场景
  • 如果你自己有公网服务器,不需要用ngrok,直接在公网服务器上监听固定端口,把回调地址换成你的公网地址即可,逻辑不需要改动
  • 临时HTTP服务和ngrok隧道只会在呼叫发起时启动,拿到结果后自动关闭,不会让你的应用变成常驻Web服务,完全适配CLI工具、聊天机器人、定时任务脚本等非Web类Node.js应用

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 22:01:13