twilio-verify について
twilio-verifyは、OpenClawでTwilio Verify V2を統合・運用するためのスキルです。アプリの認証フローを構築する開発者向けに、複数のチャネル、不正対策、レート制限、イベント監視を扱います。
主な機能
- 01ワンタイムコードの送信と照合
- 02SMS、音声、メール、TOTPへの対応
- 03カスタム認証チャネルの設定
- 04Fraud Guardによるリスク制御
- 05Webhookによる認証イベントの記録
- 06サービスとアプリ双方のレート制限
活用シーン
- 01サインインフローへのSMS認証追加
- 02音声やメールを代替チャネルとして提供
- 03高リスク試行に追加認証を要求
- 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 アカウント:
- Account SID(
AC...) - Auth Token(または API Key + Secret)
- Twilio Console → Verify → Services で作成した Verify Service SID(
VA...)
- Account SID(
- SMS/Voice を使用する場合:
- 検証済みの Messaging Service(推奨)または電話番号
- 米国の A2P を使用する場合:ブランド/キャンペーンの 10DLC 登録を完了してください(または用途に応じてトールフリー番号/ショートコードを使用してください)
- メールチャネルを使用する場合:
- SendGrid アカウントと検証済みの送信者ドメイン、または Twilio Verify のメールチャネル設定(セットアップに応じて異なります)
- SNA を使用する場合:
- SNA の利用可否はリージョン/キャリアと Twilio の有効化状況によって異なります。Console と Twilio サポートに確認してください。
ローカルツール(正確なバージョン)
- Node.js 20.11.1(LTS)または 18.19.1(LTS)
- Python 3.12.2(Python の例を使用する場合)
- Twilio ヘルパーライブラリ:
twilionpm パッケージ 4.22.0twilioPython パッケージ 9.0.5
- HTTP ツール:
curl8.5.0jq1.7
- 任意(推奨):
- Webhook のテスト用
ngrok3.13.1 - 署名検証ユーティリティ用
openssl3.0.13
- Webhook のテスト用
認証の設定(推奨パターン)
サーバーサイドアプリでは、Auth Token より API Key 認証を優先してください。
- Twilio Console → Account → API keys & tokens で API Key を作成します。
- API Key SID(
SK...) - API Key Secret(作成時に一度だけ保存)
- API Key SID(
- シークレットはシークレットマネージャー(AWS Secrets Manager、GCP Secret Manager、Vault)に保存してください。リポジトリにコミットしないでください。
例で使用する環境変数:
TWILIO_ACCOUNT_SID=ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxTWILIO_AUTH_TOKEN=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx(Auth Token を使用する場合)TWILIO_API_KEY_SID=SKxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxTWILIO_API_KEY_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxTWILIO_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... - ポリシーが実質的に異なる場合に限り、製品/テナントごとに別のサービスを使用します。
Verification と Verification Check
- Verification:チャネルを通じて、宛先(電話番号/メールアドレス)にチャレンジ(OTP)を送信します。
- Verification Check:ユーザーが入力したコード(または別の要素)を、検証試行と照合します。
アプリケーションでは、次の手順を実行してください。
- 検証を作成する(送信)
- ユーザーの入力を受け付ける
- Verification Check を作成する(検証)
- 結果に応じた処理を行う(セッショントークンの発行、電話番号の検証済みマークなど)
チャネル
一般的なチャネル:
sms:SMS による OTPcall:音声通話による 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 形式の電話番号を DB に保存します
- 曖昧な番号は早い段階で拒否します
レート制限と不正利用対策
Verify にはレート制限が組み込まれていますが、以下も実装してください。
- IP ごと、ID ごとのスロットリング(Redis トークンバケット)
- デバイスフィンガープリンティング/リスクスコアリング
- 失敗後のクールダウン
- 不審なトラフィックに対する CAPTCHA によるゲート
Fraud Guard(Verify Fraud Guard)
Fraud Guard は、以下の検出に役立ちます。
- SIM スワップのリスク
- 高リスクの宛先
- トラフィックの異常
Fraud Guard の判定を認証フローに組み込んでください。
- 高リスクの認証をブロックします
- リスクが中程度の場合は、より強力な要素(TOTP)による追加認証に切り替えます
- インシデント対応に備えてリスクシグナルを記録します
Webhook とイベント処理
以下には Webhook を使用します。
- 認証ステータスのイベント
- 配信結果(メッセージ/音声の場合)
- 監査証跡と分析
Webhook は次のように設計します。
- 冪等性を確保します(イベント SID で重複排除)
- 認証を行います(Twilio の署名検証)
- 再試行に対応します(Twilio は 2xx 以外の場合に再試行します)
Silent Network Authentication(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
主な機能
SMS/音声/メールで OTP を送信(Verify V2)
基本操作:認証を作成します。
- SMS:
- コンシューマー向けサインインのデフォルトとして最適です
- キャリアによるフィルタリングと A2P コンプライアンスに注意してください
- 音声:
- SMS が失敗した場合のフォールバックとして利用します
- ユーザー体験に配慮してください。言語/音声、コードの再読み上げ、必要に応じた 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 を使用します。一般的なフローは次のとおりです。
- TOTP ファクターを作成します(登録)
- QR コードまたはシークレットをユーザーに表示します
- 初回のコードを検証します
- ファクター SID を保存し、ユーザーに紐付けます
注:Twilio Verify TOTP API は、Verify V2 の「Entities/Factors」モデルの一部です。アカウントが利用可能であり、正しいエンドポイントを使用していることを確認してください。
運用上のガイダンス:
- ファクター SID はシークレットとして扱います(識別子ですが、機密情報でもあります)
- ユーザーごとに複数のファクターを許可します(デバイス移行に対応)
- リカバリーコードは Twilio の外部で提供します(自社システムで管理)
カスタムチャネル(メール/プッシュ/アプリ内)
コードを自分で配信しながら、以下の管理を Twilio に任せる場合は、Verify の「custom」チャネルを使用します。
- コードの生成
- 有効期間
- 試行回数の上限
- 検証チェック
パターン:
channel=customで検証をリクエストします- Twilio がコードを返します(または、構成に応じて API 経由で取得します)
- 自社のチャネル(プッシュ、アプリ内)で配信します
- Verification Check で検証します
次の場合に便利です。
- 既存のプッシュ通知基盤がある
- チャネル間で一貫したポリシーを適用したい
Fraud Guard の統合
Fraud Guard のシグナルを使用して、次の処理を行います。
- リスクの高い宛先への検証試行をブロックする
- 中程度のリスクにはステップアップ(TOTP)を要求する
- ASN/国ごとの急増を通知する
実装パターン:
- 「send verification」リクエスト時:
- リスクを評価します(Twilio と自社の評価)
- ブロックする場合:汎用メッセージとともに 403 を返します
- それ以外の場合:処理を続行します
レート制限とスロットリング
次を組み合わせます。
- Twilio Verify サービスのレート制限
- アプリケーションレベルのスロットリング
推奨される最小値:
- IP ごと:10 分あたり 5 回の送信
- 宛先ごと:10 分あたり 3 回の送信
- ID ごと(ユーザー 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(推奨)または Account SID/Auth Token による Basic 認証
重要なフラグ/フィールド:
To(string):宛先(+14155552671またはメールアドレス)Channel(string):sms|call|email|whatsapp|customLocale(string):例:en、es、frChannelConfiguration.*(object):チャネル固有の構成(内容は異なります)CustomFriendlyName(string):ログ/UX 用のラベルRateLimits.*(object):サービスレベルの上書き設定(有効な場合)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(string):送信時と同じ宛先Code(string):ユーザーが入力した OTPVerificationSid(string):フローによっては省略可能です。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(string)Status(string):pending|approved|canceledChannel(string)DateCreated(日付フィルター。Twilio 形式)PageSize(int):エンドポイントに応じて最大 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 Service の設定を確認する(読み取り)
エンドポイント:
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 を使用している場合でも、配信状況のテレメトリーが必要になることがあります。パターン:
- OTP の生成と確認には Verify を使用します
- 配信結果には Messaging のステータスコールバックを使用します(構成上、Messaging Service 経由でルーティングする場合)
toのハッシュ+タイムスタンプの時間範囲+Verification SID で関連付けます
パイプライン:
POST /verify/send→ Twilio Verify が検証を作成します- Twilio が SMS を送信します
- Messaging のステータスコールバックが
/webhooks/sms-statusに到達します MessageSid、MessageStatus、ErrorCode(例:30003)を保存します- 失敗が繰り返される場合は、自動的に音声/メールに切り替えます
Voice IVR のフォールバックとの組み合わせ
SMS が失敗した場合:
- 音声通話による OTP を提示します
- 音声通話も失敗した場合は、エージェントに転送するか、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:ステージングで Verify のスモークテストを実施する
パイプラインで:
- ステージング環境をデプロイします
- 次の内容を含むスモークテストを実行します:
- テスト用の電話番号に OTP を送信する(またはテスト用認証情報を使用する)
- 制御されたチャネルを使って OTP を確認する(Twilio のテスト用認証情報では実際の SMS は送信されません)
- 次の条件を満たした場合に、本番環境へのデプロイを許可します:
- Twilio API の認証に成功する
- Verify Service にアクセスできる
- Webhook エンドポイントの署名検証に合格する
データモデルとの統合
次を保存します:
user_iddestination_e164(保存時に暗号化)destination_hash(ログ用の HMAC)verify_service_sidlast_verification_sidverified_atfailed_attemptscooldown_until
OTP コードは保存しないでください。
エラー処理とトラブルシューティング
Twilio のエラーは文字列の照合ではなくコードで処理し、運用担当者が認識できるように正確なメッセージも含めてください。
1) 20003 — 認証エラー
メッセージ:
Twilio could not authenticate the request. Please check your credentials.
主な原因:
- API Key Secret/Auth Token が間違っています
- SDK の初期化時に
accountSidを指定せず API Key を使用しています - クロックスキューが認証に影響することはまれですが、署名検証には影響する場合があります
対処方法:
- 環境変数とシークレットマネージャーの値を確認します
- API Key を使用する Node SDK では、
{ accountSid }を渡します - API キーが漏えいした場合はローテーションします
2) 20429 — リクエストが多すぎます
メッセージ:
Rate limit exceeded
主な原因:
- Verify Service のレート制限に達しています
- トラフィックが急増しています(ボット攻撃)
- クライアントで再送が繰り返されています
対処方法:
- アプリケーションレベルのスロットリングと指数バックオフを実装します
- 再送のクールダウンを UI に追加します(例:30~60 秒)
- ポリシーが異なる場合に限り、異なるトラフィッククラスごとに Verify Services を分けることを検討します
3) 21211 — 無効な「To」電話番号
メッセージ:
The 'To' number +1415555 is not a valid phone number.
根本原因:
- E.164 形式ではない
- ユーザーが国番号なしで現地形式の番号を入力した
- 解析または正規化の不備
修正方法:
- libphonenumber で正規化する
- 国の選択を必須にするか、ユーザープロフィールから推定する
- 早い段階で、具体的な対処方法を示す UX とともに拒否する
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 を超えて待機した
- 配信の遅延(キャリア)
- クライアントの時計の混乱(UX)
修正方法:
- 正当な理由がある場合は TTL を延長する(トレードオフ:セキュリティ)
- UX を改善する:カウントダウンと再送オプションを表示する
- 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 を再構築します。 - フレームワークで必要な場合は、ボディーパースによって raw body が保持されることを確認してください。構成によっては、検証に raw body が必要です。
PII の取り扱い
- 電話番号とメールアドレスは PII として扱います。
- ログに記録するのは次の情報のみです。
- ローテーション可能なキーを使用した HMAC(destination)
- デバッグ用の下 2 桁(任意)
- 保存時には宛先を暗号化します(KMS エンベロープ暗号化)。
CIS に準拠したホストの強化(概要)
- CIS Ubuntu Linux 22.04 LTS Benchmark:
- SSH のパスワード認証を無効にし、鍵ベースの認証を必須にします
- セキュリティ更新の自動適用を有効にします
- 可能な場合は、アプリケーションホストから Twilio エンドポイントへの送信トラフィックのみを許可します
- systemd のサンドボックス化(上記のユニットファイルを参照)
- 可能な場合は、root 以外のユーザーで実行し、ファイルシステムを読み取り専用にします
不正利用の防止
- 不審な送信試行には、プルーフ・オブ・ワーク/CAPTCHA を必須にします。
- メールチャネルでは、使い捨てメールのドメインをブロックします(ポリシーによります)。
- ASN/国の異常検知を追加します。
パフォーマンスのチューニング
region/edge による 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)を使用します。
再送ループの回避
クライアントの UX:
- 再送ボタンを 30 秒間無効にします
- カウントダウンを表示します
- 失敗が繰り返される場合はバックオフします
これにより、以下を抑制できます:
- Twilio のコスト
- 20429 レート制限
- キャリアによるフィルタリングのリスク
高度なトピック
冪等性の方針
Twilio Verify の「送信」は、呼び出しを繰り返しても本質的に冪等ではありません。アプリケーション側で冪等性を実装します:
- キーを計算します:
sha256(user_id + destination + channel + floor(now/30s)) - Redis に TTL 60s で保存します
- キーが存在する場合は、既存の検証 SID/ステータスを返します
これにより、以下による誤った二重送信を防止できます:
- モバイルからのリトライ
- ダブルクリック
- ネットワークタイムアウト
マルチチャネルのフォールバック方針
決定的なフォールバックを実装します:
- SMS
- SMS の配信が
30003で失敗した場合、または 20s 以内に配信されない場合 → 音声通話 - 音声通話が失敗した場合 → メール、または 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 のステータスコールバックを使用して、
failedと30003を検出します - または、
deliveredがない状態で 20 秒経過したらタイムアウトします(SMS では常に利用できるとは限りません) - 「代わりに電話をかける」オプションをユーザーに提示します
シナリオ 3:カスタムチャネルと SendGrid テンプレートを使用したメール OTP
- カスタム検証をリクエストします:
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 .
- SendGrid でコードを配信します(アプリケーションからメールを送信します)。
- 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 | 主なフラグ/フィールド |
|---|---|---|
| OTP を送信 | POST /v2/Services/{VA}/Verifications | To、Channel、Locale |
| OTP を確認 | POST /v2/Services/{VA}/VerificationCheck | To、Code |
| 検証を一覧表示 | GET /v2/Services/{VA}/Verifications | To、Status、Channel、PageSize |
| 検証を取得 | GET /v2/Services/{VA}/Verifications/{VE} | 該当なし |
| 検証をキャンセル | POST /v2/Services/{VA}/Verifications/{VE} | Status=canceled |
| 認証(推奨) | API キー | SK... +シークレット+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 によるファクターベースの認証)