mcpField
T
twilio-verify

twilio-verify

github.com/alphaonedev安全48 次安装
代码仓库

关于 twilio-verify

twilio-verify 是指导 OpenClaw 集成和运维 Twilio Verify V2 的技能,面向需要在应用中构建身份验证流程的开发者。内容涉及多种验证渠道、欺诈控制、限流和事件监控。

主要功能

  1. 01发送并校验一次性验证码
  2. 02支持短信、语音、邮件及 TOTP
  3. 03配置自定义验证渠道
  4. 04集成 Fraud Guard 风险控制
  5. 05通过 Webhook 记录验证事件
  6. 06组合服务与应用级限流

适用场景

  1. 01为登录流程添加短信验证码
  2. 02以语音或邮件作为验证后备渠道
  3. 03为高风险验证启用额外校验
  4. 04监控验证码投递与验证状态

安装

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

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

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

详细介绍

twilio-verify

用途

让 OpenClaw 能够在生产环境中实施和运营 Twilio Verify (V2):SMS/语音/电子邮件 OTP、TOTP、自定义渠道、电话号码验证、Verify Fraud Guard(风险评分与拦截),以及在可用时使用 Silent Network Authentication (SNA)。此技能侧重于:

  • 构建可靠的验证流程(发送 → 检查 → 执行),并实现速率限制、欺诈控制和可观测性。
  • 将 Verify 与Programmable Messaging/Voice、SendGrid和由 webhook 驱动的状态/遥测集成。
  • 通过确定性的补救措施处理Twilio 的实际故障模式(运营商过滤、无效的 E.164、身份验证错误、速率限制)。
  • 实现大规模运营:成本控制、区域路由、幂等性和滥用防范。

前置条件

账户与 Twilio 资源
  • Twilio 账户,包含:
    • 账户 SID(AC...)
    • Auth Token(或 API Key + Secret)
    • 在 Twilio Console → Verify → Services 中创建的 Verify Service SID(VA...)
  • 如果使用 SMS/语音:
    • 已验证的 Messaging Service(推荐)或电话号码
    • 如果使用 US A2P:已为品牌/营销活动完成 10DLC 注册(或酌情使用免费电话号码/短代码)
  • 如果使用电子邮件渠道:
    • SendGrid 账户 + 已验证的发件人域名,或 Twilio Verify Email 渠道配置(取决于你的设置)
  • 如果使用 SNA:
    • SNA 是否可用取决于区域/运营商以及 Twilio 是否启用;请在 Console 中确认,并咨询 Twilio 支持。
本地工具(确切版本)
  • Node.js 20.11.1(LTS)或 18.19.1(LTS)
  • Python 3.12.2(如果使用 Python 示例)
  • Twilio 辅助库:
    • twilio npm 包 4.22.0
    • twilio Python 包 9.0.5
  • HTTP 工具:
    • curl 8.5.0
    • jq 1.7
  • 可选(推荐):
    • 用于 webhook 测试的 ngrok 3.13.1
    • 用于签名验证工具的 openssl 3.0.13
身份验证设置(推荐做法)

服务器端应用优先使用 API Key 身份验证,而非 Auth Token。

  • 在 Twilio Console → Account → API keys & tokens 中创建 API Key:
    • API Key SID(SK...)
    • API Key Secret(仅保存一次)
  • 将密钥存储在密钥管理器中(AWS Secrets Manager、GCP Secret Manager、Vault)。不要提交到代码库。

示例所需的环境变量:

  • TWILIO_ACCOUNT_SID=ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
  • TWILIO_AUTH_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx(如果使用 Auth Token)
  • TWILIO_API_KEY_SID=SKxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
  • TWILIO_API_KEY_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
  • TWILIO_VERIFY_SERVICE_SID=VAxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

核心概念

Verify Service

Verify Service(VA...)是以下策略的边界:

  • 启用的渠道(sms、call、email、whatsapp、push、custom)
  • 验证码长度、本地化模板、TTL、速率限制
  • Fraud Guard 配置(风险阈值、拦截)
  • Webhook(状态回调、事件)

将 Verify Service 视为按环境划分的资源:

  • 生产环境使用 VA_prod...
  • 预发布环境使用 VA_staging...
  • 只有在策略存在重大差异时,才为不同产品/租户使用单独的服务。
验证与验证检查
  • 验证:通过某个渠道向目标地址(电话/电子邮件)发送验证挑战(OTP)。
  • 验证检查:根据本次验证尝试,验证用户提供的代码(或其他因素)。

你的应用应当:

  1. 创建验证(发送)
  2. 接受用户输入
  3. 创建验证检查(验证)
  4. 执行结果(签发会话令牌、标记电话号码已验证等)
渠道

常见渠道:

  • sms:通过短信发送 OTP
  • call:通过语音呼叫发送 OTP(由 Twilio 的 TwiML 驱动)
  • email:通过电子邮件发送 OTP(Verify 电子邮件渠道或自定义渠道)
  • totp:基于时间的一次性密码(基于应用)
  • whatsapp:WhatsApp OTP(需要启用 WhatsApp)
  • custom:你自己的投递机制(推送、应用内等)

