mcpField
T
twilio-conversations

twilio-conversations

github.com/alphaonedev开发44 次安装
代码仓库

关于 twilio-conversations

面向使用 OpenClaw 的工程师,提供在生产环境中实施和运营 Twilio Conversations 的指导。内容涵盖跨短信、WhatsApp 和聊天管理会话、参与者与消息,以及 Webhook、合规和可观测性实践。

主要功能

  1. 01创建和管理对话与参与者
  2. 02通过短信、WhatsApp 和聊天发送消息
  3. 03处理 Webhook 事件并跟踪送达状态
  4. 04使用幂等处理和重试
  5. 05执行退订合规与成本控制
  6. 06排查 Twilio 错误和 Webhook 故障

适用场景

  1. 01构建跨渠道统一消息线程
  2. 02处理消息送达、已读和失败事件
  3. 03运营支持短信退订的消息服务
  4. 04诊断 Webhook 故障并响应事件

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/alphaonedev/openclaw-graph --skill twilio-conversations

安装后在 Claude Code 中输入/twilio-conversations确认已加载。

详细介绍

twilio-conversations

用途

让 OpenClaw 能够在生产环境中实现和运行 Twilio Conversations:创建和管理跨 SMS、WhatsApp 和聊天渠道的对话、参与者及消息;集成用于送达、已读和失败状态的 Webhook;强制执行退订和合规要求;并与 Twilio Programmable Messaging/Voice/Verify/Studio/SendGrid 模式组合使用。

对工程师的具体价值:

  • 构建跨渠道(SMS、WhatsApp、应用内聊天)的统一“会话”抽象,并提供一致的参与者/消息 API。
  • 实现由 Webhook 驱动的可靠状态管理(送达、已读、失败),支持幂等性、重试和背压。
  • 支持大规模运行:速率限制、分页、成本控制(Messaging Services、地理位置匹配)和合规要求(STOP、10DLC、免费电话)。
  • 提供生产级可观测性,以及针对 Twilio 错误代码和 Webhook 故障的事件响应手册。

先决条件

账户和 Twilio 设置
  • 已启用 Conversations 的 Twilio 账户。
  • 至少具备以下一项:
    • 支持 SMS 的电话号码(适用时,需为美国 A2P 注册 10DLC)
    • WhatsApp 发件人(通过 Twilio 接入 WhatsApp Business API)
    • Messaging Service(建议使用,以便扩展并控制成本)
  • Twilio 可访问的 Webhook 端点(公开 HTTPS)。本地开发可使用 ngrok 或 cloudflared。
运行时版本(已测试)
  • Node.js:20.11.1(LTS)
  • Python:3.11.7
  • Twilio Node SDK:4.23.0
  • Twilio Python SDK:9.4.1
  • OpenSSL:1.1.1w 或 3.0.13(取决于平台)
  • Docker:25.0.3(可选)
  • PostgreSQL:15.5(可选,用于持久化 Webhook/事件)
  • Redis:7.2.4(可选,用于幂等键/速率限制)
操作系统支持
  • Ubuntu 22.04 LTS(x86_64、arm64)
  • Fedora 39(x86_64)
  • macOS 14 Sonoma(Intel 和 Apple Silicon)
身份验证设置

生产服务建议使用 Twilio API Key,而非 Account SID + Auth Token。

  1. 创建 API Key:
    • Twilio Console → Account → API keys & tokens → Create API key
  2. 保存:
    • TWILIO_ACCOUNT_SID(以 AC... 开头)
    • TWILIO_API_KEY_SID(以 SK... 开头)
    • TWILIO_API_KEY_SECRET

环境变量(示例):

export TWILIO_ACCOUNT_SID="YOUR_ACCOUNT_SID"
export TWILIO_API_KEY_SID="YOUR_API_KEY_SID"
export TWILIO_API_KEY_SECRET="YOUR_API_KEY_SECRET"

如果必须使用 Auth Token(旧方式):

export TWILIO_AUTH_TOKEN="your_auth_token"
网络和防火墙
  • 允许通过 HTTPS 出站访问 api.twilio.com 和 conversations.twilio.com。
  • 入站 Webhook 端点必须允许 Twilio IP 范围访问(或验证签名;建议验证签名,而非使用 IP 允许列表)。

核心概念

Conversations 心智模型
  • Conversation:会话容器。包含 sid(CH...)、uniqueName、属性、计时器和状态。
  • Participant:对话中的实体。类型包括:
    • 聊天参与者(基于身份):identity="user-123"
    • 消息参与者(基于电话号码):messagingBinding.address="+14155550100" 和 proxyAddress(Twilio 号码或 Messaging Service 发件人)
    • WhatsApp 参与者:地址类似于 whatsapp:+14155550100
  • Message:在对话中发送的内容。包含作者、正文、媒体、属性和送达回执(取决于渠道)。
  • Webhook 事件:Twilio 会针对对话、消息、参与者生命周期及送达状态发送 HTTP 回调。
架构概览(生产环境)
  1. 入口:
    • 入站 SMS/WhatsApp 会触发 Programmable Messaging Webhook。
    • 应用内聊天使用 Conversations SDK(Web/移动端)或 REST API。
  2. 路由:
    • 将入站消息映射到 Conversation(根据电话号码、身份或业务键)。
    • 添加或确保参与者存在。
  3. 出口:
    • 通过 Conversations API 发送消息(统一会话时优先使用),或通过 Messaging API 发送(用于非会话式群发)。
  4. 状态和可观测性:
    • 持久化 Webhook 事件(仅追加),并据此推导状态(送达、已读、失败)。
    • 使用幂等键处理 Twilio 重试。
  5. 合规:
    • 处理 STOP/START/HELP(Messaging 合规要求),并移除参与者或将其加入黑名单。
    • 美国流量需完成 10DLC/免费电话验证;WhatsApp 需遵守模板规则。
