mcpField
T
twilio-verify

twilio-verify

github.com/alphaonedevセキュリティ48 回インストール
リポジトリ

twilio-verify について

twilio-verifyは、OpenClawでTwilio Verify V2を統合・運用するためのスキルです。アプリの認証フローを構築する開発者向けに、複数のチャネル、不正対策、レート制限、イベント監視を扱います。

主な機能

  1. 01ワンタイムコードの送信と照合
  2. 02SMS、音声、メール、TOTPへの対応
  3. 03カスタム認証チャネルの設定
  4. 04Fraud Guardによるリスク制御
  5. 05Webhookによる認証イベントの記録
  6. 06サービスとアプリ双方のレート制限

活用シーン

  1. 01サインインフローへのSMS認証追加
  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 アカウント:
    • Account SID(AC...)
    • Auth Token(または API Key + Secret)
    • Twilio Console → Verify → Services で作成した Verify Service SID(VA...)
  • 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 ヘルパーライブラリ:
    • 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
認証の設定(推奨パターン)

サーバーサイドアプリでは、Auth Token より API Key 認証を優先してください。

  • 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...
  • ポリシーが実質的に異なる場合に限り、製品/テナントごとに別のサービスを使用します。
Verification と Verification Check
  • Verification:チャネルを通じて、宛先(電話番号/メールアドレス)にチャレンジ(OTP)を送信します。
  • Verification Check:ユーザーが入力したコード(または別の要素)を、検証試行と照合します。

アプリケーションでは、次の手順を実行してください。

  1. 検証を作成する(送信)
  2. ユーザーの入力を受け付ける
  3. Verification Check を作成する(検証)
  4. 結果に応じた処理を行う(セッショントークンの発行、電話番号の検証済みマークなど)
チャネル

一般的なチャネル:

  • sms: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 形式の電話番号を 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 を使用します。一般的なフローは次のとおりです。

  1. TOTP ファクターを作成します(登録)
  2. QR コードまたはシークレットをユーザーに表示します
  3. 初回のコードを検証します
  4. ファクター 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|custom
  • Locale(string):例:en、es、fr
  • ChannelConfiguration.*(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):ユーザーが入力した OTP
  • VerificationSid(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|canceled
  • Channel(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 で関連付けます

パイプライン:

  1. POST /verify/send → Twilio Verify が検証を作成します
  2. Twilio が SMS を送信します
  3. Messaging のステータスコールバックが /webhooks/sms-status に到達します
  4. MessageSid、MessageStatus、ErrorCode(例:30003)を保存します
  5. 失敗が繰り返される場合は、自動的に音声/メールに切り替えます
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_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 の初期化時に 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/ステータスを返します

これにより、以下による誤った二重送信を防止できます:

  • モバイルからのリトライ
  • ダブルクリック
  • ネットワークタイムアウト
マルチチャネルのフォールバック方針

決定的なフォールバックを実装します:

  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 のステータスコールバックを使用して、failed と 30003 を検出します
  • または、delivered がない状態で 20 秒経過したらタイムアウトします(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主なフラグ/フィールド
OTP を送信POST /v2/Services/{VA}/VerificationsTo、Channel、Locale
OTP を確認POST /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 キー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 によるファクターベースの認証)

コメント