渠道选择应由策略驱动:

  • 默认使用 sms
  • 提供 call 作为投递失败时的备用渠道
  • 在账户恢复或无法使用手机号时提供 email
  • 为高保障账户提供 totp
E.164 规范化

Twilio 要求电话号码采用 E.164 格式:+14155552671。

不要直接接受用户输入的原始内容。应进行规范化和验证:

  • 使用 libphonenumber(Node:google-libphonenumber 或 libphonenumber-js)
  • 在数据库中存储规范的 E.164 格式
  • 尽早拒绝含义不明确的号码
速率限制与滥用控制

Verify 内置了速率限制,但你还应实现:

  • 按 IP 和身份限流(Redis 令牌桶)
  • 设备指纹识别 / 风险评分
  • 失败尝试后的冷却时间
  • 对可疑流量启用 CAPTCHA 验证
Fraud Guard(Verify Fraud Guard)

Fraud Guard 有助于检测:

  • SIM 卡交换风险
  • 高风险目的地
  • 流量异常

将 Fraud Guard 的判定集成到身份验证流程中:

  • 阻止高风险验证
  • 中等风险时升级到更强的因素(TOTP)
  • 记录风险信号,以便进行事件响应
Webhook 与事件

使用 Webhook 处理:

  • 验证状态事件
  • 投递结果(用于消息 / 语音)
  • 审计跟踪和分析

Webhook 的设计应满足:

  • 幂等(按事件 SID 去重)
  • 已认证(验证 Twilio 签名)
  • 可安全重试(Twilio 会在响应非 2xx 时重试)
静默网络身份验证(SNA)

SNA 通过运营商网络信号验证用户的电话号码,无需输入 OTP(在支持的情况下)。应将其视为:

  • 升级验证或无摩擦验证路径
  • 并非普遍可用;应实现 OTP 回退方案
  • 受运营商 / 地区限制和隐私要求约束

安装与设置

官方 Python SDK — Verify

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

from twilio.rest import Client
client = Client()

SERVICE_SID = os.environ["TWILIO_VERIFY_SERVICE_SID"]

# Start verification (SMS / WhatsApp / email / TOTP)
verification = client.verify.v2.services(SERVICE_SID) \
    .verifications.create(to="+15558675309", channel="sms")
print(verification.status)

# Check code
check = client.verify.v2.services(SERVICE_SID) \
    .verification_checks.create(to="+15558675309", code="123456")
print(check.status)  # "approved" | "pending"

来源:twilio/twilio-python — verify

Ubuntu 22.04 LTS(x86_64)
sudo apt-get update
sudo apt-get install -y curl jq ca-certificates gnupg

# Node.js 20.x (NodeSource)
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
sudo apt-get install -y nodejs

node -v   # v20.11.1 (or later 20.x)
npm -v

# Python 3.12 (deadsnakes PPA)
sudo apt-get install -y software-properties-common
sudo add-apt-repository -y ppa:deadsnakes/ppa
sudo apt-get update
sudo apt-get install -y python3.12 python3.12-venv python3.12-dev
python3.12 --version
Fedora 39(x86_64)
sudo dnf install -y curl jq nodejs python3.12 python3.12-devel
node -v
python3.12 --version
macOS 14(Sonoma)— Intel 和 Apple Silicon
brew update
brew install node@20 [email protected] jq curl openssl@3

# Ensure PATH includes brew Node/Python
node -v
python3.12 --version
项目依赖(Node.js)
mkdir -p verify-service && cd verify-service
npm init -y
npm install [email protected] [email protected] [email protected] [email protected]
npm install --save-dev [email protected] [email protected] @types/[email protected]
项目依赖(Python)
mkdir -p verify-service-py && cd verify-service-py
python3.12 -m venv .venv
source .venv/bin/activate
pip install --upgrade pip==24.0
pip install twilio==9.0.5 fastapi==0.109.2 uvicorn==0.27.1 pydantic==2.6.1
环境配置

创建本地环境文件(不要提交):

  • Node:/etc/openclaw/twilio-verify.env(生产环境)或 ./.env(本地)
  • Python:相同

示例(本地):

cat > ./.env <<'EOF'
TWILIO_ACCOUNT_SID=AC2f7c2c6b2d2f4a1b9a0b3c1d2e3f4a5
TWILIO_API_KEY_SID=YOUR_API_KEY_SID
TWILIO_API_KEY_SECRET=9b2c3d4e5f60718293a4b5c6d7e8f9a0
TWILIO_VERIFY_SERVICE_SID=VA0a1b2c3d4e5f60718293a4b5c6d7e8f
TWILIO_AUTH_TOKEN=use_api_key_in_prod_if_possible
APP_ENV=local
EOF

加载:

set -a
source ./.env
set +a

核心功能

通过短信 / 语音 / 电子邮件发送 OTP(Verify V2)