关键标识符
  • Account SID:AC...
  • Conversation SID:CH...
  • Participant SID:MB...(因情况而异)
  • Message SID:IM...
  • Service SID(Conversations Service):IS...(如果使用 Services)
  • Messaging Service SID:MG...
Webhook 签名验证

Twilio 使用 X-Twilio-Signature 对 Webhook 请求签名。请使用 Twilio 调用时的确切 URL(包括查询字符串)和 POST 参数进行验证。


安装与设置

官方 Python SDK — Conversations

仓库: https://github.com/twilio/twilio-python
PyPI: pip install twilio · 支持: Python 3.7–3.13

from twilio.rest import Client
client = Client()

# Create conversation
conv = client.conversations.v1.conversations.create(
    friendly_name="Support Chat #123"
)

# Add participant (SMS)
p = client.conversations.v1.conversations(conv.sid) \
    .participants.create(
        messaging_binding_address="+15558675309",
        messaging_binding_proxy_address="+15017250604"
    )

# Send message
client.conversations.v1.conversations(conv.sid) \
    .messages.create(body="Welcome to support chat!", author="system")

来源:twilio/twilio-python — conversations

1) 系统依赖项
Ubuntu 22.04
sudo apt-get update
sudo apt-get install -y ca-certificates curl gnupg jq

通过 NodeSource 安装 Node.js 20.11.1:

curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt-get install -y nodejs
node -v  # v20.11.1 (or newer 20.x)
npm -v

Python 3.11:

sudo apt-get install -y python3.11 python3.11-venv python3-pip
python3.11 --version
Fedora 39
sudo dnf install -y jq curl ca-certificates
sudo dnf module install -y nodejs:20
node -v
sudo dnf install -y python3.11 python3.11-pip
python3.11 --version
macOS 14(Intel/Apple Silicon)

Homebrew:

brew update
brew install jq node@20 [email protected]
node -v
python3.11 --version
2) 项目依赖项
Node(推荐用于 Webhook 服务)
mkdir -p twilio-conversations-service && cd twilio-conversations-service
npm init -y
npm install [email protected] [email protected] [email protected] [email protected] [email protected]
npm install --save-dev [email protected] [email protected] @types/[email protected] @types/[email protected]
Python(批处理作业 / 管理工具)
python3.11 -m venv .venv
source .venv/bin/activate
pip install --upgrade pip==24.0
pip install twilio==9.4.1 requests==2.31.0
3) 使用签名验证的 Webhook 端点(Express)

创建 src/server.ts:

import express from "express";
import bodyParser from "body-parser";
import pinoHttp from "pino-http";
import twilio from "twilio";

const app = express();
app.use(pinoHttp());

// Twilio signature validation requires the raw body for some frameworks.
// For Express with urlencoded, Twilio helper can validate using parsed params.
// Ensure you use the exact URL configured in Twilio Console.
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());

const {
  TWILIO_AUTH_TOKEN,
  PUBLIC_WEBHOOK_BASE_URL,
} = process.env;

if (!TWILIO_AUTH_TOKEN) throw new Error("TWILIO_AUTH_TOKEN is required for webhook signature validation");
if (!PUBLIC_WEBHOOK_BASE_URL) throw new Error("PUBLIC_WEBHOOK_BASE_URL is required (e.g., https://example.com)");

app.post("/twilio/conversations/webhook", (req, res) => {
  const signature = req.header("X-Twilio-Signature") || "";
  const url = `${PUBLIC_WEBHOOK_BASE_URL}/twilio/conversations/webhook`;

  const isValid = twilio.validateRequest(
    TWILIO_AUTH_TOKEN,
    signature,
    url,
    req.body
  );

  if (!isValid) {
    req.log.warn({ signature }, "Invalid Twilio signature");
    return res.status(403).send("Forbidden");
  }

  // Idempotency: Twilio may retry. Use EventSid or MessageSid as a dedupe key.
  const eventType = req.body.EventType;
  const eventSid = req.body.EventSid;
  req.log.info({ eventType, eventSid, body: req.body }, "Twilio Conversations webhook");

  // TODO: enqueue to a worker; respond fast.
  res.status(200).send("ok");
});

const port = Number(process.env.PORT || 3000);
app.listen(port, () => {
  // eslint-disable-next-line no-console
  console.log(`listening on :${port}`);
});

运行:

npx ts-node src/server.ts

使用 ngrok 暴露服务:

ngrok http 3000
# Set PUBLIC_WEBHOOK_BASE_URL to the https URL ngrok gives you
4) 配置 Conversations Webhook

在 Twilio Console 中:

  • Conversations → Services →(你的服务)→ Webhooks
  • 配置:
    • Pre-event Webhook(可选;用于授权 / 路由决策)
    • Post-event Webhook(推荐;用于事件接收)
  • 指向:
    • https://your-domain.example.com/twilio/conversations/webhook

如果要将入站 SMS/WhatsApp 消息接入 Conversations,也要将 Programmable Messaging 入站 Webhook 配置到你的应用(如果你要自定义映射),或者使用 Conversations 的消息绑定(首选)。


主要功能

创建和管理 Conversations
  • 使用 uniqueName 创建会话,以便进行幂等查找。
  • 设置 attributes JSON,以保存业务元数据(tenantId、caseId、SLA)。
  • 管理生命周期:state(active/inactive/closed)、计时器和清理。
