twilio-voice
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
Chinesetwilio-voice
Twilio Voice
Purpose
用途
Enable OpenClaw to design, implement, and operate Twilio Programmable Voice in production:
- Build inbound/outbound call flows using TwiML (,
<Dial>,<Conference>,<Gather>,<Record>with Amazon Polly voices).<Say> - Implement IVR state machines with deterministic transitions, retries, and idempotency.
- Operate call recording + transcription pipelines with retention controls and compliance guardrails.
- Integrate SIP Trunking / SIP Interfaces, BYOC (Bring Your Own Carrier), and edge routing.
- Run conferencing at scale (participant management, moderation, recording, events).
- Diagnose and remediate common Twilio Voice failures (auth, invalid numbers, rate limits, carrier issues, webhook retries).
This skill is written for engineers shipping and maintaining production voice systems with strict reliability, observability, and security requirements.
帮助OpenClaw在生产环境中设计、实现和运维Twilio可编程语音服务:
- 使用TwiML构建呼入/外呼呼叫流程(包含、
<Dial>、<Conference>、<Gather>、结合Amazon Polly语音的<Record>等标签)。<Say> - 实现具备确定性流转、重试机制和幂等性的IVR状态机。
- 运维带有留存管控和合规防护的录音+转写流水线。
- 集成SIP中继/SIP接口、BYOC(自有运营商接入)和边缘路由。
- 规模化运行会议服务(参会者管理、 moderation、录音、事件处理)。
- 诊断并修复Twilio Voice常见故障(认证失败、无效号码、速率限制、运营商问题、Webhook重试)。
本技能面向需要交付并维护具备严格可靠性、可观测性和安全性要求的生产级语音系统的工程师。
Prerequisites
前置条件
Accounts & Twilio Console setup
账号与Twilio控制台配置
- Twilio account with:
- Account SID ()
AC... - Auth Token (treat as secret)
- At least one Voice-capable phone number (E.164) or SIP domain
- Account SID (
- Configure a Voice webhook for your Twilio number:
- Console → Phone Numbers → Manage → Active numbers → (number) → Voice configuration
- Set A CALL COMES IN to your webhook URL (HTTPS) and method (POST recommended)
- Set Status Callback for call progress events (optional but recommended)
- 具备以下信息的Twilio账号:
- Account SID(格式为)
AC... - Auth Token(需保密)
- 至少一个支持语音功能的电话号码(E.164格式)或SIP域名
- Account SID(格式为
- 为Twilio号码配置Voice Webhook:
- 控制台 → 电话号码 → 管理 → 活跃号码 → (目标号码) → 语音配置
- 将“来电时”设置为你的Webhook URL(HTTPS协议)和请求方法(推荐POST)
- 为呼叫进度事件设置“状态回调”(可选但推荐)
Runtime versions (tested)
已测试的运行时版本
Pick one backend stack; examples cover Node and Python.
- Node.js: 20.11.1 LTS (or 18.19.1 LTS)
- Python: 3.11.8 (or 3.10.13)
- Twilio helper libraries
- Node:
twilio@4.23.0 - Python:
twilio==9.0.5
- Node:
- ngrok (for local webhook dev): 3.14.2
- Docker (optional): 25.0.3
- OpenSSL: 3.0.x (Linux), 3.2.x (macOS via Homebrew)
选择一种后端技术栈;示例涵盖Node和Python。
- Node.js:20.11.1 LTS(或18.19.1 LTS)
- Python:3.11.8(或3.10.13)
- Twilio辅助库
- Node:
twilio@4.23.0 - Python:
twilio==9.0.5
- Node:
- ngrok(用于本地Webhook开发):3.14.2
- Docker(可选):25.0.3
- OpenSSL:3.0.x(Linux),3.2.x(macOS通过Homebrew安装)
OS support
操作系统支持
- Ubuntu 22.04 LTS (x86_64, arm64)
- Fedora 39/40
- macOS 14 Sonoma (Intel + Apple Silicon)
- Ubuntu 22.04 LTS(x86_64、arm64)
- Fedora 39/40
- macOS 14 Sonoma(Intel + Apple Silicon)
Network & DNS
网络与DNS
- Public HTTPS endpoint for Twilio webhooks (TLS 1.2+).
- If using SIP:
- Publicly reachable SIP infrastructure or Twilio SIP Domains.
- Firewall rules for SIP signaling/media (or use Twilio Elastic SIP Trunking with secure signaling).
- 供Twilio Webhook使用的公开HTTPS端点(需支持TLS 1.2+)。
- 若使用SIP:
- 可公开访问的SIP基础设施或Twilio SIP域名。
- 为SIP信令/媒体配置防火墙规则(或使用带有安全信令的Twilio弹性SIP中继)。
Secrets management
密钥管理
- Store and
TWILIO_ACCOUNT_SIDin a secret manager:TWILIO_AUTH_TOKEN- AWS Secrets Manager / GCP Secret Manager / Vault / 1Password CLI
- Never commit credentials to git.
- Rotate Auth Token on incident response or staff changes.
- 将和
TWILIO_ACCOUNT_SID存储在密钥管理器中:TWILIO_AUTH_TOKEN- AWS Secrets Manager / GCP Secret Manager / Vault / 1Password CLI
- 切勿将凭据提交至git仓库。
- 在发生安全事件或人员变动时轮换Auth Token。
Core Concepts
核心概念
TwiML execution model
TwiML执行模型
- Twilio requests your webhook when a call arrives (or when you initiate an outbound call with a TwiML URL).
- Your server returns TwiML (XML) instructions.
- Twilio executes TwiML sequentially; some verbs (e.g., ) block until completion.
<Dial> - TwiML is stateless; state must be stored externally (DB/Redis) and referenced via parameters.
- 当呼叫接入(或你通过TwiML URL发起外呼)时,Twilio会请求你的Webhook。
- 你的服务器返回TwiML(XML格式)指令。
- Twilio会按顺序执行TwiML;部分动词(如)会阻塞直至执行完成。
<Dial> - TwiML是无状态的;状态必须存储在外部(数据库/Redis)并通过参数引用。
Call legs, SIDs, and correlation
呼叫链路、SID与关联
- identifies a call leg (inbound leg, outbound leg, child calls).
CallSid - For , Twilio often creates a child call for the dialed party; correlate via:
<Dial>- (in status callbacks / call logs)
ParentCallSid
- Use as the primary correlation key in logs and traces.
CallSid
- 用于标识呼叫链路(呼入链路、外呼链路、子呼叫)。
CallSid - 使用时,Twilio通常会为被呼叫方创建一个子呼叫;可通过以下方式关联:
<Dial>- (在状态回调/呼叫日志中)
ParentCallSid
- 在日志和追踪中使用作为主要关联键。
CallSid
Webhooks and retries
Webhook与重试
- Twilio webhooks are HTTP requests to your infrastructure.
- Twilio retries on certain failures (timeouts, 5xx). You must implement:
- Idempotency (dedupe by + event type + timestamp)
CallSid - Fast responses (< 2s typical target) and async work offloaded to queues
- Idempotency (dedupe by
- Twilio Webhook是发送至你的基础设施的HTTP请求。
- Twilio会在特定故障时重试(超时、5xx错误)。你必须实现:
- 幂等性(通过+ 事件类型 + 时间戳去重)
CallSid - 快速响应(典型目标为<2秒),并将异步工作卸载至队列
- 幂等性(通过
Status callbacks (events)
状态回调(事件)
- Voice status callback events include:
- ,
initiated,ringing,answeredcompleted
- For conferences and recordings, additional callbacks exist.
- Treat callbacks as at-least-once delivery.
- 语音状态回调事件包括:
- 、
initiated、ringing、answeredcompleted
- 针对会议和录音,还有额外的回调事件。
- 回调事件的交付机制为至少一次。
IVR state machines
IVR状态机
- Use to collect DTMF or speech.
<Gather> - Model IVR as a state machine:
- State stored server-side (Redis/DB)
- Transition on input + timeout + retries
- Always handle invalid input and no-input paths
- 使用收集DTMF或语音输入。
<Gather> - 将IVR建模为状态机:
- 状态存储在服务器端(Redis/数据库)
- 根据输入、超时和重试进行状态流转
- 必须处理无效输入和无输入场景
Recording and transcription
录音与转写
- Recording can be enabled on ,
<Dial>, or via<Conference>.<Record> - Transcription can be:
- Twilio’s transcription features (where available)
- External transcription pipeline (recommended for control/quality)
- Define retention and access controls; recordings are sensitive.
- 可在、
<Dial>或通过<Conference>启用录音。<Record> - 转写方式包括:
- Twilio自带的转写功能(视可用性而定)
- 外部转写流水线(推荐用于自定义控制/品质要求)
- 定义留存和访问控制;录音属于敏感数据。
SIP, BYOC, and edge
SIP、BYOC与边缘节点
- SIP Domains: Twilio-hosted SIP endpoints for your clients.
- Elastic SIP Trunking: connect your PBX/carrier to Twilio.
- BYOC: bring your own carrier and use Twilio for apps/features.
- Use Twilio Edge Locations to reduce latency (e.g., ,
ashburn,dublin).singapore
- SIP域名:Twilio托管的SIP端点,供你的客户端使用。
- 弹性SIP中继:将你的PBX/运营商连接至Twilio。
- BYOC:接入自有运营商,同时使用Twilio的应用逻辑和功能。
- 使用Twilio边缘节点降低延迟(例如、
ashburn、dublin)。singapore
Installation & Setup
安装与配置
Official Python SDK — Voice
官方Python SDK — 语音服务
Repository: https://github.com/twilio/twilio-python
PyPI: · Supported: Python 3.7–3.13
PyPI:
pip install twiliopython
from twilio.rest import Client
from twilio.twiml.voice_response import VoiceResponse
client = Client()python
from twilio.rest import Client
from twilio.twiml.voice_response import VoiceResponse
client = Client()Outbound call
Outbound call
call = client.calls.create(
url="https://demo.twilio.com/docs/voice.xml",
to="+15558675309",
from_="+15017250604"
)
print(call.sid)
call = client.calls.create(
url="https://demo.twilio.com/docs/voice.xml",
to="+15558675309",
from_="+15017250604"
)
print(call.sid)
TwiML response (in webhook handler)
TwiML response (in webhook handler)
resp = VoiceResponse()
resp.say("Hello from Twilio Python!")
resp.record(transcribe=True, transcribe_callback="/transcription")
Source: [twilio/twilio-python — calls](https://github.com/twilio/twilio-python/blob/main/twilio/rest/api/v2010/account/call/__init__.py)resp = VoiceResponse()
resp.say("Hello from Twilio Python!")
resp.record(transcribe=True, transcribe_callback="/transcription")
来源:[twilio/twilio-python — calls](https://github.com/twilio/twilio-python/blob/main/twilio/rest/api/v2010/account/call/__init__.py)1) Install dependencies (Ubuntu 22.04)
1) 安装依赖(Ubuntu 22.04)
bash
sudo apt-get update
sudo apt-get install -y ca-certificates curl gnupg jq
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt-get install -y nodejs
node -v # v20.11.1
npm -v # 10.xPython option:
bash
sudo apt-get update
sudo apt-get install -y python3.11 python3.11-venv python3-pip
python3.11 -V # Python 3.11.8ngrok:
bash
curl -s https://ngrok-agent.s3.amazonaws.com/ngrok.asc \
| sudo tee /etc/apt/trusted.gpg.d/ngrok.asc >/dev/null
echo "deb https://ngrok-agent.s3.amazonaws.com buster main" \
| sudo tee /etc/apt/sources.list.d/ngrok.list
sudo apt-get update && sudo apt-get install -y ngrok
ngrok version # 3.14.2bash
sudo apt-get update
sudo apt-get install -y ca-certificates curl gnupg jq
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt-get install -y nodejs
node -v # v20.11.1
npm -v # 10.xPython选项:
bash
sudo apt-get update
sudo apt-get install -y python3.11 python3.11-venv python3-pip
python3.11 -V # Python 3.11.8ngrok:
bash
curl -s https://ngrok-agent.s3.amazonaws.com/ngrok.asc \
| sudo tee /etc/apt/trusted.gpg.d/ngrok.asc >/dev/null
echo "deb https://ngrok-agent.s3.amazonaws.com buster main" \
| sudo tee /etc/apt/sources.list.d/ngrok.list
sudo apt-get update && sudo apt-get install -y ngrok
ngrok version # 3.14.22) Install dependencies (Fedora 39/40)
2) 安装依赖(Fedora 39/40)
bash
sudo dnf install -y nodejs20 jq python3.11 python3.11-pip
node -v
python3.11 -Vngrok (manual):
bash
curl -L -o /tmp/ngrok.tgz https://bin.equinox.io/c/bNyj1mQVY4c/ngrok-v3-stable-linux-amd64.tgz
sudo tar -C /usr/local/bin -xzf /tmp/ngrok.tgz ngrok
ngrok versionbash
sudo dnf install -y nodejs20 jq python3.11 python3.11-pip
node -v
python3.11 -Vngrok(手动安装):
bash
curl -L -o /tmp/ngrok.tgz https://bin.equinox.io/c/bNyj1mQVY4c/ngrok-v3-stable-linux-amd64.tgz
sudo tar -C /usr/local/bin -xzf /tmp/ngrok.tgz ngrok
ngrok version3) Install dependencies (macOS 14, Intel + Apple Silicon)
3) 安装依赖(macOS 14,Intel + Apple Silicon)
Homebrew:
bash
brew update
brew install node@20 python@3.11 jq ngrok/ngrok/ngrok
node -v
python3.11 -V
ngrok versionEnsure PATH includes Homebrew Node:
bash
echo 'export PATH="/opt/homebrew/opt/node@20/bin:$PATH"' >> ~/.zshrc # Apple Silicon
echo 'export PATH="/usr/local/opt/node@20/bin:$PATH"' >> ~/.zshrc # Intel
source ~/.zshrc通过Homebrew安装:
bash
brew update
brew install node@20 python@3.11 jq ngrok/ngrok/ngrok
node -v
python3.11 -V
ngrok version确保PATH包含Homebrew安装的Node:
bash
echo 'export PATH="/opt/homebrew/opt/node@20/bin:$PATH"' >> ~/.zshrc # Apple Silicon
echo 'export PATH="/usr/local/opt/node@20/bin:$PATH"' >> ~/.zshrc # Intel
source ~/.zshrc4) Create a minimal Voice webhook service (Node.js)
4) 创建最小化Voice Webhook服务(Node.js)
Project layout:
- (Linux) or
/srv/twilio-voice/(macOS)~/src/twilio-voice/ - Config at (Linux) or
/etc/twilio/voice.env(local dev)./.env
bash
mkdir -p ~/src/twilio-voice && cd ~/src/twilio-voice
npm init -y
npm install twilio@4.23.0 express@4.18.3 body-parser@1.20.2 pino@9.0.0Create :
server.jsjavascript
const express = require("express");
const bodyParser = require("body-parser");
const twilio = require("twilio");
const pino = require("pino");
const log = pino({ level: process.env.LOG_LEVEL || "info" });
const app = express();
// Twilio sends application/x-www-form-urlencoded by default
app.use(bodyParser.urlencoded({ extended: false }));
// Optional: validate Twilio signature (recommended in production)
const validateTwilio = (req, res, next) => {
const authToken = process.env.TWILIO_AUTH_TOKEN;
const signature = req.header("X-Twilio-Signature");
const url = `${process.env.PUBLIC_BASE_URL}${req.originalUrl}`;
const isValid = twilio.validateRequest(authToken, signature, url, req.body);
if (!isValid) return res.status(403).send("Invalid Twilio signature");
next();
};
app.post("/voice/inbound", validateTwilio, (req, res) => {
const vr = new twilio.twiml.VoiceResponse();
// Example: simple IVR greeting + gather
const gather = vr.gather({
input: "dtmf",
numDigits: 1,
timeout: 5,
action: "/voice/menu",
method: "POST",
});
gather.say(
{ voice: "Polly.Joanna", language: "en-US" },
"Press 1 for sales. Press 2 for support."
);
vr.say({ voice: "Polly.Joanna", language: "en-US" }, "No input received. Goodbye.");
vr.hangup();
res.type("text/xml").send(vr.toString());
});
app.post("/voice/menu", validateTwilio, (req, res) => {
const digit = req.body.Digits;
const vr = new twilio.twiml.VoiceResponse();
if (digit === "1") {
vr.dial({ callerId: process.env.TWILIO_CALLER_ID }, "+14155550100");
} else if (digit === "2") {
vr.dial({ callerId: process.env.TWILIO_CALLER_ID }, "+14155550101");
} else {
vr.say({ voice: "Polly.Joanna", language: "en-US" }, "Invalid choice.");
vr.redirect({ method: "POST" }, "/voice/inbound");
}
res.type("text/xml").send(vr.toString());
});
app.get("/healthz", (req, res) => res.status(200).send("ok"));
const port = Number(process.env.PORT || 3000);
app.listen(port, () => log.info({ port }, "twilio-voice server listening"));Run locally:
bash
export TWILIO_AUTH_TOKEN="your_auth_token"
export PUBLIC_BASE_URL="https://example.ngrok-free.app"
export TWILIO_CALLER_ID="+14155551234"
node server.jsExpose via ngrok:
bash
ngrok http 3000项目结构:
- Linux:;macOS:
/srv/twilio-voice/~/src/twilio-voice/ - 配置文件:Linux:;本地开发:
/etc/twilio/voice.env./.env
bash
mkdir -p ~/src/twilio-voice && cd ~/src/twilio-voice
npm init -y
npm install twilio@4.23.0 express@4.18.3 body-parser@1.20.2 pino@9.0.0创建:
server.jsjavascript
const express = require("express");
const bodyParser = require("body-parser");
const twilio = require("twilio");
const pino = require("pino");
const log = pino({ level: process.env.LOG_LEVEL || "info" });
const app = express();
// Twilio sends application/x-www-form-urlencoded by default
app.use(bodyParser.urlencoded({ extended: false }));
// Optional: validate Twilio signature (recommended in production)
const validateTwilio = (req, res, next) => {
const authToken = process.env.TWILIO_AUTH_TOKEN;
const signature = req.header("X-Twilio-Signature");
const url = `${process.env.PUBLIC_BASE_URL}${req.originalUrl}`;
const isValid = twilio.validateRequest(authToken, signature, url, req.body);
if (!isValid) return res.status(403).send("Invalid Twilio signature");
next();
};
app.post("/voice/inbound", validateTwilio, (req, res) => {
const vr = new twilio.twiml.VoiceResponse();
// Example: simple IVR greeting + gather
const gather = vr.gather({
input: "dtmf",
numDigits: 1,
timeout: 5,
action: "/voice/menu",
method: "POST",
});
gather.say(
{ voice: "Polly.Joanna", language: "en-US" },
"Press 1 for sales. Press 2 for support."
);
vr.say({ voice: "Polly.Joanna", language: "en-US" }, "No input received. Goodbye.");
vr.hangup();
res.type("text/xml").send(vr.toString());
});
app.post("/voice/menu", validateTwilio, (req, res) => {
const digit = req.body.Digits;
const vr = new twilio.twiml.VoiceResponse();
if (digit === "1") {
vr.dial({ callerId: process.env.TWILIO_CALLER_ID }, "+14155550100");
} else if (digit === "2") {
vr.dial({ callerId: process.env.TWILIO_CALLER_ID }, "+14155550101");
} else {
vr.say({ voice: "Polly.Joanna", language: "en-US" }, "Invalid choice.");
vr.redirect({ method: "POST" }, "/voice/inbound");
}
res.type("text/xml").send(vr.toString());
});
app.get("/healthz", (req, res) => res.status(200).send("ok"));
const port = Number(process.env.PORT || 3000);
app.listen(port, () => log.info({ port }, "twilio-voice server listening"));本地运行:
bash
export TWILIO_AUTH_TOKEN="your_auth_token"
export PUBLIC_BASE_URL="https://example.ngrok-free.app"
export TWILIO_CALLER_ID="+14155551234"
node server.js通过ngrok暴露服务:
bash
ngrok http 3000copy the https URL into PUBLIC_BASE_URL and Twilio Console webhook
复制HTTPS URL到PUBLIC_BASE_URL和Twilio控制台的Webhook设置中
undefinedundefined5) Python alternative (FastAPI)
5) Python替代方案(FastAPI)
bash
mkdir -p ~/src/twilio-voice-py && cd ~/src/twilio-voice-py
python3.11 -m venv .venv
source .venv/bin/activate
pip install "twilio==9.0.5" "fastapi==0.109.2" "uvicorn[standard]==0.27.1"app.pypython
import os
from fastapi import FastAPI, Request, Response
from twilio.twiml.voice_response import VoiceResponse, Gather
from twilio.request_validator import RequestValidator
app = FastAPI()
def validate_twilio(request: Request, form: dict) -> None:
auth_token = os.environ["TWILIO_AUTH_TOKEN"]
validator = RequestValidator(auth_token)
signature = request.headers.get("X-Twilio-Signature", "")
url = os.environ["PUBLIC_BASE_URL"] + str(request.url.path)
if not validator.validate(url, form, signature):
raise PermissionError("Invalid Twilio signature")
@app.post("/voice/inbound")
async def inbound(request: Request):
form = dict(await request.form())
validate_twilio(request, form)
vr = VoiceResponse()
gather = Gather(input="dtmf", num_digits=1, timeout=5, action="/voice/menu", method="POST")
gather.say("Press 1 for sales. Press 2 for support.", voice="Polly.Joanna", language="en-US")
vr.append(gather)
vr.say("No input received. Goodbye.", voice="Polly.Joanna", language="en-US")
vr.hangup()
return Response(content=str(vr), media_type="text/xml")
@app.post("/voice/menu")
async def menu(request: Request):
form = dict(await request.form())
validate_twilio(request, form)
digit = form.get("Digits")
vr = VoiceResponse()
if digit == "1":
vr.dial("+14155550100", caller_id=os.environ["TWILIO_CALLER_ID"])
elif digit == "2":
vr.dial("+14155550101", caller_id=os.environ["TWILIO_CALLER_ID"])
else:
vr.say("Invalid choice.", voice="Polly.Joanna", language="en-US")
vr.redirect("/voice/inbound", method="POST")
return Response(content=str(vr), media_type="text/xml")Run:
bash
export TWILIO_AUTH_TOKEN="your_auth_token"
export PUBLIC_BASE_URL="https://example.ngrok-free.app"
export TWILIO_CALLER_ID="+14155551234"
uvicorn app:app --host 0.0.0.0 --port 3000bash
mkdir -p ~/src/twilio-voice-py && cd ~/src/twilio-voice-py
python3.11 -m venv .venv
source .venv/bin/activate
pip install "twilio==9.0.5" "fastapi==0.109.2" "uvicorn[standard]==0.27.1"app.pypython
import os
from fastapi import FastAPI, Request, Response
from twilio.twiml.voice_response import VoiceResponse, Gather
from twilio.request_validator import RequestValidator
app = FastAPI()
def validate_twilio(request: Request, form: dict) -> None:
auth_token = os.environ["TWILIO_AUTH_TOKEN"]
validator = RequestValidator(auth_token)
signature = request.headers.get("X-Twilio-Signature", "")
url = os.environ["PUBLIC_BASE_URL"] + str(request.url.path)
if not validator.validate(url, form, signature):
raise PermissionError("Invalid Twilio signature")
@app.post("/voice/inbound")
async def inbound(request: Request):
form = dict(await request.form())
validate_twilio(request, form)
vr = VoiceResponse()
gather = Gather(input="dtmf", num_digits=1, timeout=5, action="/voice/menu", method="POST")
gather.say("Press 1 for sales. Press 2 for support.", voice="Polly.Joanna", language="en-US")
vr.append(gather)
vr.say("No input received. Goodbye.", voice="Polly.Joanna", language="en-US")
vr.hangup()
return Response(content=str(vr), media_type="text/xml")
@app.post("/voice/menu")
async def menu(request: Request):
form = dict(await request.form())
validate_twilio(request, form)
digit = form.get("Digits")
vr = VoiceResponse()
if digit == "1":
vr.dial("+14155550100", caller_id=os.environ["TWILIO_CALLER_ID"])
elif digit == "2":
vr.dial("+14155550101", caller_id=os.environ["TWILIO_CALLER_ID"])
else:
vr.say("Invalid choice.", voice="Polly.Joanna", language="en-US")
vr.redirect("/voice/inbound", method="POST")
return Response(content=str(vr), media_type="text/xml")运行:
bash
export TWILIO_AUTH_TOKEN="your_auth_token"
export PUBLIC_BASE_URL="https://example.ngrok-free.app"
export TWILIO_CALLER_ID="+14155551234"
uvicorn app:app --host 0.0.0.0 --port 3000Key Capabilities
核心能力
Inbound call handling (TwiML webhook)
呼入呼叫处理(TwiML Webhook)
- Validate against the exact URL Twilio requested.
X-Twilio-Signature - Parse .
application/x-www-form-urlencoded - Return TwiML with correct .
Content-Type: text/xml - Keep webhook latency low; do not block on DB migrations, third-party APIs, or long computations.
Recommended inbound webhook contract:
- Endpoint:
POST /voice/inbound - Must handle:
- missing/invalid parameters
- repeated requests (retries)
- unexpected HTTP methods (return 405)
- 根据Twilio请求的精确URL验证。
X-Twilio-Signature - 解析格式数据。
application/x-www-form-urlencoded - 返回带有正确的TwiML。
Content-Type: text/xml - 降低Webhook延迟;不要在Webhook中阻塞数据库迁移、第三方API调用或长时间计算。
推荐的呼入Webhook约定:
- 端点:
POST /voice/inbound - 必须处理:
- 缺失/无效参数
- 重复请求(重试)
- 意外的HTTP方法(返回405)
Outbound calling (REST API + TwiML URL)
外呼呼叫(REST API + TwiML URL)
- Create outbound calls via REST:
- Provide ,
to, and eitherfrom(TwiML URL) orurlinline.twiml
- Provide
- Use status callbacks for lifecycle events.
- Use only when you can tolerate extra latency and false positives.
machineDetection
- 通过REST API创建外呼:
- 提供、
to,以及from(TwiML URL)或内联url。twiml
- 提供
- 使用状态回调监听生命周期事件。
- 仅当你能容忍额外延迟和误判时,才使用。
machineDetection
IVR with <Gather>
(DTMF + speech)
<Gather>基于<Gather>
的IVR(DTMF + 语音)
<Gather>- DTMF is deterministic; speech requires tuning and fallback.
- Always implement:
- path (no input)
timeout - invalid input path
- max retries
- For speech:
- specify ,
speechTimeout, and consider barge-in behavior.language
- specify
- DTMF是确定性的;语音输入需要调优和降级方案。
- 必须实现:
- 场景(无输入)
timeout - 无效输入场景
- 最大重试次数
- 针对语音输入:
- 指定、
speechTimeout,并考虑抢话行为。language
- 指定
Conferencing (<Conference>
)
<Conference>会议服务(<Conference>
)
<Conference>- Use conferences for:
- agent + customer + supervisor
- warm transfers
- multi-party bridges
- Control:
startConferenceOnEnterendConferenceOnExitbeep- and recording callbacks
record
- Track participants via Conference/Participant resources.
- 会议服务适用于:
- 坐席+客户+主管三方通话
- 暖转移
- 多方通话桥接
- 控制选项:
startConferenceOnEnterendConferenceOnExitbeep- 和录音回调
record
- 通过Conference/Participant资源追踪参会者。
Call recording
呼叫录音
- Options:
<Dial record="record-from-answer"><Conference record="record-from-start">- verb for voicemail-like flows
<Record>
- Use recording status callbacks to ingest metadata and enforce retention.
- Encrypt at rest in your storage if you export recordings.
- 启用方式:
<Dial record="record-from-answer"><Conference record="record-from-start">- 动词用于类似语音信箱的流程
<Record>
- 使用录音状态回调获取元数据并执行留存规则。
- 若导出录音,需在存储时加密静态数据。
Transcription
语音转写
- Prefer external transcription for:
- model choice
- redaction
- language detection
- diarization
- Pipeline:
- recording callback → enqueue job
- fetch recording media (authenticated)
- transcribe
- store transcript + link to /
CallSidRecordingSid
- 推荐使用外部转写服务的场景:
- 自定义模型选择
- 内容脱敏
- 语言检测
- 说话人分离
- 流水线流程:
- 录音回调 → 加入任务队列
- 获取录音媒体(需认证)
- 执行转写
- 存储转写结果并关联/
CallSidRecordingSid
SIP trunking / SIP domains / BYOC
SIP中继 / SIP域名 / BYOC
- SIP Domains for client registration and inbound SIP to Twilio apps.
- Elastic SIP Trunking for PBX connectivity.
- BYOC for carrier control; Twilio still provides app logic and features.
- SIP域名:供客户端注册,以及SIP呼入到Twilio应用的端点。
- 弹性SIP中继:用于PBX与Twilio的连接。
- BYOC:接入自有运营商,同时使用Twilio的应用逻辑和功能。
Voice SDK (browser/iOS/Android)
Voice SDK(浏览器/iOS/Android)
- Use Access Tokens with Voice grants.
- Token TTL: short (5–15 minutes) with refresh.
- Enforce device registration and push notifications (mobile).
- 使用带有Voice授权的访问令牌。
- 令牌TTL:较短(5–15分钟),并支持刷新。
- 强制设备注册和推送通知(移动端)。
Command Reference
命令参考
This section focuses on Twilio CLI and common operational commands. Twilio CLI is optional; production systems should use REST APIs and IaC where possible.
本节聚焦Twilio CLI和常见运维命令。Twilio CLI为可选工具;生产系统应尽可能使用REST API和基础设施即代码(IaC)。
Twilio CLI installation
Twilio CLI安装
macOS (Homebrew)
macOS(Homebrew)
bash
brew install twilio/brew/twilio
twilio --versionbash
brew install twilio/brew/twilio
twilio --versionUbuntu/Debian (npm global)
Ubuntu/Debian(npm全局安装)
bash
sudo npm install -g twilio-cli@5.20.0
twilio --versionbash
sudo npm install -g twilio-cli@5.20.0
twilio --versionFedora (npm global)
Fedora(npm全局安装)
bash
sudo npm install -g twilio-cli@5.20.0
twilio --versionbash
sudo npm install -g twilio-cli@5.20.0
twilio --versionAuthenticate Twilio CLI
Twilio CLI认证
bash
twilio loginbash
twilio loginprompts for Account SID and Auth Token
提示输入Account SID和Auth Token
Non-interactive (CI) via env:
```bash
export TWILIO_ACCOUNT_SID="ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
export TWILIO_AUTH_TOKEN="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
非交互式(CI环境)通过环境变量:
```bash
export TWILIO_ACCOUNT_SID="ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
export TWILIO_AUTH_TOKEN="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"Voice: list calls
语音:列出呼叫记录
bash
twilio api:core:calls:list \
--limit 50 \
--properties sid,from,to,status,startDate,endDate,duration,price,priceUnitRelevant flags:
- : max records
--limit <n> - : select fields (reduces noise)
--properties <csv>
bash
twilio api:core:calls:list \
--limit 50 \
--properties sid,from,to,status,startDate,endDate,duration,price,priceUnit相关参数:
- :最大记录数
--limit <n> - :选择字段(减少冗余信息)
--properties <csv>
Voice: fetch a call
语音:获取单个呼叫详情
bash
twilio api:core:calls:fetch --sid YOUR_CA_SIDFlags:
- : Call SID (required)
--sid <CA...>
bash
twilio api:core:calls:fetch --sid YOUR_CA_SID参数:
- :呼叫SID(必填)
--sid <CA...>
Voice: create an outbound call
语音:创建外呼
bash
twilio api:core:calls:create \
--from +14155551234 \
--to +14155559876 \
--url https://voice.example.com/twiml/outbound \
--status-callback https://voice.example.com/webhooks/voice/status \
--status-callback-event initiated ringing answered completed \
--status-callback-method POST \
--timeout 20Relevant flags (core):
- : caller ID / identity
--from <E.164|client:...|sip:...> - : destination
--to <E.164|client:...|sip:...> - : TwiML URL (mutually exclusive with
--url <https://...>)--twiml - : inline TwiML
--twiml <xml> - : method for
--method <GET|POST>fetch (default POST recommended)url --status-callback <url>--status-callback-event <events...>--status-callback-method <GET|POST>- : ring timeout
--timeout <seconds> - : AMD (adds latency)
--machine-detection <Enable|DetectMessageEnd|...> - : enable recording (where supported)
--record --recording-status-callback <url>--recording-status-callback-method <GET|POST>
bash
twilio api:core:calls:create \
--from +14155551234 \
--to +14155559876 \
--url https://voice.example.com/twiml/outbound \
--status-callback https://voice.example.com/webhooks/voice/status \
--status-callback-event initiated ringing answered completed \
--status-callback-method POST \
--timeout 20核心相关参数:
- :主叫ID / 身份标识
--from <E.164|client:...|sip:...> - :被叫方
--to <E.164|client:...|sip:...> - :TwiML URL(与
--url <https://...>互斥)--twiml - :内联TwiML
--twiml <xml> - :获取
--method <GET|POST>的请求方法(推荐默认POST)url --status-callback <url>--status-callback-event <events...>--status-callback-method <GET|POST>- :振铃超时时间
--timeout <seconds> - :自动应答检测(会增加延迟)
--machine-detection <Enable|DetectMessageEnd|...> - :启用录音(视支持情况)
--record --recording-status-callback <url>--recording-status-callback-method <GET|POST>
Voice: list recordings
语音:列出录音记录
bash
twilio api:core:recordings:list --limit 20 \
--properties sid,callSid,dateCreated,duration,price,priceUnit,statusbash
twilio api:core:recordings:list --limit 20 \
--properties sid,callSid,dateCreated,duration,price,priceUnit,statusVoice: fetch recording metadata
语音:获取录音元数据
bash
twilio api:core:recordings:fetch --sid RE3f3b1b2c0d0f4a5b6c7d8e9f0a1b2c3bash
twilio api:core:recordings:fetch --sid RE3f3b1b2c0d0f4a5b6c7d8e9f0a1b2c3Voice: download recording media (authenticated)
语音:下载录音媒体(需认证)
Twilio CLI does not always provide direct media download helpers; use curl with basic auth:
bash
export TWILIO_ACCOUNT_SID="ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
export TWILIO_AUTH_TOKEN="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
curl -u "$TWILIO_ACCOUNT_SID:$TWILIO_AUTH_TOKEN" \
-L "https://api.twilio.com/2010-04-01/Accounts/$TWILIO_ACCOUNT_SID/Recordings/RE3f3b1b2c0d0f4a5b6c7d8e9f0a1b2c3.wav" \
-o recording.wav
file recording.wavTwilio CLI并不总是提供直接的媒体下载工具;可使用curl结合基础认证:
bash
export TWILIO_ACCOUNT_SID="ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
export TWILIO_AUTH_TOKEN="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
curl -u "$TWILIO_ACCOUNT_SID:$TWILIO_AUTH_TOKEN" \
-L "https://api.twilio.com/2010-04-01/Accounts/$TWILIO_ACCOUNT_SID/Recordings/RE3f3b1b2c0d0f4a5b6c7d8e9f0a1b2c3.wav" \
-o recording.wav
file recording.wavVoice: conferences list/fetch
语音:会议列表/详情
bash
twilio api:core:conferences:list --status in-progress --limit 20
twilio api:core:conferences:fetch --sid YOUR_CF_SIDbash
twilio api:core:conferences:list --status in-progress --limit 20
twilio api:core:conferences:fetch --sid YOUR_CF_SIDVoice: conference participants list
语音:会议参会者列表
bash
twilio api:core:conferences:participants:list \
--conference-sid YOUR_CF_SID \
--limit 50bash
twilio api:core:conferences:participants:list \
--conference-sid YOUR_CF_SID \
--limit 50Voice: update participant (mute/kick)
语音:更新参会者状态(静音/移除)
bash
twilio api:core:conferences:participants:update \
--conference-sid YOUR_CF_SID \
--sid CAbcdef0123456789abcdef0123456789 \
--muted trueFlags:
--muted <true|false>- (where supported)
--hold <true|false> - (music/announcements while on hold)
--hold-url <url>
bash
twilio api:core:conferences:participants:update \
--conference-sid YOUR_CF_SID \
--sid CAbcdef0123456789abcdef0123456789 \
--muted true参数:
--muted <true|false>- (视支持情况)
--hold <true|false> - (保持时的音乐/通知)
--hold-url <url>
Twilio CLI: API request (generic)
Twilio CLI:通用API请求
Use for endpoints not wrapped by CLI:
bash
twilio api:core:accounts:fetch
twilio api:request --method GET \
--uri "/2010-04-01/Accounts/$TWILIO_ACCOUNT_SID/Calls.json?PageSize=20"Flags:
--method <GET|POST|PUT|DELETE>- : relative to
--uri <path>https://api.twilio.com - : form fields (repeatable)
--data <k=v>
用于CLI未封装的端点:
bash
twilio api:core:accounts:fetch
twilio api:request --method GET \
--uri "/2010-04-01/Accounts/$TWILIO_ACCOUNT_SID/Calls.json?PageSize=20"参数:
--method <GET|POST|PUT|DELETE>- :相对
--uri <path>的路径https://api.twilio.com - :表单字段(可重复)
--data <k=v>
Configuration Reference
配置参考
Environment variables (Linux production)
环境变量(Linux生产环境)
File:
/etc/twilio/voice.envbash
undefined文件路径:
/etc/twilio/voice.envbash
undefinedTwilio auth
Twilio认证
TWILIO_ACCOUNT_SID=AC2f1c2d3e4f5a6b7c8d9e0f1a2b3c4d5
TWILIO_AUTH_TOKEN=9f8e7d6c5b4a3a2b1c0d9e8f7a6b5c4d
TWILIO_ACCOUNT_SID=AC2f1c2d3e4f5a6b7c8d9e0f1a2b3c4d5
TWILIO_AUTH_TOKEN=9f8e7d6c5b4a3a2b1c0d9e8f7a6b5c4d
Public base URL used for signature validation
用于签名验证的公开基础URL
PUBLIC_BASE_URL=https://voice.prod.example.com
PUBLIC_BASE_URL=https://voice.prod.example.com
Caller ID must be a Twilio number or verified caller ID
主叫ID必须是Twilio号码或已验证的主叫ID
TWILIO_CALLER_ID=+14155551234
TWILIO_CALLER_ID=+14155551234
Webhook behavior
Webhook行为
LOG_LEVEL=info
PORT=3000
LOG_LEVEL=info
PORT=3000
Recording/transcription pipeline
录音/转写流水线
RECORDINGS_BUCKET=s3://prod-voice-recordings-us-east-1
TRANSCRIPTION_PROVIDER=deepgram
TRANSCRIPTION_WEBHOOK_SECRET=whsec_6b1f0b2a9c3d4e5f
Systemd unit (Ubuntu/Fedora):
File: `/etc/systemd/system/twilio-voice.service`
```ini
[Unit]
Description=Twilio Voice Webhook Service
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=twilio
Group=twilio
EnvironmentFile=/etc/twilio/voice.env
WorkingDirectory=/srv/twilio-voice
ExecStart=/usr/bin/node /srv/twilio-voice/server.js
Restart=on-failure
RestartSec=2
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/srv/twilio-voice
AmbientCapabilities=
CapabilityBoundingSet=
LockPersonality=true
MemoryDenyWriteExecute=true
[Install]
WantedBy=multi-user.targetEnable:
bash
sudo useradd --system --home /srv/twilio-voice --shell /usr/sbin/nologin twilio
sudo mkdir -p /srv/twilio-voice
sudo chown -R twilio:twilio /srv/twilio-voice
sudo systemctl daemon-reload
sudo systemctl enable --now twilio-voice
sudo systemctl status twilio-voice --no-pagerRECORDINGS_BUCKET=s3://prod-voice-recordings-us-east-1
TRANSCRIPTION_PROVIDER=deepgram
TRANSCRIPTION_WEBHOOK_SECRET=whsec_6b1f0b2a9c3d4e5f
Systemd单元配置(Ubuntu/Fedora):
文件路径:`/etc/systemd/system/twilio-voice.service`
```ini
[Unit]
Description=Twilio Voice Webhook Service
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=twilio
Group=twilio
EnvironmentFile=/etc/twilio/voice.env
WorkingDirectory=/srv/twilio-voice
ExecStart=/usr/bin/node /srv/twilio-voice/server.js
Restart=on-failure
RestartSec=2
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/srv/twilio-voice
AmbientCapabilities=
CapabilityBoundingSet=
LockPersonality=true
MemoryDenyWriteExecute=true
[Install]
WantedBy=multi-user.target启用服务:
bash
sudo useradd --system --home /srv/twilio-voice --shell /usr/sbin/nologin twilio
sudo mkdir -p /srv/twilio-voice
sudo chown -R twilio:twilio /srv/twilio-voice
sudo systemctl daemon-reload
sudo systemctl enable --now twilio-voice
sudo systemctl status twilio-voice --no-pagerNGINX reverse proxy (TLS termination)
NGINX反向代理(TLS终止)
File:
/etc/nginx/sites-available/voice.prod.example.comnginx
server {
listen 443 ssl http2;
server_name voice.prod.example.com;
ssl_certificate /etc/letsencrypt/live/voice.prod.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/voice.prod.example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
# Twilio webhooks are small; keep limits tight
client_max_body_size 64k;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_read_timeout 10s;
}
location = /healthz {
proxy_pass http://127.0.0.1:3000/healthz;
}
}文件路径:
/etc/nginx/sites-available/voice.prod.example.comnginx
server {
listen 443 ssl http2;
server_name voice.prod.example.com;
ssl_certificate /etc/letsencrypt/live/voice.prod.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/voice.prod.example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
# Twilio Webhook数据量小;限制配置需严格
client_max_body_size 64k;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_read_timeout 10s;
}
location = /healthz {
proxy_pass http://127.0.0.1:3000/healthz;
}
}TwiML examples (stored templates)
TwiML示例(存储的模板)
If you store TwiML templates in-repo, keep them versioned:
Path:
/srv/twilio-voice/twiml/outbound.xmlxml
<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Say voice="Polly.Joanna" language="en-US">Connecting your call.</Say>
<Dial callerId="+14155551234" record="record-from-answer" recordingStatusCallback="https://voice.prod.example.com/webhooks/recording">
<Number>+14155559876</Number>
</Dial>
</Response>若将TwiML模板存储在仓库中,需进行版本控制:
路径:
/srv/twilio-voice/twiml/outbound.xmlxml
<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Say voice="Polly.Joanna" language="en-US">Connecting your call.</Say>
<Dial callerId="+14155551234" record="record-from-answer" recordingStatusCallback="https://voice.prod.example.com/webhooks/recording">
<Number>+14155559876</Number>
</Dial>
</Response>Integration Patterns
集成模式
Compose with Twilio Messaging (cluster requirement alignment)
与Twilio消息服务集成(集群需求对齐)
Common pattern: voice call fails → send SMS fallback with opt-out handling.
Flow:
- Attempt outbound call
- If status callback indicates with
completedCallStatus=busy|no-answer|failed - Send SMS: “We tried to reach you…” via Messaging Service
- Respect STOP/START keywords and compliance
Pseudo-pipeline:
mermaid
flowchart LR
A[Create Call] --> B[Status Callback]
B -->|answered| C[Normal completion]
B -->|busy/no-answer/failed| D[Send SMS fallback]
D --> E[Message Status Webhook]常见模式:语音呼叫失败 → 发送SMS降级通知,并处理退订逻辑。
流程:
- 尝试发起外呼
- 若状态回调显示且
completedCallStatus=busy|no-answer|failed - 通过消息服务发送SMS:“我们尝试联系您…”
- 遵守STOP/START关键字和合规要求
伪流水线:
mermaid
flowchart LR
A[Create Call] --> B[Status Callback]
B -->|answered| C[Normal completion]
B -->|busy/no-answer/failed| D[Send SMS fallback]
D --> E[Message Status Webhook]Compose with Verify (2FA via voice)
与Verify集成(语音OTP)
- Use Verify V2 for voice OTP when SMS is blocked.
- Voice channel can be more expensive; rate limit and fraud guard.
- 当SMS被拦截时,使用Verify V2发送语音OTP。
- 语音渠道成本更高;需进行速率限制和欺诈防护。
Compose with Studio
与Studio集成
- Use Studio for rapid iteration of IVR logic, but keep critical flows in code for versioning.
- Trigger Studio Flow via REST Trigger API for certain branches (e.g., after-hours routing).
- 使用Studio快速迭代IVR逻辑,但关键流程需保留在代码中以支持版本控制。
- 通过REST触发API调用Studio Flow处理特定分支(例如非工作时间路由)。
Event-driven recording/transcription pipeline
事件驱动的录音/转写流水线
- Recording status callback → enqueue job (SQS/PubSub/Kafka)
- Worker downloads recording media → transcribes → stores transcript
- Attach transcript to CRM ticket
Example (AWS SQS + worker):
- Webhook service publishes message
{RecordingSid, CallSid, RecordingUrl, Timestamp} - Worker uses Twilio REST to fetch recording metadata and media
- 录音状态回调 → 加入任务队列(SQS/PubSub/Kafka)
- 工作节点下载录音媒体 → 执行转写 → 存储转写结果
- 将转写结果关联至CRM工单
示例(AWS SQS + 工作节点):
- Webhook服务发布消息
{RecordingSid, CallSid, RecordingUrl, Timestamp} - 工作节点使用Twilio REST API获取录音元数据和媒体
Observability integration
可观测性集成
- Emit structured logs with ,
CallSid,ParentCallSid,From,To.Direction - Metrics:
- webhook latency p50/p95
- call answer rate
- gather completion rate
- conference join failures
- Tracing:
- propagate as trace attribute
CallSid
- propagate
- 输出包含、
CallSid、ParentCallSid、From、To的结构化日志。Direction - 指标:
- Webhook延迟p50/p95
- 呼叫接通率
- Gather完成率
- 会议加入失败率
- 追踪:
- 将作为追踪属性传递
CallSid
- 将
Error Handling & Troubleshooting
错误处理与故障排查
Handle errors at three layers: webhook HTTP, Twilio REST API, and carrier/SIP.
在三个层级处理错误:Webhook HTTP错误、Twilio REST API错误、运营商/SIP错误。
1) Twilio could not find a valid URL for the Voice request
Twilio could not find a valid URL for the Voice request1) Twilio could not find a valid URL for the Voice request
Twilio could not find a valid URL for the Voice requestSymptom (Console debugger):
- “Twilio could not find a valid URL for the Voice request. Please check your TwiML App settings.”
Root cause:
- Phone number Voice webhook not configured, or points to invalid URL.
Fix:
- Console → Phone Numbers → (number) → Voice configuration → set webhook URL.
- Ensure HTTPS and publicly reachable.
症状(控制台调试器):
- “Twilio could not find a valid URL for the Voice request. Please check your TwiML App settings.”
根本原因:
- 电话号码的Voice Webhook未配置,或指向无效URL。
修复方案:
- 控制台 → 电话号码 → (目标号码) → 语音配置 → 设置Webhook URL。
- 确保URL为HTTPS且可公开访问。
2) 11200 - HTTP retrieval failure
11200 - HTTP retrieval failure2) 11200 - HTTP retrieval failure
11200 - HTTP retrieval failureSymptom:
- Twilio Debugger shows:
Error - 11200- “HTTP retrieval failure”
Root cause:
- Your webhook endpoint timed out, returned 5xx, TLS handshake failed, or DNS issues.
Fix:
- Ensure endpoint responds within ~2–5 seconds.
- Check TLS cert validity and chain.
- Verify firewall allows inbound from Twilio (don’t IP allowlist unless you maintain Twilio IP ranges).
- Add and monitor.
/healthz
症状:
- Twilio调试器显示:
Error - 11200- “HTTP retrieval failure”
根本原因:
- 你的Webhook端点超时、返回5xx错误、TLS握手失败或DNS问题。
修复方案:
- 确保端点在约2–5秒内响应。
- 检查TLS证书有效性和证书链。
- 验证防火墙允许Twilio的入站请求(除非你维护Twilio IP范围列表,否则不要使用IP白名单)。
- 添加端点并监控。
/healthz
3) 12300 - Invalid Content-Type
12300 - Invalid Content-Type3) 12300 - Invalid Content-Type
12300 - Invalid Content-TypeSymptom:
- Twilio Debugger:
Error - 12300- “Invalid Content-Type”
Root cause:
- Webhook returned TwiML but with wrong (e.g.,
Content-Type).application/json
Fix:
- Set .
Content-Type: text/xml - Ensure response body is valid XML.
症状:
- Twilio调试器:
Error - 12300- “Invalid Content-Type”
根本原因:
- Webhook返回TwiML但使用了错误的(例如
Content-Type)。application/json
修复方案:
- 设置。
Content-Type: text/xml - 确保响应体为有效的XML。
4) 12100 - Document parse failure
12100 - Document parse failure4) 12100 - Document parse failure
12100 - Document parse failureSymptom:
- Twilio Debugger:
Error - 12100- “Document parse failure”
Root cause:
- Malformed XML (unescaped characters, invalid nesting).
Fix:
- Use helper library TwiML builders.
- Validate generated XML; ensure is escaped as
&.&
症状:
- Twilio调试器:
Error - 12100- “Document parse failure”
根本原因:
- XML格式错误(未转义字符、无效嵌套)。
修复方案:
- 使用辅助库的TwiML构建器。
- 验证生成的XML;确保转义为
&。&
5) 21211 - The 'To' number +... is not a valid phone number
21211 - The 'To' number +... is not a valid phone number5) 21211 - The 'To' number +... is not a valid phone number
21211 - The 'To' number +... is not a valid phone numberSymptom (REST API response):
json
{
"code": 21211,
"message": "The 'To' number +1415555 is not a valid phone number.",
"status": 400
}Root cause:
- Non-E.164 formatting, missing country code, invalid characters.
Fix:
- Normalize to E.164 ().
+14155551212 - Validate with libphonenumber before calling Twilio.
- For SIP, use .
sip:user@domain
症状(REST API响应):
json
{
"code": 21211,
"message": "The 'To' number +1415555 is not a valid phone number.",
"status": 400
}根本原因:
- 非E.164格式、缺少国家代码、包含无效字符。
修复方案:
- 标准化为E.164格式(例如)。
+14155551212 - 在调用Twilio前使用libphonenumber验证号码。
- 对于SIP,使用格式。
sip:user@domain
6) 20003 - Authenticate
20003 - Authenticate6) 20003 - Authenticate
20003 - AuthenticateSymptom:
- REST API returns:
HTTP 401code: 20003- message: “Authenticate”
Root cause:
- Wrong Account SID/Auth Token, rotated token, or using test creds against live endpoints.
Fix:
- Verify secrets in runtime environment.
- Rotate and redeploy.
- Ensure you’re using correct subaccount credentials if applicable.
症状:
- REST API返回:
HTTP 401code: 20003- message: “Authenticate”
根本原因:
- 错误的Account SID/Auth Token、令牌已轮换,或使用测试凭据访问生产端点。
修复方案:
- 验证运行时环境中的密钥。
- 轮换密钥并重新部署。
- 若使用子账号,确保使用正确的子账号凭据。
7) 20429 - Too Many Requests
20429 - Too Many Requests7) 20429 - Too Many Requests
20429 - Too Many RequestsSymptom:
- REST API returns:
HTTP 429code: 20429- message: “Too Many Requests”
Root cause:
- Rate limit exceeded (bursting call creates, conference ops, recording fetches).
Fix:
- Implement exponential backoff with jitter.
- Queue outbound call creation; cap concurrency.
- Cache call/recording metadata to reduce repeated fetches.
症状:
- REST API返回:
HTTP 429code: 20429- message: “Too Many Requests”
根本原因:
- 超出速率限制(批量创建呼叫、会议操作、获取录音)。
修复方案:
- 实现带抖动的指数退避算法。
- 将外呼创建加入队列;限制并发数。
- 缓存呼叫/录音元数据以减少重复获取。
8) 30003 - Unreachable destination handset
30003 - Unreachable destination handset8) 30003 - Unreachable destination handset
30003 - Unreachable destination handsetSymptom:
- Call ends quickly; status callback indicates failure.
- Twilio error code .
30003
Root cause:
- Carrier unreachable, invalid routing, handset off-network, or blocked by carrier.
Fix:
- Retry with backoff only if business-appropriate.
- Consider alternate routes (BYOC) for critical traffic.
- Confirm destination is voice-capable and not blocked.
症状:
- 呼叫快速结束;状态回调显示失败。
- Twilio错误码。
30003
根本原因:
- 运营商不可达、路由无效、终端离线或被运营商拦截。
修复方案:
- 仅当业务允许时,使用退避策略重试。
- 对于关键流量,考虑使用替代路由(BYOC)。
- 确认被叫方支持语音功能且未被拦截。
9) 403 Invalid Twilio signature
(your app)
403 Invalid Twilio signature9) 403 Invalid Twilio signature
(你的应用)
403 Invalid Twilio signatureSymptom:
- Your service returns 403 with message “Invalid Twilio signature”.
Root cause:
- Signature validation uses wrong URL (missing scheme/host, wrong path, ngrok URL changed).
- Proxy not forwarding correct /
Host.X-Forwarded-Proto
Fix:
- Compute validation URL using externally visible URL.
- Ensure matches Twilio-configured webhook base.
PUBLIC_BASE_URL - In NGINX, forward and
Host.X-Forwarded-Proto
症状:
- 你的服务返回403并显示消息“Invalid Twilio signature”。
根本原因:
- 签名验证使用了错误的URL(缺少协议/主机、错误路径、ngrok URL已变更)。
- 代理未转发正确的/
Host。X-Forwarded-Proto
修复方案:
- 使用外部可见的URL计算验证地址。
- 确保与Twilio配置的Webhook基础URL一致。
PUBLIC_BASE_URL - 在NGINX中转发和
Host。X-Forwarded-Proto
10) SIP: 488 Not Acceptable Here
/ no audio
488 Not Acceptable Here10) SIP: 488 Not Acceptable Here
/ 无音频
488 Not Acceptable HereSymptom:
- SIP call connects but no audio, or SIP rejects with 488.
Root cause:
- Codec mismatch, SRTP mismatch, NAT/firewall blocking RTP.
Fix:
- Ensure common codecs (PCMU/PCMA/Opus where supported).
- If using SRTP, align policies.
- Open RTP ports or use Twilio-managed media; verify symmetric RTP.
症状:
- SIP呼叫接通但无音频,或SIP返回488错误。
根本原因:
- 编解码器不匹配、SRTP不匹配、NAT/防火墙拦截RTP。
修复方案:
- 确保使用通用编解码器(PCMU/PCMA/Opus,视支持情况)。
- 若使用SRTP,对齐策略配置。
- 开放RTP端口或使用Twilio托管媒体;验证对称RTP。
Security Hardening
安全加固
Webhook authenticity
Webhook真实性
- Validate on every webhook.
X-Twilio-Signature - Ensure you validate against the exact URL Twilio requested (scheme/host/path).
- Reject requests missing signature with 403.
- 对每个Webhook验证。
X-Twilio-Signature - 确保针对Twilio请求的精确URL(协议/主机/路径)进行验证。
- 拒绝缺少签名的请求并返回403。
TLS and HTTP
TLS与HTTP
- Enforce TLS 1.2+ (prefer TLS 1.3).
- Disable weak ciphers (use modern NGINX defaults).
- Set strict timeouts:
- upstream read timeout 10s
- request body size 64k
- 强制使用TLS 1.2+(优先TLS 1.3)。
- 禁用弱密码套件(使用NGINX现代默认配置)。
- 设置严格超时:
- 上游读取超时10秒
- 请求体大小限制64k
Secrets
密钥管理
- Store Auth Token in secret manager; do not log it.
- Rotate Auth Token periodically (quarterly) and on incident.
- Use subaccounts to isolate environments (dev/stage/prod).
- 将Auth Token存储在密钥管理器中;切勿记录日志。
- 定期(每季度)并在发生安全事件时轮换Auth Token。
- 使用子账号隔离环境(开发/测试/生产)。
Least privilege (system)
最小权限(系统层面)
- Run webhook service as non-root user.
- Systemd hardening (example unit already includes):
NoNewPrivileges=trueProtectSystem=strictProtectHome=truePrivateTmp=true
- Align with CIS Benchmarks:
- CIS Ubuntu Linux 22.04 LTS Benchmark (sections on service hardening, file permissions)
- CIS NGINX Benchmark (TLS configuration, headers, logging)
- 以非root用户运行Webhook服务。
- Systemd加固(示例单元已包含):
NoNewPrivileges=trueProtectSystem=strictProtectHome=truePrivateTmp=true
- 对齐CIS基准:
- CIS Ubuntu Linux 22.04 LTS基准(服务加固、文件权限相关章节)
- CIS NGINX基准(TLS配置、请求头、日志相关章节)
Data protection (recordings/transcripts)
数据保护(录音/转写结果)
- Treat recordings/transcripts as sensitive:
- encrypt at rest (KMS)
- restrict access via IAM
- audit access logs
- Define retention:
- e.g., delete recordings after 30 days unless legal hold
- If exporting recordings, use signed URLs with short TTL.
- 将录音/转写结果视为敏感数据:
- 静态加密(KMS)
- 通过IAM限制访问
- 审计访问日志
- 定义留存规则:
- 例如,除非有法律保留要求,否则30天后删除录音
- 若导出录音,使用短TTL的签名URL。
PII minimization
PII最小化
- Do not log full phone numbers unless necessary; mask:
- →
+14155551234+1415****234
- Avoid storing DTMF inputs that may contain account numbers unless required; if required, tokenize.
- 除非必要,否则不要记录完整电话号码;进行掩码处理:
- →
+14155551234+1415****234
- 避免存储可能包含账号信息的DTMF输入;若必须存储,需进行令牌化处理。
Performance Tuning
性能调优
Webhook latency budget
Webhook延迟预算
Target: p95 < 200ms for TwiML generation (excluding network).
Optimizations:
- Precompute static TwiML templates for common branches.
- Avoid synchronous calls to external services inside webhook.
- Use in-memory caches for routing tables (with periodic refresh).
Expected impact:
- Moving CRM lookup out of webhook into async job often reduces p95 from ~800ms to ~120ms.
目标:TwiML生成的p95延迟 < 200ms(不含网络延迟)。
优化方案:
- 为常见分支预计算静态TwiML模板。
- 避免在Webhook中同步调用外部服务。
- 使用内存缓存存储路由表(定期刷新)。
预期效果:
- 将CRM查询从Webhook移至异步任务,通常可将p95延迟从约800ms降至约120ms。
Reduce Twilio REST API chatter
减少Twilio REST API调用
- Cache call metadata for short windows (e.g., 30–60 seconds) to avoid repeated .
Calls.fetch - For conference participant polling, prefer event-driven callbacks where possible.
Expected impact:
- 30–70% reduction in REST calls during incident debugging and dashboards.
- 短时间缓存呼叫元数据(例如30–60秒),避免重复调用。
Calls.fetch - 对于会议参会者轮询,优先使用事件驱动的回调。
预期效果:
- 在故障排查和仪表盘场景中,REST调用减少30–70%。
Conference scaling
会议服务扩容
- Avoid creating unique conferences per minor interaction; reuse conference rooms per session.
- Use deterministic conference names (e.g., ) to simplify correlation.
support-${ticketId} - Ensure you handle participant join/leave events idempotently.
- 避免为次要交互创建唯一会议;按会话复用会议室。
- 使用确定性会议名称(例如)简化关联。
support-${ticketId} - 确保幂等处理参会者加入/离开事件。
Recording download throughput
录音下载吞吐量
- Download recordings asynchronously with bounded concurrency (e.g., 10–25 workers).
- Use HTTP keep-alive and streaming to object storage.
Expected impact:
- Stable worker CPU/memory; avoids 429 rate limits and reduces tail latency.
- 异步下载录音,限制并发数(例如10–25个工作节点)。
- 使用HTTP长连接和流式传输至对象存储。
预期效果:
- 工作节点CPU/内存稳定;避免429速率限制并降低尾部延迟。
Advanced Topics
进阶主题
Idempotency for webhooks
Webhook幂等性
Twilio retries can cause duplicate processing.
Pattern:
- Compute idempotency key:
voice:{CallSid}:{EventType}:{Timestamp}
- Store in Redis with TTL (e.g., 24h).
- If key exists, return 200 quickly.
Twilio重试可能导致重复处理。
实现模式:
- 计算幂等键:
voice:{CallSid}:{EventType}:{Timestamp}
- 将键存储在Redis中并设置TTL(例如24小时)。
- 若键已存在,快速返回200。
Handling <Gather>
edge cases
<Gather>处理<Gather>
边缘场景
<Gather>- may be missing on timeout.
Digits - Users can press multiple digits quickly; enforce .
numDigits - For speech, partial results and confidence vary; always provide DTMF fallback.
- 超时场景下可能缺失。
Digits - 用户可能快速按下多个数字;需强制限制。
numDigits - 语音输入的部分结果和置信度存在差异;始终提供DTMF降级方案。
<Dial>
child call behavior
<Dial><Dial>
子呼叫行为
<Dial>- Status callbacks for parent and child calls differ.
- If you need the dialed leg SID, capture it via callbacks or Twilio events.
- For transfers, track to link legs.
ParentCallSid
- 父呼叫和子呼叫的状态回调不同。
- 若需要被呼叫方的链路SID,通过回调或Twilio事件捕获。
- 对于转移操作,跟踪以关联链路。
ParentCallSid
Answering machine detection (AMD)
应答机检测(AMD)
- Adds latency and can misclassify.
- Use only when business logic requires it (e.g., leave voicemail).
- Implement fallback: if AMD uncertain, route to human or play prompt.
- 会增加延迟且可能误分类。
- 仅当业务逻辑需要时使用(例如留语音信箱)。
- 实现降级方案:若AMD结果不确定,路由至人工或播放提示音。
Edge locations
边缘节点
- For latency-sensitive apps, specify Twilio edge in REST client:
- Node helper supports /
edgeconfiguration.region
- Node helper supports
- Ensure consistent edge selection across services to avoid cross-region media paths.
- 对于延迟敏感的应用,在REST客户端中指定Twilio边缘节点:
- Node辅助库支持/
edge配置。region
- Node辅助库支持
- 确保所有服务选择一致的边缘节点,避免跨区域媒体路径。
Compliance and consent
合规与同意
- Recording consent requirements vary by jurisdiction.
- Implement explicit consent prompts before enabling recording when required.
- Store consent event with timestamp and .
CallSid
- 录音同意要求因地区而异。
- 当法律要求时,在启用录音前实现明确的同意提示。
- 存储同意事件,包含时间戳和。
CallSid
Usage Examples
使用示例
1) Inbound IVR → route to on-call via <Dial>
with Polly voice
<Dial>1) 呼入IVR → 通过<Dial>
路由至值班人员(使用Polly语音)
<Dial>Node TwiML snippet:
javascript
const vr = new twilio.twiml.VoiceResponse();
vr.say({ voice: "Polly.Matthew", language: "en-US" }, "You have reached incident response.");
vr.dial(
{
callerId: process.env.TWILIO_CALLER_ID,
timeout: 20,
record: "record-from-answer",
recordingStatusCallback: "https://voice.prod.example.com/webhooks/recording",
},
"+14155550123"
);
res.type("text/xml").send(vr.toString());Operational notes:
- Ensure on-call number is E.164.
- Recording callback must be signature-validated too.
Node TwiML片段:
javascript
const vr = new twilio.twiml.VoiceResponse();
vr.say({ voice: "Polly.Matthew", language: "en-US" }, "You have reached incident response.");
vr.dial(
{
callerId: process.env.TWILIO_CALLER_ID,
timeout: 20,
record: "record-from-answer",
recordingStatusCallback: "https://voice.prod.example.com/webhooks/recording",
},
"+14155550123"
);
res.type("text/xml").send(vr.toString());运维注意事项:
- 确保值班人员号码为E.164格式。
- 录音回调也需进行签名验证。
2) Outbound call with inline TwiML (REST) + status callbacks
2) 使用内联TwiML(REST)+ 状态回调发起外呼
Using curl:
bash
export TWILIO_ACCOUNT_SID="AC2f1c2d3e4f5a6b7c8d9e0f1a2b3c4d5"
export TWILIO_AUTH_TOKEN="9f8e7d6c5b4a3a2b1c0d9e8f7a6b5c4d"
curl -X POST "https://api.twilio.com/2010-04-01/Accounts/$TWILIO_ACCOUNT_SID/Calls.json" \
--data-urlencode "From=+14155551234" \
--data-urlencode "To=+14155559876" \
--data-urlencode "Twiml=<Response><Say voice=\"Polly.Joanna\">This is a test call.</Say><Hangup/></Response>" \
--data-urlencode "StatusCallback=https://voice.prod.example.com/webhooks/voice/status" \
--data-urlencode "StatusCallbackEvent=initiated ringing answered completed" \
-u "$TWILIO_ACCOUNT_SID:$TWILIO_AUTH_TOKEN"Notes:
- Inline TwiML is useful for simple notifications; for complex flows, use a URL.
使用curl:
bash
export TWILIO_ACCOUNT_SID="AC2f1c2d3e4f5a6b7c8d9e0f1a2b3c4d5"
export TWILIO_AUTH_TOKEN="9f8e7d6c5b4a3a2b1c0d9e8f7a6b5c4d"
curl -X POST "https://api.twilio.com/2010-04-01/Accounts/$TWILIO_ACCOUNT_SID/Calls.json" \
--data-urlencode "From=+14155551234" \
--data-urlencode "To=+14155559876" \
--data-urlencode "Twiml=<Response><Say voice=\"Polly.Joanna\">This is a test call.</Say><Hangup/></Response>" \
--data-urlencode "StatusCallback=https://voice.prod.example.com/webhooks/voice/status" \
--data-urlencode "StatusCallbackEvent=initiated ringing answered completed" \
-u "$TWILIO_ACCOUNT_SID:$TWILIO_AUTH_TOKEN"注意事项:
- 内联TwiML适用于简单通知;复杂流程建议使用URL。
3) Warm transfer using conference
3) 使用会议服务实现暖转移
TwiML pattern:
- Agent leg joins conference muted=false
- Customer leg joins conference
- Supervisor can join with for monitoring
startConferenceOnEnter=false
Example TwiML:
xml
<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Dial>
<Conference
startConferenceOnEnter="true"
endConferenceOnExit="true"
beep="onEnter"
record="record-from-start"
recordingStatusCallback="https://voice.prod.example.com/webhooks/recording"
>support-ticket-842193</Conference>
</Dial>
</Response>Operational notes:
- Use deterministic conference names to correlate with tickets.
- Use participant update API to mute/unmute during warm transfer.
TwiML模式:
- 坐席链路加入会议,静音状态为false
- 客户链路加入会议
- 主管可通过加入会议进行监控
startConferenceOnEnter=false
示例TwiML:
xml
<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Dial>
<Conference
startConferenceOnEnter="true"
endConferenceOnExit="true"
beep="onEnter"
record="record-from-start"
recordingStatusCallback="https://voice.prod.example.com/webhooks/recording"
>support-ticket-842193</Conference>
</Dial>
</Response>运维注意事项:
- 使用确定性会议名称关联工单。
- 使用参会者更新API在暖转移过程中静音/取消静音。
4) Voicemail drop with <Record>
and transcription pipeline
<Record>4) 使用<Record>
实现语音信箱投递并集成转写流水线
<Record>TwiML:
xml
<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Say voice="Polly.Joanna">Please leave a message after the tone.</Say>
<Record
maxLength="120"
timeout="5"
playBeep="true"
recordingStatusCallback="https://voice.prod.example.com/webhooks/recording"
recordingStatusCallbackMethod="POST"
/>
<Say voice="Polly.Joanna">Thank you. Goodbye.</Say>
<Hangup/>
</Response>Pipeline:
- Recording callback enqueues transcription job.
- Worker downloads and transcribes.
.wav - Store transcript keyed by .
RecordingSid
TwiML:
xml
<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Say voice="Polly.Joanna">Please leave a message after the tone.</Say>
<Record
maxLength="120"
timeout="5"
playBeep="true"
recordingStatusCallback="https://voice.prod.example.com/webhooks/recording"
recordingStatusCallbackMethod="POST"
/>
<Say voice="Polly.Joanna">Thank you. Goodbye.</Say>
<Hangup/>
</Response>流水线流程:
- 录音回调将转写任务加入队列。
- 工作节点下载文件并执行转写。
.wav - 存储转写结果并关联。
RecordingSid
5) SIP inbound → route to PSTN fallback
5) SIP呼入 → 路由至PSTN降级方案
TwiML:
xml
<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Say voice="Polly.Matthew">Routing your SIP call.</Say>
<Dial callerId="+14155551234" timeout="15">
<Number>+14155550199</Number>
</Dial>
<Say voice="Polly.Matthew">We could not connect your call.</Say>
<Hangup/>
</Response>Notes:
- Ensure SIP domain is configured to hit this TwiML app/webhook.
- Monitor SIP response codes and RTP stats if available.
TwiML:
xml
<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Say voice="Polly.Matthew">Routing your SIP call.</Say>
<Dial callerId="+14155551234" timeout="15">
<Number>+14155550199</Number>
</Dial>
<Say voice="Polly.Matthew">We could not connect your call.</Say>
<Hangup/>
</Response>注意事项:
- 确保SIP域名配置为指向该TwiML应用/Webhook。
- 若可用,监控SIP响应码和RTP统计数据。
6) IVR state machine with retries (DTMF)
6) 带重试机制的IVR状态机(DTMF)
Pseudo-implementation approach:
- State stored in Redis:
- key:
ivr:{CallSid} - value:
{state, retries}
- key:
- On , set state
/voice/inboundretries 0.MENU - On , validate digit:
/voice/menu- if invalid and retries < 2: increment retries, redirect to
/voice/inbound - else: route to operator or hang up
- if invalid and retries < 2: increment retries, redirect to
This avoids infinite loops and makes behavior deterministic.
伪实现思路:
- 状态存储在Redis中:
- 键:
ivr:{CallSid} - 值:
{state, retries}
- 键:
- 在中设置状态为
/voice/inbound,重试次数为0。MENU - 在中验证输入的数字:
/voice/menu- 若无效且重试次数 < 2:增加重试次数,重定向至
/voice/inbound - 否则:路由至人工或挂断
- 若无效且重试次数 < 2:增加重试次数,重定向至
此方案避免无限循环,确保行为确定性。
Quick Reference
快速参考
| Task | Command / API | Key flags / fields |
|---|---|---|
| Create outbound call | | |
| List calls | | |
| Fetch call | | |
| List recordings | | |
| Download recording | | |
| List conferences | | |
| List participants | | |
| Mute participant | | |
| Debug webhook failures | Twilio Console Debugger | look for |
| Validate webhook | helper libs | |
| 任务 | 命令 / API | 关键参数 / 字段 |
|---|---|---|
| 创建外呼 | | |
| 列出呼叫记录 | | |
| 获取单个呼叫详情 | | |
| 列出录音记录 | | |
| 下载录音 | | |
| 列出会议 | | |
| 列出参会者 | | |
| 静音参会者 | | |
| 调试Webhook故障 | Twilio Console Debugger | 查找 |
| 验证Webhook | 辅助库 | |
Graph Relationships
依赖关系
DEPENDS_ON
DEPENDS_ON
- (Account auth, REST API fundamentals, subaccounts)
twilio-core - (signature validation, retries, idempotency)
http-webhooks - (TLS termination, reverse proxy correctness)
tls-nginx - (structured logs, metrics, tracing)
observability
- (账号认证、REST API基础、子账号)
twilio-core - (签名验证、重试、幂等性)
http-webhooks - (TLS终止、反向代理正确性)
tls-nginx - (结构化日志、指标、追踪)
observability
COMPOSES
COMPOSES
- (SMS fallback, status webhooks, STOP handling)
twilio-messaging - (voice OTP, fraud guard, rate limiting)
twilio-verify - (email notifications for missed calls/voicemails)
sendgrid - (hybrid flows: Studio for rapid iteration + code for critical paths)
twilio-studio - (recording/transcription pipelines)
queue-workers
- (SMS降级、状态Webhook、退订处理)
twilio-messaging - (语音OTP、欺诈防护、速率限制)
twilio-verify - (未接来电/语音信箱的邮件通知)
sendgrid - (混合流程:Studio快速迭代 + 代码处理关键路径)
twilio-studio - (录音/转写流水线)
queue-workers
SIMILAR_TO
SIMILAR_TO
- (similar call control concepts, different APIs)
plivo-voice - (voice webhooks + NCCO vs TwiML)
vonage-voice - (contact center flows; heavier platform abstraction)
aws-connect
- (类似呼叫控制概念,API不同)
plivo-voice - (语音Webhook + NCCO vs TwiML)
vonage-voice - (联络中心流程;更重的平台抽象)
aws-connect