核心操作:创建一项 Verification。

  • 短信:
    • 消费者登录的最佳默认方式
    • 注意运营商过滤和 A2P 合规性
  • 语音:
    • 短信失败时的备用方式
    • 确保用户体验良好:语言 / 语音、重复验证码,以及必要时的 DTMF 处理
  • 电子邮件:
    • 适用于账户恢复或无法使用手机号的情况
    • 如果使用 SendGrid / 自定义电子邮件,确保 SPF/DKIM/DMARC 对齐

Node 示例(发送):

// src/send.ts
import twilio from "twilio";

const accountSid = process.env.TWILIO_ACCOUNT_SID!;
const apiKeySid = process.env.TWILIO_API_KEY_SID!;
const apiKeySecret = process.env.TWILIO_API_KEY_SECRET!;
const verifyServiceSid = process.env.TWILIO_VERIFY_SERVICE_SID!;

const client = twilio(apiKeySid, apiKeySecret, { accountSid });

export async function sendOtp(to: string, channel: "sms" | "call" | "email") {
  const verification = await client.verify.v2
    .services(verifyServiceSid)
    .verifications.create({
      to,
      channel,
      locale: "en",
    });

  return {
    sid: verification.sid,
    status: verification.status, // "pending"
    to: verification.to,
    channel: verification.channel,
  };
}
检查 OTP(Verification Check)

使用用户提供的验证码创建一项 Verification Check。

Node 示例(检查):

// src/check.ts
import twilio from "twilio";

const accountSid = process.env.TWILIO_ACCOUNT_SID!;
const apiKeySid = process.env.TWILIO_API_KEY_SID!;
const apiKeySecret = process.env.TWILIO_API_KEY_SECRET!;
const verifyServiceSid = process.env.TWILIO_VERIFY_SERVICE_SID!;

const client = twilio(apiKeySid, apiKeySecret, { accountSid });

export async function checkOtp(to: string, code: string) {
  const check = await client.verify.v2
    .services(verifyServiceSid)
    .verificationChecks.create({ to, code });

  return {
    sid: check.sid,
    status: check.status, // "approved" or "pending"/"canceled"
    valid: check.valid,
  };
}

执行规则(通常):

  • 仅当 status === "approved" 且 valid === true 时接受
  • 失败时,递增本地计数器并应用冷却时间
TOTP 注册与验证

使用 Verify TOTP 处理基于应用的验证码。典型流程:

  1. 创建 TOTP 因子(注册)
  2. 向用户显示二维码 / 密钥
  3. 验证初始验证码
  4. 存储因子 SID 并将其绑定到用户

注意:Twilio Verify TOTP API 属于 Verify V2 的“Entities/Factors”模型。请确保你的账户有权使用这些 API,并且使用了正确的端点。

操作指南:

  • 将因子 SID 视为机密信息(虽然它们是标识符,但仍然敏感)
  • 允许每位用户拥有多个因子(便于迁移设备)
  • 在 Twilio 之外提供恢复码(由你的系统负责)
自定义渠道(电子邮件 / 推送 / 应用内)

当你自行发送验证码,但希望由 Twilio 管理以下内容时,请使用 Verify“custom”渠道:

  • 验证码生成
  • TTL
  • 尝试次数限制
  • 验证检查

模式:

  • 使用 channel=custom 请求验证
  • Twilio 会返回验证码(或根据配置通过 API 获取)
  • 通过你的渠道发送(推送、应用内)
  • 通过 Verification Check 验证

适用于以下情况:

  • 你已有推送基础设施
  • 你希望在各渠道间统一执行策略
Fraud Guard 集成

使用 Fraud Guard 信号来:

  • 阻止向高风险目标发送验证请求
  • 对中等风险请求要求进行阶梯式验证(TOTP)
  • 监控每个 ASN / 国家或地区的请求激增情况

实现模式:

  • 收到“发送验证”请求时:
    • 评估风险(Twilio + 你自己的评估)
    • 如果被阻止:返回通用消息和 403
    • 否则:继续处理
速率限制与节流

结合使用:

  • Twilio Verify 服务的速率限制
  • 应用级节流

建议的最低限制:

  • 每个 IP:每 10 分钟发送 5 次
  • 每个目标:每 10 分钟发送 3 次
  • 每个身份(用户 ID):每 10 分钟检查 5 次
  • Twilio 5xx 错误激增时启用全局断路器
Webhook 驱动的可观测性

使用:

  • Verify 事件 Webhook(如已配置)
  • 用于 SMS 送达结果的 Messaging 状态回调(如使用 Messaging)
  • 用于通话结果的 Voice 状态回调

存储:

  • 验证 SID
  • 目标哈希(HMAC)
  • 渠道
  • 状态变更
  • 错误代码

命令参考

本节假定直接通过 curl 调用 REST,以及使用辅助库。Twilio 未提供功能完全对等的官方“twilio verify”CLI;请使用 REST 调用或辅助 SDK。

REST:创建验证(发送 OTP)

端点:

  • POST https://verify.twilio.com/v2/Services/{ServiceSid}/Verifications

身份验证:

  • 使用 API Key SID / Secret 进行 Basic 身份验证(推荐),或使用 Account SID / Auth Token