添加参与者(聊天 + 消息 + WhatsApp)
  • 通过 identity 添加聊天参与者(适用于 SDK 用户)。
  • 通过 messagingBinding.address 和 proxyAddress(Twilio 发件人)添加消息参与者。
  • WhatsApp 地址使用 whatsapp:+E164。

生产环境实践:

  • 始终确保业务实体 → 会话 uniqueName 之间有确定性映射。
  • 添加参与者前,先实施参与者数量限制和拦截名单检查。
发送带送达跟踪的消息
  • 使用 author 和 body 发送消息。
  • 附加 attributes,以记录关联 ID。
  • 通过 Webhook 事件和/或消息状态回调(取决于渠道)跟踪送达情况。
Webhook:事件接收、重试与幂等性
  • 验证 X-Twilio-Signature。
  • 在 5 秒内响应;将工作加入队列。
  • 根据 EventSid(首选)或 (MessageSid, EventType, Timestamp) 去重。
合规:STOP/START/HELP 和选择退出
  • 对于 SMS/WhatsApp,STOP 处理主要属于 Programmable Messaging 的职责。
  • 如果你将入站消息接入 Conversations,仍必须遵守选择退出要求:
    • 按电话号码维护抑制名单。
    • 收到 STOP 后,移除参与者或将其标记为已拦截;阻止发送出站消息。
使用 Messaging Services 优化成本
  • 使用具备以下功能的 Messaging Service:
    • 地理位置匹配
    • 固定发件人
    • 智能编码
  • 对于 Conversations 消息参与者,将 proxyAddress 设置为 Twilio 号码;大规模使用时,优先选择 Messaging Service(如果你的设计支持;通常通过 Messaging API 发送出站消息,同时将 Conversations 保留为系统记录)。
与 Voice/Verify/Studio/SendGrid 组合使用
  • 将会话升级为 Voice(拨号/会议),并将通话资料发布回会话。
  • 添加参与者或显示敏感信息前,使用 Verify 进行增强身份验证。
  • 在会话事件发生时触发 Studio 工作流。
  • 发送 SendGrid 事务性电子邮件,并将其作为消息/属性同步到会话中。

命令参考

此技能假设 OpenClaw 可以通过以下方式执行:

  • Twilio SDK(Node/Python)
  • 直接调用 REST(curl)
  • Twilio CLI(可选;对 Conversations 的支持有限)
REST API 基础地址
  • Conversations API 基础地址:https://conversations.twilio.com/v1

身份验证选项:

  • 使用 API Key 进行基本身份验证:
    • 用户名:TWILIO_API_KEY_SID
    • 密码:TWILIO_API_KEY_SECRET
  • 或使用 Account SID + Auth Token。
curl 辅助程序(API Key)
export TWILIO_API_KEY_SID="YOUR_API_KEY_SID"
export TWILIO_API_KEY_SECRET="YOUR_API_KEY_SECRET"
export TWILIO_ACCOUNT_SID="YOUR_ACCOUNT_SID"
Conversations
创建对话

端点:

  • POST /v1/Conversations

标志/字段:

  • FriendlyName(字符串)
  • UniqueName(字符串;用于实现幂等性)
  • Attributes(字符串化 JSON)
  • MessagingServiceSid(字符串;可选)
  • State(active|inactive|closed)
  • Timers.Inactive(ISO-8601 时长字符串,例如 PT1H;取决于 API 支持情况)

curl:

curl -sS -X POST "https://conversations.twilio.com/v1/Conversations" \
  -u "$TWILIO_API_KEY_SID:$TWILIO_API_KEY_SECRET" \
  --data-urlencode "FriendlyName=Support Case 10492" \
  --data-urlencode "UniqueName=case-10492" \
  --data-urlencode 'Attributes={"tenantId":"acme","caseId":10492,"priority":"p1"}'

Node:

import twilio from "twilio";

const client = twilio(process.env.TWILIO_API_KEY_SID, process.env.TWILIO_API_KEY_SECRET, {
  accountSid: process.env.TWILIO_ACCOUNT_SID,
});

const conv = await client.conversations.v1.conversations.create({
  friendlyName: "Support Case 10492",
  uniqueName: "case-10492",
  attributes: JSON.stringify({ tenantId: "acme", caseId: 10492, priority: "p1" }),
});
console.log(conv.sid);
获取对话
  • GET /v1/Conversations/{ConversationSid}
curl -sS "https://conversations.twilio.com/v1/Conversations/CHXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" \
  -u "$TWILIO_API_KEY_SID:$TWILIO_API_KEY_SECRET"
列出对话(分页)
  • GET /v1/Conversations?PageSize=50&PageToken=...

查询参数:

  • PageSize(1–1000;实际建议:50–200)
  • PageToken(字符串)
  • 根据 API 版本,可能支持按 State 筛选
curl -sS "https://conversations.twilio.com/v1/Conversations?PageSize=50" \
  -u "$TWILIO_API_KEY_SID:$TWILIO_API_KEY_SECRET"
更新对话
  • POST /v1/Conversations/{ConversationSid}

字段:

  • FriendlyName
  • Attributes(字符串化 JSON)
  • State
curl -sS -X POST "https://conversations.twilio.com/v1/Conversations/CHXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" \
  -u "$TWILIO_API_KEY_SID:$TWILIO_API_KEY_SECRET" \
  --data-urlencode "State=closed" \
  --data-urlencode 'Attributes={"tenantId":"acme","caseId":10492,"priority":"p1","closedBy":"agent-7"}'
删除对话
  • DELETE /v1/Conversations/{ConversationSid}
curl -sS -X DELETE "https://conversations.twilio.com/v1/Conversations/CHXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX" \
  -u "$TWILIO_API_KEY_SID:$TWILIO_API_KEY_SECRET"
参与者
添加聊天参与者(身份)
  • POST /v1/Conversations/{ConversationSid}/Participants

字段:

  • Identity(字符串)
  • Attributes(字符串化 JSON)
curl -sS -X POST "https://conversations.twilio.com/v1/Conversations/CH.../Participants" \
  -u "$TWILIO_API_KEY_SID:$TWILIO_API_KEY_SECRET" \
  --data-urlencode "Identity=user-123" \
  --data-urlencode 'Attributes={"role":"customer"}'
添加消息参与者(SMS)

字段:

  • MessagingBinding.Address(E.164 格式,例如 +14155550100)
  • MessagingBinding.ProxyAddress(E.164 格式的 Twilio 号码,例如 +14155551234)
  • Attributes
curl -sS -X POST "https://conversations.twilio.com/v1/Conversations/CH.../Participants" \
  -u "$TWILIO_API_KEY_SID:$TWILIO_API_KEY_SECRET" \
  --data-urlencode "MessagingBinding.Address=+14155550100" \
  --data-urlencode "MessagingBinding.ProxyAddress=+14155551234" \
  --data-urlencode 'Attributes={"role":"customer","channel":"sms"}'
添加消息参与者(WhatsApp)

使用 whatsapp: 前缀:

curl -sS -X POST "https://conversations.twilio.com/v1/Conversations/CH.../Participants" \
  -u "$TWILIO_API_KEY_SID:$TWILIO_API_KEY_SECRET" \
  --data-urlencode "MessagingBinding.Address=whatsapp:+14155550100" \
  --data-urlencode "MessagingBinding.ProxyAddress=whatsapp:+14155559876" \
  --data-urlencode 'Attributes={"role":"customer","channel":"whatsapp"}'
列出参与者
  • GET /v1/Conversations/{ConversationSid}/Participants?PageSize=50
curl -sS "https://conversations.twilio.com/v1/Conversations/CH.../Participants?PageSize=50" \
  -u "$TWILIO_API_KEY_SID:$TWILIO_API_KEY_SECRET"
移除参与者
  • DELETE /v1/Conversations/{ConversationSid}/Participants/{ParticipantSid}
curl -sS -X DELETE "https://conversations.twilio.com/v1/Conversations/CH.../Participants/MB..." \
  -u "$TWILIO_API_KEY_SID:$TWILIO_API_KEY_SECRET"
消息
发送消息
  • POST /v1/Conversations/{ConversationSid}/Messages

字段:

  • Author(字符串;身份或系统标签)
  • Body(字符串)
  • Attributes(字符串化 JSON)
  • MediaSid / 媒体字段(使用媒体时;取决于 API)
curl -sS -X POST "https://conversations.twilio.com/v1/Conversations/CH.../Messages" \
  -u "$TWILIO_API_KEY_SID:$TWILIO_API_KEY_SECRET" \
  --data-urlencode "Author=agent-7" \
  --data-urlencode "Body=We’re looking into this now. ETA 15 minutes." \
  --data-urlencode 'Attributes={"correlationId":"req-01HPQ9K7Z9Y7J8V7Z0","visibility":"customer"}'
列出消息
  • GET /v1/Conversations/{ConversationSid}/Messages?PageSize=50&Order=asc|desc
curl -sS "https://conversations.twilio.com/v1/Conversations/CH.../Messages?PageSize=50&Order=desc" \
  -u "$TWILIO_API_KEY_SID:$TWILIO_API_KEY_SECRET"
获取消息
  • GET /v1/Conversations/{ConversationSid}/Messages/{MessageSid}
curl -sS "https://conversations.twilio.com/v1/Conversations/CH.../Messages/IM..." \
  -u "$TWILIO_API_KEY_SID:$TWILIO_API_KEY_SECRET"
删除消息(内容审核)
  • DELETE /v1/Conversations/{ConversationSid}/Messages/{MessageSid}
curl -sS -X DELETE "https://conversations.twilio.com/v1/Conversations/CH.../Messages/IM..." \
  -u "$TWILIO_API_KEY_SID:$TWILIO_API_KEY_SECRET"
Webhook(Conversations 服务)

Conversations 通常使用 Service 来配置默认设置和 Webhook。

  • 列出服务:GET /v1/Services
  • 创建服务:POST /v1/Services
  • 配置 Webhook:POST /v1/Services/{ServiceSid}/Configuration/Webhooks

关键字段(因 Webhook 类型而异):

  • PostWebhookUrl
  • PostWebhookMethod(GET|POST)
  • Filters(事件类型数组)
  • PreWebhookUrl、PreWebhookMethod
  • WebhookTimeout(秒;若受支持)

由于 Webhook 配置字段会不断变化,建议在初始设置时优先使用 SDK 类型定义或控制台;之后尽可能将配置导出到 IaC(Terraform)。


配置参考

环境变量

建议文件:/etc/openclaw/twilio-conversations.env(Linux)或 ~/.config/openclaw/twilio-conversations.env(开发环境)

TWILIO_ACCOUNT_SID=YOUR_ACCOUNT_SID
TWILIO_API_KEY_SID=YOUR_API_KEY_SID
TWILIO_API_KEY_SECRET=replace_me
TWILIO_AUTH_TOKEN=replace_me_for_webhook_validation_only

PUBLIC_WEBHOOK_BASE_URL=https://conversations-webhooks.acme.example
TWILIO_CONVERSATIONS_SERVICE_SID=YOUR_IS_SID

DEFAULT_PROXY_ADDRESS=+14155551234
DEFAULT_WHATSAPP_PROXY=whatsapp:+14155559876