重要标志 / 字段:

  • To(字符串):目标(+14155552671 或电子邮件)
  • Channel(字符串):sms|call|email|whatsapp|custom
  • Locale(字符串):例如 en、es、fr
  • ChannelConfiguration.*(对象):渠道专属配置(各渠道有所不同)
  • CustomFriendlyName(字符串):用于日志 / 用户体验的标签
  • RateLimits.*(对象):服务级别覆盖项(如已启用)
  • RiskCheck / Fraud Guard 字段(如已启用;取决于账户)

示例(SMS):

curl -sS -X POST "https://verify.twilio.com/v2/Services/${TWILIO_VERIFY_SERVICE_SID}/Verifications" \
  -u "${TWILIO_API_KEY_SID}:${TWILIO_API_KEY_SECRET}" \
  --data-urlencode "To=+14155552671" \
  --data-urlencode "Channel=sms" \
  --data-urlencode "Locale=en"

示例(语音通话):

curl -sS -X POST "https://verify.twilio.com/v2/Services/${TWILIO_VERIFY_SERVICE_SID}/Verifications" \
  -u "${TWILIO_API_KEY_SID}:${TWILIO_API_KEY_SECRET}" \
  --data-urlencode "To=+14155552671" \
  --data-urlencode "Channel=call" \
  --data-urlencode "Locale=en"

示例(电子邮件):

curl -sS -X POST "https://verify.twilio.com/v2/Services/${TWILIO_VERIFY_SERVICE_SID}/Verifications" \
  -u "${TWILIO_API_KEY_SID}:${TWILIO_API_KEY_SECRET}" \
  --data-urlencode "[email protected]" \
  --data-urlencode "Channel=email" \
  --data-urlencode "Locale=en"
REST:创建验证检查(验证 OTP)

端点:

  • POST https://verify.twilio.com/v2/Services/{ServiceSid}/VerificationCheck

字段:

  • To(字符串):与发送时相同的目标
  • Code(字符串):用户提供的 OTP
  • VerificationSid(字符串):某些流程中可选;除非你存储了 SID,否则建议使用 To+Code

示例:

curl -sS -X POST "https://verify.twilio.com/v2/Services/${TWILIO_VERIFY_SERVICE_SID}/VerificationCheck" \
  -u "${TWILIO_API_KEY_SID}:${TWILIO_API_KEY_SECRET}" \
  --data-urlencode "To=+14155552671" \
  --data-urlencode "Code=123456"
REST:列出验证记录(审计 / 调试)

端点:

  • GET https://verify.twilio.com/v2/Services/{ServiceSid}/Verifications

查询参数(常用):

  • To(字符串)
  • Status(字符串):pending|approved|canceled
  • Channel(字符串)
  • DateCreated(日期筛选;采用 Twilio 格式)
  • PageSize(整数):根据端点不同,最多为 1000

示例:

curl -sS "https://verify.twilio.com/v2/Services/${TWILIO_VERIFY_SERVICE_SID}/Verifications?To=%2B14155552671&PageSize=50" \
  -u "${TWILIO_API_KEY_SID}:${TWILIO_API_KEY_SECRET}" | jq .
REST:根据 SID 获取验证记录

端点:

  • GET https://verify.twilio.com/v2/Services/{ServiceSid}/Verifications/{VerificationSid}
curl -sS "https://verify.twilio.com/v2/Services/${TWILIO_VERIFY_SERVICE_SID}/Verifications/VExxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -u "${TWILIO_API_KEY_SID}:${TWILIO_API_KEY_SECRET}" | jq .
REST:取消验证(使其失效)

端点:

  • POST https://verify.twilio.com/v2/Services/{ServiceSid}/Verifications/{VerificationSid},并设置 Status=canceled
curl -sS -X POST "https://verify.twilio.com/v2/Services/${TWILIO_VERIFY_SERVICE_SID}/Verifications/VExxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \
  -u "${TWILIO_API_KEY_SID}:${TWILIO_API_KEY_SECRET}" \
  --data-urlencode "Status=canceled"
REST:读取 Verify 服务配置

端点:

  • GET https://verify.twilio.com/v2/Services/{ServiceSid}
curl -sS "https://verify.twilio.com/v2/Services/${TWILIO_VERIFY_SERVICE_SID}" \
  -u "${TWILIO_API_KEY_SID}:${TWILIO_API_KEY_SECRET}" | jq .
Twilio Node 辅助库:客户端初始化选项

twilio(apiKeySid, apiKeySecret, { accountSid, region, edge, logLevel })

重要选项:

  • accountSid:使用 API Key 身份验证时必需
  • region:例如 us1、ie1、au1(数据驻留/延迟)
  • edge:例如 ashburn、dublin(延迟优化)
  • logLevel:debug|info|warn|error(避免在生产环境使用 debug)

示例:

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!,
  region: "us1",
  edge: "ashburn",
});

配置参考

OpenClaw 技能配置(示例)

路径:

  • /etc/openclaw/skills/twilio/twilio-verify.toml