# Compliance / suppression
SUPPRESSION_REDIS_URL=redis://:[email protected]:6379/2

使用 systemd 单元加载:

/etc/systemd/system/openclaw-twilio-conversations.service:

[Unit]
Description=OpenClaw Twilio Conversations Webhook Service
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
EnvironmentFile=/etc/openclaw/twilio-conversations.env
WorkingDirectory=/opt/openclaw/twilio-conversations
ExecStart=/usr/bin/node dist/server.js
Restart=on-failure
RestartSec=2
User=openclaw
Group=openclaw
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/var/lib/openclaw /var/log/openclaw

[Install]
WantedBy=multi-user.target
OpenClaw 技能配置(示例)

/opt/openclaw/config/skills/twilio-conversations.toml:

[twilio_conversations]
service_sid = "YOUR_IS_SID"
default_proxy_address = "+14155551234"
default_whatsapp_proxy = "whatsapp:+14155559876"

[twilio_conversations.webhooks]
public_base_url = "https://conversations-webhooks.acme.example"
path = "/twilio/conversations/webhook"
validate_signature = true
max_processing_ms = 2000

[twilio_conversations.idempotency]
backend = "redis"
redis_url = "redis://:[email protected]:6379/2"
ttl_seconds = 86400

[twilio_conversations.compliance]
enforce_stop = true
stop_keywords = ["STOP", "STOPALL", "UNSUBSCRIBE", "CANCEL", "END", "QUIT"]
start_keywords = ["START", "YES", "UNSTOP"]
help_keywords = ["HELP", "INFO"]
NGINX 反向代理

/etc/nginx/conf.d/openclaw-twilio-conversations.conf:

server {
  listen 443 ssl http2;
  server_name conversations-webhooks.acme.example;

  ssl_certificate     /etc/letsencrypt/live/conversations-webhooks.acme.example/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/conversations-webhooks.acme.example/privkey.pem;

  client_max_body_size 1m;

  location /twilio/conversations/webhook {
    proxy_pass http://127.0.0.1:3000/twilio/conversations/webhook;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

    proxy_connect_timeout 2s;
    proxy_read_timeout 5s;
    proxy_send_timeout 5s;
  }
}

集成模式

模式:入站 SMS → 映射到对话 → Agent 回复

工作流:

  1. Programmable Messaging 入站 webhook 接收 SMS。
  2. 根据 uniqueName = "sms:+14155550100" 查找或创建对话(也可以按租户范围)。
  3. 确保消息参与者存在,且 MessagingBinding.Address = 发送方。
  4. 将入站消息发布到对话中(作者 = 电话号码)。
  5. Agent 通过 Conversations 消息 API 回复。

关键点:入站 SMS 已作为 Messaging 事件存在;你要将其镜像到 Conversations,以便统一会话线程。确保不会重复发送出站消息。

模式:以 Conversations 作为事实来源,使用 Messaging Service 发送出站消息
  • 在 Conversations 中存储所有消息,以供 UI 展示和查看历史记录。
  • 大规模发送出站 SMS 时,通过 Messaging API 使用 MessagingServiceSid=MG...,以实现地理位置匹配和吞吐量优化。
  • 将出站消息镜像到 Conversations,并附上用于关联 Messaging MessageSid 的属性。

这样既能避免管理发送方的复杂性,也能保留单一会话线程。

模式:添加新的电话参与者前先验证
  • 用户请求将电话号码添加到对话中。
  • 向该号码触发 Verify V2 SMS。
  • 只有验证成功后,才添加为消息参与者。
模式:升级到 Voice,并附加通话录音/转录文本
  • 对话中出现升级处理关键词时,创建 Voice 呼叫(TwiML Dial/Conference)。
  • 启用录音和转录。
  • 将录音 URL/转录文本作为消息或对话属性发布回对话中。
模式:由对话事件触发 Studio 流程
  • 事件后 webhook 接收 onMessageAdded。
  • 对于特定意图,调用 Studio REST Trigger API 来运行流程。
  • 流程可以通过 Conversations 或 Messaging 发送消息。

错误处理与故障排查

根据代码、HTTP 状态和是否可重试来处理 Twilio 错误。始终记录:

  • Twilio X-Twilio-Request-Id
  • HTTP 状态
  • 错误代码/消息
  • 相关 SID
1)认证失败(20003)

典型错误:

  • HTTP 401
  • 正文包含:
    • "code": 20003
    • "message": "Authenticate"

根本原因:

  • API 密钥 secret 错误、身份验证令牌错误,或在 SDK 配置中使用 API 密钥时未提供 accountSid。

解决方法:

  • 验证 TWILIO_API_KEY_SID/SECRET。
  • 在 Node SDK 中使用 API 密钥时,传入 { accountSid: TWILIO_ACCOUNT_SID }。
  • 轮换已泄露的密钥。
2)速率限制(20429)

错误:

  • HTTP 429
  • "code": 20429
  • "message": "Too Many Requests"

根本原因:

  • 突发流量超过 Twilio API 限制或账户并发限制。

解决方法:

  • 实现带随机抖动的指数退避(例如,基础延迟 250ms,最大 5s)。
  • 将写入操作加入队列;对列表操作进行批处理;发生超时时减小 PageSize。
  • 为每个账户/服务设置工作进程并发上限。
3)电话号码无效(21211)

错误:

  • HTTP 400
  • "code": 21211
  • "message": "The 'To' number +1415555 is not a valid phone number."

根本原因:

  • 格式不是 E.164、缺少国家代码或包含无效字符。

解决方法:

  • 添加消息参与者前,将号码规范化为 E.164 格式。
  • 在应用层使用 libphonenumber。
  • 尽早拒绝无效号码,并提供明确的错误信息。
4)Messaging 消息发送失败(30003)

错误(Messaging 状态回调):

  • "ErrorCode": "30003"
  • "MessageStatus": "undelivered"
  • "ErrorMessage": "Unreachable destination handset."(取决于运营商)

根本原因:

  • 运营商网络无法连接、手机关机、号码已屏蔽或路由无效。

解决方法:

  • 将参与者标记为无法联系;回退到其他渠道(WhatsApp/电子邮件/语音)。
  • 重试策略:限制重试次数,避免无限循环。
  • 对于 WhatsApp,确保模板和用户选择加入设置正确。
5) Webhook 签名验证失败

日志:

  • Invalid Twilio signature

根本原因:

  • PUBLIC_WEBHOOK_BASE_URL 与 Twilio 实际调用的 URL 不匹配(协议/主机/路径)。
  • NGINX 重写了路径,或验证 URL 中缺少查询字符串。
  • Twilio 发送 application/x-www-form-urlencoded(或反之)时使用了 JSON 请求体解析。

修复方法:

  • 确保验证 URL 与 Twilio 配置完全一致。
  • 通过 X-Forwarded-* 保留原始主机/协议,并正确重建 URL。
  • 确认内容类型和请求体解析方式。
6) 参与者已存在

常见 API 错误:

  • HTTP 409
  • 类似以下内容的消息:
    • "message": "Participant already exists"

根本原因:

  • 重试/并发情况下,添加参与者的调用不具备幂等性。

修复方法:

  • 如果参与者符合预期绑定,则将 409 视为成功。
  • 创建前按确定性方式查找参与者(列出参与者并按身份/地址匹配)。
  • 按会话串行执行参与者创建。
7) 未找到会话

错误:

  • HTTP 404
  • "message": "The requested resource /Conversations/CH... was not found"

根本原因:

  • SID 错误、会话已删除,或 SID 属于其他账户。

修复方法:

  • 验证 SID 前缀和长度。
  • 确保使用正确的账户凭据。
  • 如果使用 uniqueName,请通过列出/筛选重新解析(或在数据库中存储映射)。
8) Webhook 超时和重试

表现:

  • Twilio 多次重试同一事件;日志中出现重复记录。

根本原因:

  • Webhook 处理程序超过 Twilio 超时时间,或返回非 2xx 状态码。
  • 下游依赖项(数据库)响应缓慢。

修复方法:

  • 入队后立即返回 200。
  • 使用持久化队列(SQS/Kafka/RabbitMQ)并异步处理。
  • 使用以 EventSid 为键、TTL 为 24 小时以上的 Redis 实现幂等性。
9) WhatsApp 模板/会话问题(常见)

表现:

  • WhatsApp 出站消息因政策相关错误发送失败(因地区/账户而异)。

根本原因:

  • 尝试在 24 小时会话窗口外发送自由格式消息;此时必须使用模板。

修复方法:

  • 使用已批准的模板重新联系用户。
  • 跟踪每位参与者最后一条入站消息的时间;确保按要求使用模板。
10) 违反退订要求

表现:

  • 运营商过滤、投诉或 Twilio 合规警告。

根本原因:

  • 用户发送 STOP 后仍继续发送 SMS,或未在各系统中遵守退订要求。

修复方法:

  • 使用集中式屏蔽列表;每次发送出站消息前都进行检查。
  • 收到 STOP 入站消息后,立即将用户加入屏蔽列表,并根据政策确认退订。

安全加固

密钥管理
  • 不要将 TWILIO_API_KEY_SECRET 存储在代码库中。
  • 使用:
    • AWS Secrets Manager / GCP Secret Manager / Vault
    • 在可用时使用 systemd LoadCredential=(systemd 252+)
  • 每季度轮换 API 密钥;发生安全事件时立即轮换。
Webhook 验证
  • 始终验证 X-Twilio-Signature。
  • 仅允许 HTTPS;在边缘层将 HTTP 重定向到 HTTPS。
  • 拒绝缺少签名的请求。
最小权限
  • 为每项服务使用单独的 API 密钥(Webhook 与批处理作业分开)。
  • 根据内部政策限制密钥的使用(Twilio 密钥适用于整个账户;通过网络分段和密钥分发实施限制)。
数据最小化
  • 如果不需要,避免存储完整消息正文;仅存储哈希/元数据。
  • 如果存储消息正文,请对静态数据加密(Postgres TDE 的替代方案:磁盘加密 + 应用级信封加密)。
符合 CIS 的主机加固(实用映射)
  • CIS Ubuntu Linux 22.04 LTS 基准:
    • 禁用 SSH 密码认证;强制使用密钥认证。
    • 启用自动安全更新。
    • 将入站端口限制为仅允许 Webhook 边缘使用 443。
  • 以非 root 用户(openclaw 用户)运行服务。
  • systemd 沙箱设置:
    • NoNewPrivileges=true
    • ProtectSystem=strict
    • ProtectHome=true
    • PrivateTmp=true
审计日志
  • 记录所有管理操作:
    • 创建/关闭/删除会话
    • 添加/移除参与者
    • 发送出站消息
  • 包含关联 ID 和操作者身份。

性能调优

Webhook 接收延迟

目标:Webhook 处理器的 p95 延迟 < 50ms,且始终 < 500ms。

优化措施:

  • 解析并验证签名,然后将请求加入队列并返回 200。
  • 避免在请求线程中同步写入数据库。

预期影响:

  • 优化前:数据库竞争时 p95 延迟为 800–1500ms → Twilio 重试。
  • 优化后:p95 延迟为 10–30ms;重试几乎为零。