# /etc/openclaw/skills/twilio/twilio-verify.toml
[twilio]
account_sid = "AC2f7c2c6b2d2f4a1b9a0b3c1d2e3f4a5"
auth_mode = "api_key" # "api_key" | "auth_token"
api_key_sid_env = "TWILIO_API_KEY_SID"
api_key_secret_env = "TWILIO_API_KEY_SECRET"
auth_token_env = "TWILIO_AUTH_TOKEN"
region = "us1"
edge = "ashburn"

[verify]
service_sid = "VA0a1b2c3d4e5f60718293a4b5c6d7e8f"
default_channel = "sms"
fallback_channels = ["call", "email"]
default_locale = "en"
code_ttl_seconds = 600

[rate_limits]
# App-level throttles (in addition to Twilio)
send_per_ip_per_10m = 5
send_per_to_per_10m = 3
check_per_identity_per_10m = 5
cooldown_seconds_after_failed_check = 60

[fraud_guard]
enabled = true
block_on_high_risk = true
step_up_on_medium_risk = true
step_up_channel = "totp"

[logging]
redact_fields = ["to", "email", "code", "auth_token", "api_key_secret"]
log_level = "info"
Node 服务配置(示例)

路径:

  • ./config/verify.config.json
{
  "twilio": {
    "region": "us1",
    "edge": "ashburn"
  },
  "verify": {
    "serviceSid": "VA0a1b2c3d4e5f60718293a4b5c6d7e8f",
    "defaultLocale": "en",
    "channels": ["sms", "call", "email"]
  },
  "security": {
    "hmacKeyEnv": "VERIFY_DESTINATION_HMAC_KEY",
    "webhookAuthTokenEnv": "TWILIO_AUTH_TOKEN"
  }
}
systemd 单元(生产环境)

路径:

  • /etc/systemd/system/openclaw-verify.service
[Unit]
Description=OpenClaw Verify Gateway
After=network-online.target
Wants=network-online.target

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

[Install]
WantedBy=multi-user.target

集成模式

与 Programmable Messaging 状态回调组合使用

即使使用 Verify,你也可能需要投递遥测数据。模式:

  • 使用 Verify 生成并检查 OTP
  • 使用 Messaging 状态回调获取投递结果(如果你的设置通过 Messaging Service 路由)
  • 通过 to 哈希值 + 时间戳窗口 + verification SID 进行关联

流水线:

  1. POST /verify/send → Twilio Verify 创建验证
  2. Twilio 发送 SMS
  3. Messaging 状态回调触发 /webhooks/sms-status
  4. 存储 MessageSid、MessageStatus、ErrorCode(例如 30003)
  5. 如果反复失败,自动切换到语音/电子邮件
与语音 IVR 后备方案组合使用

如果 SMS 失败:

  • 提供语音呼叫 OTP 选项
  • 如果语音也失败,将请求路由给 Agent,或要求使用 TOTP

如果你已有 IVR 状态机:

  • 将 Verify 呼叫 OTP 与 IVR 呼叫分开
  • IVR 仅用于支持流程;Verify 呼叫针对 OTP 进行了优化
与 SendGrid 组合使用以提供自定义电子邮件渠道

如果你需要使用 Verify 模板以外的品牌电子邮件:

  • 使用 Verify custom 渠道生成验证码
  • 通过 SendGrid 动态模板发送电子邮件
  • 通过 Verification Check 进行验证

SendGrid 动态模板示例(Handlebars):

{
  "personalizations": [
    {
      "to": [{ "email": "[email protected]" }],
      "dynamic_template_data": {
        "code": "123456",
        "ttl_minutes": 10
      }
    }
  ],
  "from": { "email": "[email protected]", "name": "Example Security" },
  "template_id": "d-13b8f94f2b2a4c4f9a8d0a1b2c3d4e5f"
}
CI/CD:在 staging 环境中对 Verify 进行冒烟测试

在流水线中:

  • 部署 staging
  • 运行冒烟测试,以便:
    • 向测试电话号码发送 OTP(或使用测试凭据)
    • 使用受控渠道检查 OTP(Twilio 测试凭据不会发送真实 SMS)
  • 只有满足以下条件才允许部署到生产环境:
    • Twilio API 身份验证成功
    • Verify 服务可访问
    • Webhook 端点签名验证通过
数据模型集成

存储:

  • user_id
  • destination_e164(静态加密)
  • destination_hash(用于日志的 HMAC)
  • verify_service_sid
  • last_verification_sid
  • verified_at
  • failed_attempts
  • cooldown_until

不要存储 OTP 验证码。


错误处理与故障排查

按代码处理 Twilio 错误,而不要通过字符串匹配;但应包含确切消息,以便操作人员识别。

1)20003 — 身份验证错误

消息: Twilio could not authenticate the request. Please check your credentials.

根本原因:

  • API Key Secret/Auth Token 错误
  • 在 SDK 初始化时使用 API Key,却未提供 accountSid
  • 时钟偏差很少会影响身份验证,但可能影响签名验证

修复方法:

  • 检查环境变量和密钥管理器中的值
  • 使用 Node SDK 和 API Key 时:传入 { accountSid }
  • 如果 API Key 泄露,请轮换密钥
2)20429 — 请求过多

消息: Rate limit exceeded