API 速率限制与批处理
  • 列表操作使用 PageSize=200 以减少往返次数,但要留意响应大小和超时。
  • 在 Redis 中按 uniqueName 缓存对话 SID(TTL 为 1h),避免调用列表或搜索接口。

预期影响:

  • 在高流量路由服务中,将 Twilio API 调用减少 60–90%。
参与者查询策略
  • 维护自己的映射表:
    • (tenantId, externalUserId) -> conversationSid
    • (tenantId, phoneE164) -> conversationSid
  • 避免通过列出参与者或消息来“发现”状态。

预期影响:

  • 避免 O(n) 扫描;随着对话规模增大,延迟仍保持稳定。
消息扇出控制

如果添加许多参与者(群聊),外发消息扇出可能代价高昂且缓慢。

  • 为每个对话设置参与者人数上限(策略)。
  • 对于广播,使用 Messaging API 和分段发送,而不是使用单个对话。

高级主题

用于授权的事件前 Webhook

使用事件前 Webhook 来:

  • 阻止未知身份添加参与者。
  • 强制执行租户边界(身份必须与对话属性匹配)。
  • 在接受消息前拒绝包含不允许内容(PII 泄露)的消息。

实现说明:

  • 事件前 Webhook 必须具有高可用性;故障可能阻断消息流。
  • 按照 Twilio 事件前 Webhook 契约,明确返回允许或拒绝。
幂等的对话创建

Twilio 不保证并发调用方能够通过 uniqueName 原子地执行“若不存在则创建”。

模式:

  1. 尝试使用 UniqueName 创建。
  2. 如果发生冲突或重复,则通过映射数据库(首选)或列表/筛选(备用)按 UniqueName 获取。
  3. 存储映射。
多租户隔离
  • 在 uniqueName 前添加租户前缀:t_acme_case_10492
  • 在 attributes 中存储 tenantId。
  • 处理每个 Webhook 事件前验证租户。
消息排序与最终一致性
  • Webhook 事件可能乱序到达。
  • 使用事件时间戳和消息索引(如果提供)进行排序。
  • 将 Webhook 视为事件流;构建支持重放能力的派生状态。
媒体处理
  • 媒体消息可能需要单独的检索和存储策略。
  • 强制执行内容类型允许列表和大小限制。
  • 在向内部用户公开入站媒体前,考虑进行病毒扫描。
与 Programmable Messaging 状态回调的互操作

你可能会收到:

  • Conversations 事件后 Webhook(添加消息)
  • Messaging 状态回调(已送达/失败)

通过以下方式统一处理:

  • 通过 Messaging API 发送时,将 Messaging MessageSid 存储在 Conversations 消息属性中。
  • 收到状态回调后,更新内部消息状态,并可选择向对话中发送系统消息(或更新外部 UI)。

使用示例

场景 1:为支持工单创建对话,添加 SMS 客户和聊天 Agent,并发送初始消息
# 1) Create conversation
CONV_SID=$(curl -sS -X POST "https://conversations.twilio.com/v1/Conversations" \
  -u "$TWILIO_API_KEY_SID:$TWILIO_API_KEY_SECRET" \
  --data-urlencode "FriendlyName=Support Case 10492" \
  --data-urlencode "UniqueName=t_acme_case_10492" \
  --data-urlencode 'Attributes={"tenantId":"acme","caseId":10492,"priority":"p1"}' | jq -r .sid)

echo "$CONV_SID"

# 2) Add SMS participant (customer)
curl -sS -X POST "https://conversations.twilio.com/v1/Conversations/$CONV_SID/Participants" \
  -u "$TWILIO_API_KEY_SID:$TWILIO_API_KEY_SECRET" \
  --data-urlencode "MessagingBinding.Address=+14155550100" \
  --data-urlencode "MessagingBinding.ProxyAddress=+14155551234" \
  --data-urlencode 'Attributes={"role":"customer","channel":"sms"}' | jq .

# 3) Add chat participant (agent)
curl -sS -X POST "https://conversations.twilio.com/v1/Conversations/$CONV_SID/Participants" \
  -u "$TWILIO_API_KEY_SID:$TWILIO_API_KEY_SECRET" \
  --data-urlencode "Identity=agent-7" \
  --data-urlencode 'Attributes={"role":"agent"}' | jq .

# 4) Send message
curl -sS -X POST "https://conversations.twilio.com/v1/Conversations/$CONV_SID/Messages" \
  -u "$TWILIO_API_KEY_SID:$TWILIO_API_KEY_SECRET" \
  --data-urlencode "Author=agent-7" \
  --data-urlencode "Body=Hi—this is Acme Support. We’re on it." \
  --data-urlencode 'Attributes={"correlationId":"req-01HPQ9K7Z9Y7J8V7Z0"}' | jq .
场景 2:使用抑制列表(Redis)处理入站 STOP 并移除参与者

用于处理入站消息 Webhook(来自 Messaging)并强制执行 STOP 的 Python 代码片段:

import os
import redis
from twilio.rest import Client

STOP_WORDS = {"STOP", "STOPALL", "UNSUBSCRIBE", "CANCEL", "END", "QUIT"}
START_WORDS = {"START", "YES", "UNSTOP"}

r = redis.Redis.from_url(os.environ["SUPPRESSION_REDIS_URL"])
client = Client(os.environ["TWILIO_API_KEY_SID"], os.environ["TWILIO_API_KEY_SECRET"], os.environ["TWILIO_ACCOUNT_SID"])

def handle_inbound_sms(from_e164: str, body: str, conversation_sid: str, participant_sid: str):
    normalized = body.strip().upper()
    key = f"suppress:sms:{from_e164}"

    if normalized in STOP_WORDS:
        r.set(key, "1")
        # Remove participant to prevent further outbound via Conversations
        client.conversations.v1.conversations(conversation_sid).participants(participant_sid).delete()
        return {"action": "suppressed"}

    if normalized in START_WORDS:
        r.delete(key)
        return {"action": "unsuppressed"}

    if r.get(key):
        return {"action": "ignored_suppressed"}

    return {"action": "accepted"}
场景 3:使用 EventSid 作为键,通过幂等性处理 Webhook 接收(Redis)

核心逻辑的 Node 代码片段:

import Redis from "ioredis";

const redis = new Redis(process.env.SUPPRESSION_REDIS_URL);

export async function dedupeEvent(eventSid) {
  const key = `twilio:event:${eventSid}`;
  const ok = await redis.set(key, "1", "NX", "EX", 86400);
  return ok === "OK"; // true if first time
}

在 Webhook 处理器中:

  • 如果 dedupeEvent(EventSid) 为 false,则立即返回 200。
场景 4:升级到 Voice 会议,并将录音链接发送回对话

高级步骤:

  1. 通过 TwiML <Dial><Conference record="record-from-start">case-10492</Conference></Dial> 创建 Voice 会议。
  2. 收到 recording.completed Webhook 后,将包含录音 URL 的消息发送到对话中。

发送回对话:

curl -sS -X POST "https://conversations.twilio.com/v1/Conversations/CH.../Messages" \
  -u "$TWILIO_API_KEY_SID:$TWILIO_API_KEY_SECRET" \
  --data-urlencode "Author=system" \
  --data-urlencode "Body=Call recording available: https://api.twilio.com/2010-04-01/Accounts/AC.../Recordings/RE... .mp3" \
  --data-urlencode 'Attributes={"type":"call_recording","recordingSid":"RE0123456789abcdef0123456789abcdef"}'
场景 5:使用模板重新触达 WhatsApp 用户并同步到会话

模式:

  • 如果距离上次收到消息已超过 24 小时,则通过 Messaging API 发送 WhatsApp 模板(不能发送自由格式消息)。
  • 使用属性将模板发送记录同步到会话中。

同步消息:

curl -sS -X POST "https://conversations.twilio.com/v1/Conversations/CH.../Messages" \
  -u "$TWILIO_API_KEY_SID:$TWILIO_API_KEY_SECRET" \
  --data-urlencode "Author=system" \
  --data-urlencode "Body=Sent WhatsApp template: order_update_v2" \
  --data-urlencode 'Attributes={"channel":"whatsapp","template":"order_update_v2","messagingMessageSid":"SM0123456789abcdef0123456789abcdef"}'
场景 6:安全批量关闭不活跃会话

Python 批处理任务:

  • 列出会话,根据最后活动时间进行筛选(从你的数据库获取,或使用可用的 Twilio 字段),然后设置 State=closed。
import os
from twilio.rest import Client

client = Client(os.environ["TWILIO_API_KEY_SID"], os.environ["TWILIO_API_KEY_SECRET"], os.environ["TWILIO_ACCOUNT_SID"])

for conv in client.conversations.v1.conversations.list(limit=200):
    # Prefer your own last_activity tracking; Twilio fields vary by API.
    if conv.state == "active" and conv.friendly_name.startswith("Support Case"):
        client.conversations.v1.conversations(conv.sid).update(state="closed")
        print("closed", conv.sid)

快速参考

任务端点 / 命令关键字段 / 标志
创建会话POST /v1/ConversationsFriendlyName、UniqueName、Attributes、State
列出会话GET /v1/ConversationsPageSize、PageToken
更新会话POST /v1/Conversations/{CH}State、Attributes、FriendlyName
删除会话DELETE /v1/Conversations/{CH}不适用
添加聊天参与者POST /v1/Conversations/{CH}/ParticipantsIdentity、Attributes
添加 SMS 参与者同上MessagingBinding.Address、MessagingBinding.ProxyAddress
添加 WhatsApp 参与者同上MessagingBinding.Address=whatsapp:+E164、ProxyAddress=whatsapp:+E164
列出参与者GET /v1/Conversations/{CH}/ParticipantsPageSize、PageToken
移除参与者DELETE /v1/Conversations/{CH}/Participants/{MB}不适用
发送消息POST /v1/Conversations/{CH}/MessagesAuthor、Body、Attributes
列出消息GET /v1/Conversations/{CH}/MessagesPageSize、Order、PageToken
Webhook 验证X-Twilio-Signature根据确切的 URL 和参数进行验证
重试处理不适用按 EventSid 去重,在遇到 20429 时进行退避

图关系

DEPENDS_ON
  • twilio-core-auth(API 密钥、令牌轮换、请求签名验证模式)
  • webhook-ingestion(幂等性、排队、背压、签名验证)
  • redis(可选;幂等性 / 抑制)
  • postgres(可选;事件存储 / 审计日志)
COMPOSES
  • twilio-programmable-messaging(SMS/MMS/WhatsApp 送达回调、STOP 处理、10DLC/免费电话)
  • twilio-voice(升级处理、录音、转录、IVR 状态机)
  • twilio-verify(添加参与者 / 执行敏感操作前的逐步验证)
  • twilio-studio(基于会话事件的流程触发器)
  • sendgrid(将事务性电子邮件同步到会话线程)
SIMILAR_TO
  • slack-conversations(线程 + 参与者模型、事件驱动更新)
  • zendesk-ticketing(将案例 / 工单生命周期映射到会话状态)
  • intercom-messaging(通过身份 + 联系人绑定实现全渠道消息传递)

评论

支持 Markdown