根本原因:

  • 达到 Verify 服务速率限制
  • 突发流量(机器人攻击)
  • 客户端反复循环重新发送

修复方法:

  • 实施应用级限流和指数退避
  • 在界面中加入重新发送冷却时间(例如 30–60 秒)
  • 仅当策略不同才考虑为不同流量类别使用独立的 Verify 服务
3) 21211 — 无效的“To”电话号码

消息: The 'To' number +1415555 is not a valid phone number.

根本原因:

  • 不是 E.164 格式
  • 用户输入了不带国家/地区代码的本地格式
  • 解析或规范化错误

修复方法:

  • 使用 libphonenumber 进行规范化
  • 要求用户选择国家/地区,或根据用户资料推断
  • 尽早拒绝,并在用户体验中提供明确的解决指引
4) 30003 — 目标手机无法接通/运营商过滤

消息(Messaging): Unreachable destination handset

根本原因:

  • 运营商过滤(A2P 问题、内容过滤)
  • 号码未启用或不在服务范围内
  • 目标号码类型错误(将固定电话号码用于 SMS)

修复方法:

  • 提供语音备用方案
  • 确保符合 10DLC 要求,并使用正确的发送方类型(免费电话/短代码)
  • 使用带有地理区域匹配和适当发送方池的 Messaging Service
5) 60200 — 参数无效(Verify)

消息: Invalid parameter: Channel

根本原因:

  • 不支持的渠道字符串
  • Verify Service 未启用该渠道

修复方法:

  • 在 API 层验证渠道枚举值
  • 确保服务配置包含该渠道
  • 如果策略不同,请使用单独的服务
6) 60203 — 已达到最大验证尝试次数/验证已被阻止

消息: Max check attempts reached

根本原因:

  • 用户反复输入错误代码
  • 针对某个目标的攻击

修复方法:

  • 强制执行冷却时间,并在锁定后要求重新发送
  • 添加机器人防护措施
  • 针对每个目标哈希值的峰值发出警报
7) 60202 — 验证已过期

消息: Verification expired

根本原因:

  • 用户等待时间超过 TTL
  • 送达延迟(运营商)
  • 客户端时钟问题(用户体验)

修复方法:

  • 在理由充分时延长 TTL(权衡:安全性)
  • 改进用户体验:显示倒计时和重新发送选项
  • SMS 延迟时优先使用语音备用方案
8) Webhook 签名验证失败

典型日志: Error: Twilio Request Validation Failed.

根本原因:

  • 使用了错误的 Auth Token 进行验证
  • URL 不匹配(ngrok URL 已更改,缺少查询字符串)
  • 反向代理重写了主机名/路径

修复方法:

  • 使用 Twilio 调用的确切公网 URL 进行验证
  • 在代理中保留原始 URL(X-Forwarded-Host、X-Forwarded-Proto)
  • 确保 Auth Token 一致;谨慎轮换
9) 11200 — HTTP 检索失败(Voice/TwiML)

消息(Voice 调试器): HTTP retrieval failure

根本原因:

  • Twilio 无法访问你的 webhook(防火墙、DNS)
  • TLS 配置错误
  • 响应缓慢,超过超时时间

修复方法:

  • 确保端点使用公网 HTTPS
  • 降低延迟,在几秒内响应
  • 添加运行状况检查和多区域入口
10) 21610 — STOP/选择退出(Messaging)

消息: Attempt to send to unsubscribed recipient

根本原因:

  • 用户向你的发送方回复了 STOP
  • 你重复使用发送方池,却未考虑选择退出状态

修复方法:

  • 尊重选择退出;不要再尝试发送 SMS
  • 提供语音/电子邮件/TOTP 替代方案
  • 维护以目标号码为键的屏蔽列表

安全加固

密钥管理
  • 将 Twilio API Key Secret/Auth Token 存储在密钥管理器中。
  • 每季度轮换 API 密钥,或在发生安全事件后轮换。
  • 遵循最小权限原则:为每个环境使用单独的 API 密钥。
Webhook 验证(必需)

验证所有入站 webhook 的 Twilio 签名。

Node 示例:

import twilio from "twilio";
import type { Request, Response } from "express";

export function validateTwilioWebhook(req: Request, res: Response, next: Function) {
  const authToken = process.env.TWILIO_AUTH_TOKEN!;
  const signature = req.header("X-Twilio-Signature") || "";
  const url = `${req.protocol}://${req.get("host")}${req.originalUrl}`;

  const isValid = twilio.validateRequest(authToken, signature, url, req.body);
  if (!isValid) return res.status(403).send("Forbidden");
  next();
}

操作说明:

  • 如果位于代理之后,请设置 app.set('trust proxy', true),并使用转发标头重建 URL。
  • 确保正文解析能按需保留原始正文;某些框架配置需要原始正文才能进行验证。
PII 处理
  • 将电话号码和电子邮件视为 PII。
  • 仅记录:
    • 使用支持轮换的密钥生成的 HMAC(destination)
    • 用于调试的末尾 2 位数字(可选)
  • 使用 KMS 信封加密对静态存储的目标号码进行加密。
符合 CIS 标准的主机加固(高层建议)
  • CIS Ubuntu Linux 22.04 LTS 基准:
    • 禁用 SSH 密码身份验证,强制使用密钥
    • 启用自动安全更新
    • 在可行的情况下,将应用主机的出站流量限制为仅访问 Twilio 端点
  • systemd 沙箱(参见上方的单元文件)
  • 尽可能以非 root 用户运行,并使用只读文件系统
滥用防范
  • 对可疑的发送尝试要求提供工作量证明 / CAPTCHA。
  • 对电子邮件渠道屏蔽一次性电子邮件域名(视策略而定)。
  • 添加 ASN / 国家异常检测。

性能调优

使用区域 / 边缘节点降低 Twilio API 延迟

在 SDK 初始化时设置 region 和 edge。

预期影响:

  • 典型 p50 改善:视距离而定,为 30–80ms
  • 跨区域部署时,p95 改善:50–150ms

测量方法:

  • 为 sendOtp 和 checkOtp 的耗时添加监测
  • 使用相同流量比较调整前后的结果
连接复用和超时
  • 使用 keep-alive HTTP 代理(Node)以减少 TLS 握手开销。
  • 设置合理的超时时间:
    • 连接超时:2s
    • 请求超时:5s(发送)、5s(检查)
  • 仅针对安全的失败情况(网络错误、5xx)实施重试。不要针对 4xx 重试。
缓存规范化结果

大规模场景下,电话号码解析可能开销较大。

  • 按原始输入缓存 E.164 规范化结果,设置较短的 TTL(例如 10 分钟),并以 (raw, defaultCountry) 为键。
避免重复发送循环

客户端体验:

  • 禁用重新发送按钮 30 秒
  • 显示倒计时
  • 多次失败时采用退避策略

这样可以减少:

  • Twilio 成本
  • 20429 速率限制
  • 运营商过滤风险

高级主题

幂等策略

Twilio Verify 的“发送”操作在重复调用时并非天然具备幂等性。请在应用层实现幂等性:

  • 计算键:sha256(user_id + destination + channel + floor(now/30s))
  • 在 Redis 中存储该键,TTL 为 60s
  • 如果键已存在,则返回现有的验证 SID / 状态

这样可以防止以下情况导致意外重复发送:

  • 移动端重试
  • 双击
  • 网络超时
多渠道回退策略

实施确定性的回退策略:

  1. SMS
  2. 如果 SMS 发送失败并返回 30003,或 20s 内未送达 → 语音
  3. 如果语音失败 → 电子邮件或 TOTP 注册提示

在某些司法管辖区,未经用户同意不得自动回退;请确保合规。

处理固定电话和 VoIP
  • 某些号码无法接收 SMS。
  • 使用运营商查询(Twilio Lookup API)检测线路类型:
    • 如果是固定电话:跳过 SMS,提供语音 / 电子邮件选项
    • 如果是 VoIP:考虑更高的欺诈风险,增加验证因素
国际化
  • 根据用户偏好设置 Locale。
  • 确保目标区域设置有对应的模板。
  • 对于语音,确保语言 / 语音选择正确(如果使用自定义语音流程)。
Verify + 账户关联

关联账户时验证电话号码:

  • 在允许验证电话号码变更前,要求用户已通过身份验证
  • 对敏感变更强制重新进行身份验证
  • 要求确认现有验证因素,以防止“电话号码劫持”
SNA 回退设计

如果启用了 SNA:

  • 优先对符合条件的设备 / 网络尝试 SNA
  • 如果不可用或失败:
    • 回退到 SMS / 语音
  • 记录 SNA 资格及失败原因,以便调优

使用示例

场景 1:通过 SMS 发送登录 OTP,并设置重新发送冷却时间(Node + Express)
// src/server.ts
import express from "express";
import twilio from "twilio";
import { z } from "zod";
import pino from "pino";

const log = pino({ level: process.env.LOG_LEVEL || "info" });
const app = express();
app.use(express.json());

const client = twilio(process.env.TWILIO_API_KEY_SID!, process.env.TWILIO_API_KEY_SECRET!, {
  accountSid: process.env.TWILIO_ACCOUNT_SID!,
  region: "us1",
  edge: "ashburn",
});

const serviceSid = process.env.TWILIO_VERIFY_SERVICE_SID!;

const SendSchema = z.object({
  to: z.string().min(5),
  channel: z.enum(["sms", "call", "email"]).default("sms"),
});

const CheckSchema = z.object({
  to: z.string().min(5),
  code: z.string().min(4).max(10),
});

app.post("/verify/send", async (req, res) => {
  const { to, channel } = SendSchema.parse(req.body);

  // TODO: enforce app-level rate limits here (Redis token bucket)
  const v = await client.verify.v2.services(serviceSid).verifications.create({
    to,
    channel,
    locale: "en",
  });

  log.info({ verificationSid: v.sid, channel: v.channel }, "verify_send");
  res.json({ sid: v.sid, status: v.status });
});

app.post("/verify/check", async (req, res) => {
  const { to, code } = CheckSchema.parse(req.body);

  const c = await client.verify.v2.services(serviceSid).verificationChecks.create({ to, code });

  log.info({ checkSid: c.sid, status: c.status, valid: c.valid }, "verify_check");

  if (c.status === "approved" && c.valid) {
    // Issue session token, mark verified, etc.
    return res.json({ ok: true });
  }
  return res.status(401).json({ ok: false });
});

app.listen(3000, () => log.info("listening on :3000"));

运行:

npx tsx src/server.ts
curl -sS -X POST http://localhost:3000/verify/send -H 'content-type: application/json' \
  -d '{"to":"+14155552671","channel":"sms"}' | jq .
场景 2:SMS 失败后回退到语音(由策略驱动)

伪逻辑:

type DeliverySignal = { smsFailed: boolean; smsTimedOut: boolean };

function chooseChannel(signal: DeliverySignal) {
  if (signal.smsFailed || signal.smsTimedOut) return "call";
  return "sms";
}

实际操作:

  • 使用 Messaging 状态回调检测返回 30003 的 failed 状态
  • 或在 20 秒内未收到 delivered 状态时超时(SMS 并非总能提供此状态)
  • 向用户提供“改为给我打电话”选项
场景 3:使用自定义渠道 + SendGrid 模板发送电子邮件 OTP
  1. 请求自定义验证:
curl -sS -X POST "https://verify.twilio.com/v2/Services/${TWILIO_VERIFY_SERVICE_SID}/Verifications" \
  -u "${TWILIO_API_KEY_SID}:${TWILIO_API_KEY_SECRET}" \
  --data-urlencode "[email protected]" \
  --data-urlencode "Channel=custom" | jq .
  1. 通过 SendGrid 发送验证码(由你的应用发送电子邮件)。
  2. 通过 Verify VerificationCheck 检查验证码。
场景 4:高风险账户的 TOTP 注册

流程:

  • 用户使用密码登录
  • 风险引擎将风险标记为中高
  • 要求注册 TOTP:
    • 创建因子
    • 验证初始代码
    • 存储因子 SID
  • 后续登录时,要求进行 TOTP 检查

生产环境中的关键细节:

  • 提供恢复途径(支持 + 身份验证)
  • 允许使用多个设备
场景 5:更改个人资料时进行手机验证(提升验证级别)

用户更改手机号码时:

  • 要求已有会话 + 重新验证身份
  • 向新号码发送 OTP
  • 仅在批准后:
    • 在 DB 中更新手机号码
    • 设置 verified_at
  • 除非策略允许,否则禁止换用已由其他账户验证的号码
场景 6:带签名验证和幂等性的 Webhook 端点
import express from "express";
import twilio from "twilio";
import crypto from "crypto";

const app = express();

// For some frameworks you may need raw body; adjust accordingly.
app.use(express.urlencoded({ extended: false }));

const seen = new Set<string>(); // replace with Redis in prod

app.post("/webhooks/verify-events", (req, res) => {
  const authToken = process.env.TWILIO_AUTH_TOKEN!;
  const signature = req.header("X-Twilio-Signature") || "";
  const url = `${req.protocol}://${req.get("host")}${req.originalUrl}`;

  const ok = twilio.validateRequest(authToken, signature, url, req.body);
  if (!ok) return res.status(403).send("Forbidden");

  const eventSid = req.body.Sid || req.body.EventSid || "";
  const dedupeKey = crypto.createHash("sha256").update(eventSid).digest("hex");
  if (seen.has(dedupeKey)) return res.status(200).send("ok");
  seen.add(dedupeKey);

  // Persist event, update metrics, etc.
  return res.status(200).send("ok");
});

快速参考

任务命令 / API关键标志/字段
发送 OTPPOST /v2/Services/{VA}/VerificationsTo、Channel、Locale
检查 OTPPOST /v2/Services/{VA}/VerificationCheckTo、Code
列出验证记录GET /v2/Services/{VA}/VerificationsTo、Status、Channel、PageSize
获取验证记录GET /v2/Services/{VA}/Verifications/{VE}不适用
取消验证POST /v2/Services/{VA}/Verifications/{VE}Status=canceled
身份验证(首选)API KeySK... + 密钥 + accountSid
常见错误Twilio 代码20003、20429、21211、30003、60202、60203
Webhook 安全性签名验证X-Twilio-Signature、精确 URL

图关系

DEPENDS_ON
  • twilio-core-auth(Account SID + API Key/Auth Token 处理)
  • twilio-webhooks(签名验证、重试/幂等模式)
  • pii-handling(脱敏、静态加密)
  • rate-limiting(Redis 令牌桶 / 漏桶)
COMPOSES
  • twilio-messaging(传递遥测、STOP 处理、10DLC 注意事项)
  • twilio-voice(语音回退、呼叫状态回调)
  • sendgrid-transactional(自定义邮件渠道传递、退信处理)
  • studio-flows(可选,用于编排复杂的验证流程)
SIMILAR_TO
  • auth-otp-generic(不使用 Twilio 管理策略的 OTP 流程)
  • firebase-phone-auth(由其他提供商管理的手机验证)
  • okta-verify(基于因子的企业 IAM 验证)

评论

支持 Markdown