twilio-lookup について
twilio-lookupは、Twilio Lookupを使った電話番号情報処理スキルです。Twilioの本番ワークフローを構築するエンジニア向けに、番号の正規化・検証や通信事業者、回線種別、発信者名の照会を扱います。
主な機能
- 01番号の検証と E.164 正規化
- 02通信事業者と回線種別の取得
- 03利用可能な場合の発信者名取得
- 04地域・リスクポリシーの適用
- 05Messaging、Voice、Verify の事前確認
活用シーン
- 01メッセージ送信前の無効番号除外
- 02携帯・固定・VoIP別のルーティング
- 03国や地域に基づく番号制限
- 04ユーザープロフィールへの電話情報追加
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ alphaonedev/ openclaw-graph --skill twilio-lookupインストール後、Claude Code で/twilio-lookupを入力して読み込まれたか確認します。
詳細
twilio-lookup
目的
twilio-lookup は、Twilio Lookup を使用した本番環境品質の電話番号インテリジェンスワークフローを実現します。電話番号の検証、E.164 形式への正規化、キャリアメタデータの取得、回線種別(携帯電話/固定電話/VoIP)、発信者番号/CNAM(対応している場合)を扱います。エンジニアは、次の目的でこれを使用します。
- SMS/WhatsApp/Voice の送信前に無効な番号や到達不能な番号を拒否し、21211/30003 のエラーとコストを削減します。
- 回線種別に基づいてトラフィックをルーティングします(例:固定電話への SMS を避ける、VoIP が多い地域では Voice のフォールバックを選ぶ)。
- 地域ポリシーを適用します(例:リスクの高い国をブロックする、10DLC では US/CA を必須にする)。
- キャリア名と発信者名でユーザープロフィールを拡充し、不正利用のスコアリングやサポートツールに活用します。
- サービス間で一貫性のある正規化パイプライン(E.164 +国番号)を構築します。
このガイドでは、強力なエラー処理、レート制限、セキュリティ制御を備えた、より大規模な Twilio 本番環境スタック(Messaging/Voice/Verify/Studio/SendGrid)への Lookup の統合を想定しています。
前提条件
アカウントと API 認証情報
- Twilio Account SID(形式は
ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx) - Twilio Auth Token
- 任意:Twilio API Key + Secret(本番環境では Auth Token より推奨)
- API Key SID の形式は
SKxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
- API Key SID の形式は
API Key の作成:
- Twilio Console → Account → API keys & tokens → Create API key
- シークレットはシークレットマネージャー(AWS Secrets Manager、GCP Secret Manager、Vault)に保存します。コミットしないでください。
Twilio Lookup API
- Lookup API v2 は、現在推奨されている API です。
- 一部のフィールド(例:発信者名)ではアドオンが必要な場合や、地域によって利用可否が異なる場合があります。
ランタイムのバージョン(テスト済み)
- Node.js:20.11.1(LTS)
- Python:3.11.8
- Java:17.0.10(Temurin)
- Go:1.22.1
- Twilio ヘルパーライブラリ:
- twilio-node:4.23.0
- twilio-python:9.4.1
- Twilio CLI(任意ですが便利です):5.19.0
OS のサポート
- Ubuntu 22.04 LTS(x86_64)
- Fedora 39(x86_64)
- macOS 14 Sonoma(Intel + Apple Silicon)
ネットワークと TLS
lookups.twilio.com(認証やその他のサービスにはapi.twilio.com)へのアウトバウンド HTTPS 通信。- TLS インスペクションプロキシでは SNI と最新の暗号スイートを許可する必要があります。許可しない場合、断続的に
ECONNRESET/ハンドシェイクエラーが発生します。
主な概念
メンタルモデル:「正規化 → 拡充 → 判断 → 実行」
- 正規化:ユーザー入力を E.164(例:
+14155552671)に変換し、妥当性を検証します。 - 拡充:メタデータ(キャリア、回線種別、発信者名)を取得します。
- 判断:ポリシーを適用します(許可/拒否、SMS と Voice のルーティング、Verify の必須化)。
- 実行:メッセージ/通話/認証を送信し、監査や今後のルーティングのために拡充データを保存します。
Lookup API のエンティティ
- 電話番号:入力はローカル形式または国内形式を使用できますが、出力は E.164 形式にします。
- フィールド:コスト/レイテンシを増加させる任意の追加項目です。
- よく使われるフィールド:
line_type_intelligence(回線種別 + キャリア)caller_name(利用可能な場合の CNAM に類似するデータ)
- よく使われるフィールド:
- 国番号:入力が E.164 形式ではない場合、解析に影響します。
アーキテクチャの概要
一般的な本番環境のフロー:
- エッジ/API が
phone文字列を受け取ります。 - サービスが Lookup を使用して正規化と検証を行います。
- 結果を次の項目とともにユーザープロフィールテーブルに保存します。
e164、country_code、national_formatline_type、carrier_name、mobile_country_code、mobile_network_codecaller_name(使用する場合)lookup_timestamp、lookup_version、risk_flags
- 下流の処理:
- Messaging サービスが地域に合った Messaging Service SID を選択します。
- Verify では正規化済みの E.164 を使用します。
- Voice では E.164 を使用し、必要に応じて SIP ルーティングを行います。
コストとレイテンシのトレードオフ
- 基本的な電話番号検証は、追加フィールドを要求するよりも低コストです。
fields=line_type_intelligenceは追加の照会処理を必要とします。結果をキャッシュしてください。fields=caller_nameは処理に時間がかかり、信頼性も低い場合があります。必要な場合にのみ使用してください。
キャッシュ戦略
- リスク許容度に応じて、E.164形式の番号を一定のTTL(例:7~30日)でキャッシュします。
- キャリアや回線種別は変わる場合があります(番号ポータビリティ)。リスクの高いフローでは、重要なイベント(パスワードのリセット、支払い)時に再確認します。
インストールとセットアップ
公式 Python SDK — Lookup
リポジトリ: https://github.com/twilio/twilio-python
PyPI: pip install twilio · 対応バージョン: Python 3.7~3.13
from twilio.rest import Client
client = Client()
# Basic lookup
phone = client.lookups.v2.phone_numbers("+15558675309").fetch()
print(phone.country_code, phone.phone_number)
# With add-ons
phone = client.lookups.v2.phone_numbers("+15558675309").fetch(
fields=["line_type_intelligence", "caller_name", "sim_swap"]
)
print(phone.line_type_intelligence) # {"type": "mobile", "error_code": null}
print(phone.caller_name) # {"caller_name": "Alice", "error_code": null}
ソース:twilio/twilio-python — lookups
1) Twilio CLI をインストールする(任意ですが推奨)
Ubuntu 22.04(x86_64)
curl -sSL https://twilio-cli-prod.s3.amazonaws.com/twilio-cli-linux-x86_64.tar.gz -o /tmp/twilio.tar.gz
sudo tar -xzf /tmp/twilio.tar.gz -C /usr/local/bin twilio
twilio --version
Fedora 39(x86_64)
curl -sSL https://twilio-cli-prod.s3.amazonaws.com/twilio-cli-linux-x86_64.tar.gz -o /tmp/twilio.tar.gz
sudo tar -xzf /tmp/twilio.tar.gz -C /usr/local/bin twilio
twilio --version
Homebrew 経由で macOS(Intel + Apple Silicon)
brew update
brew install twilio/brew/twilio
twilio --version
2) Twilio CLI を認証する
推奨:API Key
twilio login --apikey YOUR_API_KEY_SID --apisecret 'your_api_key_secret' --profile prod
twilio profiles:list
代替:Account SID + Auth Token
twilio login --sid YOUR_ACCOUNT_SID --token 'your_auth_token' --profile prod
3) ヘルパーライブラリをインストールする
Node.js(20.11.1)
node --version
npm --version
mkdir -p twilio-lookup-demo-node && cd twilio-lookup-demo-node
npm init -y
npm install [email protected] [email protected]
Python(3.11.8)
python3 --version
python3 -m venv .venv
source .venv/bin/activate
pip install --upgrade pip==24.0
pip install twilio==9.4.1 httpx==0.27.0
4) 環境変数
シェル(開発環境)またはシークレットマネージャー(本番環境)に設定します。
export TWILIO_ACCOUNT_SID="YOUR_ACCOUNT_SID"
export TWILIO_AUTH_TOKEN="your_auth_token"
# or API key auth:
export TWILIO_API_KEY_SID="YOUR_API_KEY_SID"
export TWILIO_API_KEY_SECRET="your_api_key_secret"
5) 接続を確認する
curl -sS https://lookups.twilio.com/ -I | head
HTTP/2 404 などが返されます(ルートパスが見つからないのは問題ありません)。目的は TLS 接続の確認です。
主な機能
電話番号の検証 + E.164形式への正規化
- さまざまな形式のユーザー入力を受け付けます。
- 入力が E.164形式でない場合は、
countryCodeを使用します。 - 正規化した E.164形式の番号と、そこから導出した国を保存します。
主な出力フィールド:
phone_number(E.164)national_formatcountry_codevalid(v1)/v2 のセマンティクスは異なるため、「Lookup が成功した」ことと解析結果として扱います。
キャリアの Lookup + 回線種別情報
fields=line_type_intelligence を使用して、以下を取得します。
line_type(例:mobile、landline、voip、nonFixedVoip、tollFree)carrier_namemobile_country_code(MCC)mobile_network_code(MNC)error_code(キャリア固有の問題)
本番環境での使用例:
- ポリシーに応じて、SMS の送信先を
mobile/nonFixedVoipのみに限定します。 - 不正利用スコアリングのため、
voipにフラグを付けます(アカウント乗っ取りでよく見られます)。 - フリーダイヤル番号を検出し、異なるメッセージングルールを適用します。
発信者 ID / CNAM(caller_name)
fields=caller_name を使用します(利用可能かどうかは国と番号種別によって異なります)。
一般的な用途:
- サポートツールで「名前の可能性が高い情報」を表示します。
- 不正利用のヒューリスティック(申告された名前と CNAM の不一致)に利用します。
CNAM を本人確認の確定情報として扱わないでください。
ポリシーの適用(地域 + リスク)
Lookup の結果を以下と組み合わせます。
- 国の許可/拒否リスト
- リスクの高いことが知られているキャリア
- 機密性の高い操作に対する VoIP の制限
- 10DLC の制約(米国の A2P メッセージングには登録が必要です。Lookup を使うと、米国の番号が実際に米国の番号であることを確認できます)
Messaging/Voice/Verify の送信前ゲート
- Messaging:
21211(無効な To)と30003(到達不能)を減らします。 - Voice:無効な番号への発信を避け、SIP トランクと PSTN のどちらを使うか選択します。
- Verify:E.164形式に正規化し、ポリシーで必要な場合は VoIP をブロックします。
コマンドリファレンス
Twilio CLI:Lookup(インストール済みの場合)
Twilio CLI のコマンド名は、バージョンやプラグインによって異なる場合があります。CLI 5.19.0 では、通常 Lookup は api の機能から利用できます。
電話番号を取得する(基本)
twilio api:lookups:v2:phone-numbers:fetch --phone-number "+14155552671" --profile prod
フラグ:
--phone-number(文字列、必須):E.164形式または未加工の入力値。--profile(文字列):Twilio CLI のプロファイル名。
フィールドを指定して取得する
twilio api:lookups:v2:phone-numbers:fetch \
--phone-number "+14155552671" \
--fields "line_type_intelligence,caller_name" \
--profile prod
フラグ:
--fields(カンマ区切り):line_type_intelligence、caller_name
国コードを指定して取得する(E.164形式ではない入力の場合)
twilio api:lookups:v2:phone-numbers:fetch \
--phone-number "4155552671" \
--country-code "US" \
--fields "line_type_intelligence" \
--profile prod
フラグ:
--country-code(ISO-3166-1 alpha-2):例:US、CA、GB
CLI のビルドで api:lookups:* が公開されていない場合は、直接 HTTPS(下記)またはヘルパーライブラリを使用してください。
直接 HTTPS(curl)— Lookup v2
Twilio Lookup v2 のエンドポイント形式:
GET https://lookups.twilio.com/v2/PhoneNumbers/{PhoneNumber}?Fields=...&CountryCode=...
基本的な Lookup
curl -sS -u "$TWILIO_ACCOUNT_SID:$TWILIO_AUTH_TOKEN" \
"https://lookups.twilio.com/v2/PhoneNumbers/+14155552671" | jq
フィールドを指定する場合
curl -sS -u "$TWILIO_ACCOUNT_SID:$TWILIO_AUTH_TOKEN" \
"https://lookups.twilio.com/v2/PhoneNumbers/+14155552671?Fields=line_type_intelligence,caller_name" | jq
国内形式の入力に CountryCode を指定する場合
curl -sS -u "$TWILIO_ACCOUNT_SID:$TWILIO_AUTH_TOKEN" \
"https://lookups.twilio.com/v2/PhoneNumbers/4155552671?CountryCode=US&Fields=line_type_intelligence" | jq
注意:
- 使用するツールで
+が正しく処理されない場合は、URL エンコードを使用します(%2B14155552671)。 - 本番環境では
SK...:secretの Basic 認証を使用し、API Key 認証を優先します。
Node.js(twilio-node 4.23.0)
基本的な検索
import twilio from "twilio";
const client = twilio(process.env.TWILIO_ACCOUNT_SID, process.env.TWILIO_AUTH_TOKEN);
const res = await client.lookups.v2
.phoneNumbers("+14155552671")
.fetch();
console.log(res.phoneNumber, res.countryCode, res.nationalFormat);
フィールドを指定する場合
import twilio from "twilio";
const client = twilio(process.env.TWILIO_ACCOUNT_SID, process.env.TWILIO_AUTH_TOKEN);
const res = await client.lookups.v2
.phoneNumbers("+14155552671")
.fetch({ fields: "line_type_intelligence,caller_name" });
console.log({
e164: res.phoneNumber,
lineType: res.lineTypeIntelligence?.type,
carrier: res.lineTypeIntelligence?.carrier_name,
callerName: res.callerName?.caller_name,
});
フラグ/オプション:
fetch({ fields: "..." }):カンマ区切りのフィールドfetch({ countryCode: "US" }):国内番号を入力する場合(ヘルパーのバージョンが対応している場合)
API キー認証(推奨)
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 }
);
Python(twilio-python 9.4.1)
基本的な検索
import os
from twilio.rest import Client
client = Client(os.environ["TWILIO_ACCOUNT_SID"], os.environ["TWILIO_AUTH_TOKEN"])
res = client.lookups.v2.phone_numbers("+14155552671").fetch()
print(res.phone_number, res.country_code, res.national_format)
フィールドを指定する場合
import os
from twilio.rest import Client
client = Client(os.environ["TWILIO_ACCOUNT_SID"], os.environ["TWILIO_AUTH_TOKEN"])
res = client.lookups.v2.phone_numbers("+14155552671").fetch(
fields="line_type_intelligence,caller_name"
)
print({
"e164": res.phone_number,
"line_type": getattr(res, "line_type_intelligence", None),
"caller_name": getattr(res, "caller_name", None),
})
API キー認証
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"],
)
設定リファレンス
1) サービス設定ファイル(推奨)
パス(Linux):
/etc/openclaw/twilio/lookup.toml
パス(macOS 開発環境):
~/Library/Application Support/OpenClaw/twilio/lookup.toml
例:
# /etc/openclaw/twilio/lookup.toml
[twilio]
account_sid = "YOUR_ACCOUNT_SID"
auth_mode = "api_key" # "api_key" or "auth_token"
[twilio.api_key]
sid = "YOUR_API_KEY_SID"
secret_env = "TWILIO_API_KEY_SECRET" # secret is read from env at runtime
[lookup]
base_url = "https://lookups.twilio.com"
default_country_code = "US"
default_fields = ["line_type_intelligence"]
timeout_ms = 2500
max_retries = 2
retry_backoff_ms = 200
retry_jitter_ms = 100
[cache]
enabled = true
backend = "redis" # "redis" or "memory"
ttl_seconds = 604800 # 7 days
negative_ttl_seconds = 3600 # cache invalid numbers for 1 hour
[policy]
allow_countries = ["US", "CA"]
deny_line_types = ["landline"]
flag_line_types = ["voip", "nonFixedVoip"]
2) 環境変数
本番環境のランタイムでは、以下をサポートする必要があります。
TWILIO_ACCOUNT_SIDTWILIO_AUTH_TOKEN(認証トークンを使用する場合)TWILIO_API_KEY_SIDTWILIO_API_KEY_SECRETOPENCLAW_TWILIO_LOOKUP_CONFIG=/etc/openclaw/twilio/lookup.toml
systemd のドロップイン設定の例:
# /etc/systemd/system/openclaw.service.d/twilio-lookup.conf
[Service]
Environment="OPENCLAW_TWILIO_LOOKUP_CONFIG=/etc/openclaw/twilio/lookup.toml"
Environment="TWILIO_ACCOUNT_SID=YOUR_ACCOUNT_SID"
Environment="TWILIO_API_KEY_SID=YOUR_API_KEY_SID"
Environment="TWILIO_API_KEY_SECRET=/run/secrets/twilio_api_key_secret"
TWILIO_API_KEY_SECRET がファイルパスを指している場合、ランタイムではファイルの内容を読み込む必要があります(一般的な方法です)。サポートされていない場合は、シークレットインジェクターを使ってシークレットを環境変数に直接保存してください。
3) Redis キャッシュ設定(使用する場合)
パス:
/etc/openclaw/redis.conf.d/lookup.conf
# /etc/openclaw/redis.conf.d/lookup.conf
maxmemory 512mb
maxmemory-policy allkeys-lru
timeout 0
tcp-keepalive 300
キー形式の推奨:
lookup:v2:{e164}:{fields_hash}:{country_code}
統合パターン
パターン:メッセージング(SMS/WhatsApp)の送信前判定
パイプライン:
- Lookup で正規化と回線種別の判定
- 無効な場合 → リクエストを拒否(HTTP 400)
- 固定電話の場合 → 音声通話に切り替えるか、ブロック
- 携帯電話/VoIP の場合 → Messaging Service 経由で送信(地域照合)
- メッセージステータスの Webhook を追跡し、STOP を処理
例(擬似コード):
POST /send_sms
-> lookup(+input, fields=line_type_intelligence)
-> if line_type in deny: 422 "unsupported line type"
-> send SMS via Messaging Service SID MGxxxxxxxx...
-> store message SID + lookup snapshot
Twilio Messaging の本番環境向けパターンと組み合わせます。
- 地域照合を使用する Messaging Service により、コストを削減し、配信性を向上させます。
- ステータスコールバックの Webhook リトライと冪等性を処理します。
- オプトアウトキーワード(STOP/START/HELP)を適用します。
パターン:VoIP 制限を設けた登録確認
- まず Lookup を実行します。結果が
voipで、詐欺対策ポリシーにより MFA での VoIP が許可されていない場合は、Verify のカスタムチャネルを通じて別のチャネル(TOTP/メール)を要求します。
パターン:固定電話への音声フォールバック
- Lookup の結果が
landlineの場合は SMS をスキップし、Polly 音声を使った TwiML<Say>で音声通話を発信するか、<Dial>で接続します。
パターン:エンリッチメントを伴う Studio Flow のトリガー
- バックエンドで Lookup を使用し、エンリッチメント済みのパラメーター(
line_type、carrier、country)を指定して、REST Trigger API 経由で Studio Flow をトリガーします。 - Studio ではこれらのパラメーターに基づき、IVR/SMS のフローを分岐できます。
パターン:データウェアハウスのエンリッチメントジョブ
- バッチジョブで新規または変更された電話番号を読み込み、レート制限を適用して Lookup を実行し、結果をウェアハウスに書き込みます。
- キャッシュとバックオフを使用して、
20429 Too Many Requestsを回避します。
エラー処理とトラブルシューティング
エラーを最優先事項として扱い、分類して安全に再試行し、対処可能な診断情報を提示してください。
1) Twilio エラー 20003 — 認証
メッセージ(一般的な例):
Authenticate- または JSON:
"code": 20003, "message": "Authenticate"
主な原因:
- Auth Token/API Key secret が間違っている
- ヘルパークライアントで
accountSidを指定せずに API Key SID を使用している - シークレットをローテーションしたが、デプロイされていない
対処方法:
- シークレットマネージャーとランタイムへの注入設定で認証情報を確認します。
- ヘルパーライブラリで API キー認証を使う場合は、
accountSidを設定します。 - 正しい Basic 認証で
lookups.twilio.comにアクセスしていることを確認します。
2) Twilio エラー 20429 — レート制限
メッセージ:
"code": 20429, "message": "Too Many Requests"
主な原因:
- クライアント側のスロットリングを行わずに大量のトラフィックが発生している
- バッチエンリッチメントジョブの負荷が高すぎる
対処方法:
- アカウントごと(必要に応じてリージョンごと)にトークンバケットを実装します。
- ジッターを加えた指数バックオフを使い、再試行回数に上限(例:2~3 回)を設けます。
- 結果をキャッシュし、同じ E.164 番号の検索を繰り返さないようにします。
3) Twilio エラー 21211 — 無効な「To」(下流の Messaging/Voice)
メッセージ:
The 'To' number +1415555 is not a valid phone number.
根本原因:
- Lookup による正規化を省略している
- 国の情報を確認せずにユーザー入力を受け付けている
- E.164 形式以外で保存し、後で再利用している
修正方法:
- Lookup から取得した E.164 形式の番号を必ず保存します。
- 国内形式の入力には
countryCodeを必須にします。 - API の境界で検証を追加し、あいまいな形式は拒否します。
4) Twilio エラー 30003 — 宛先の端末に到達できない(Messaging)
メッセージ:
Unreachable destination handset
根本原因:
- 番号は有効ですが、到達できません(利用停止中、ローミング制限、ブロックなど)
- キャリアによるフィルタリング/宛先の制限
修正方法:
- Lookup の回線種別とキャリアを使用して、問題のあるセグメントを検出します。
- 代替チャネル(WhatsApp、Voice、メール)を実装します。
- 配信失敗を記録し、繰り返し送信を抑制します。
5) Lookup からの HTTP 400 — リクエストの形式が不正
メッセージ(典型例):
400 Bad Request
根本原因:
Fieldsの値が無効(タイプミス)- 電話番号の
+の URL エンコードに誤りがある CountryCodeの形式がサポートされていない(USAではなくUSが必要)
修正方法:
- 許可リストを使用して
fieldsを検証します:line_type_intelligence、caller_name。 - 生の HTTP クライアントでは電話番号を URL エンコードします。
- ISO 3166-1 alpha-2 の国コードを必須にします。
6) Lookup からの HTTP 404 — 番号が見つからない/無効
メッセージ(一般的な JSON):
"status": 404, "message": "The requested resource /PhoneNumbers/... was not found"
根本原因:
- 指定された国の情報では、番号を解析できない、または有効と判定できない
- 国内形式の入力に
CountryCodeが指定されていない
修正方法:
- ユーザーのロケールが分かる場合は、明示的な
CountryCodeを指定して一度だけ再試行します。 - それ以外の場合は拒否し、E.164 形式での入力を求めます。
7) ネットワークエラー:タイムアウト/TLS
メッセージ:
- Node:
ETIMEDOUT、ECONNRESET - Python httpx:
ReadTimeout、ConnectError
根本原因:
- 外向き通信のファイアウォール/プロキシの問題
- 負荷時にタイムアウトが短すぎる
- DNS が不安定
修正方法:
- Lookup のタイムアウトを約 2~3 秒に設定し、重要な処理を無期限にブロックしないようにします。
- 再試行はネットワークエラーの場合に限り、4xx では行いません。
- DNS リゾルバーの安定性を確保し、必要に応じて OS レベルでの DNS キャッシュを検討します。
8) JSON の解析/スキーマの変更
メッセージ:
TypeError: Cannot read properties of undefined (reading 'type')- または Python:
AttributeError: 'PhoneNumberInstance' object has no attribute ...
根本原因:
- リクエストしていないフィールドも存在すると仮定している
- リージョンが caller_name に対応していない
- ヘルパーライブラリのバージョンが一致していない
修正方法:
- 任意の拡張フィールドは null になり得るものとして扱います。
- アクセスを条件付きにします:
res.lineTypeIntelligence?.type。 - ヘルパーライブラリのバージョンを固定し、コントラクトテストを追加します。
9) Twilio エラー 21614/21610(オプトアウト)— 下流の Messaging
メッセージ:
21610: Message cannot be sent because the recipient has opted out of receiving messages from this number.21614: 'To' number is not a valid mobile number
根本原因:
- STOP リストを遵守していない
- 固定電話に SMS を送信している
修正方法:
- オプトアウト抑制リストを管理し、受信した STOP/START に対応します。
- 送信前に Lookup の回線種別による判定を行います。
セキュリティ強化
認証情報の取り扱い
- Auth Token よりも API Key + Secret を優先します。
- API キーを四半期ごと(またはポリシーに従って)にローテーションします。
- シークレットマネージャーにシークレットを保存し、実行時に注入します。
- ログに以下が含まれないようにします:
- Auth Token/API key secret
- 平文の完全な電話番号(マスクまたはハッシュ化)
マスキングの推奨:
- 下2~4桁のみを記録します:
+1415555**** - または、照合用に E.164 形式の SHA-256 を保存します。
最小権限と影響範囲
- 環境(dev/stage/prod)ごとに Twilio のサブアカウントを分けます。
- サービスごとに API キーを分けます(lookup-enricher と messaging-sender)。
- Twilio Console のロールを使用する場合は、キーを作成/ローテーションできるユーザーを制限します。
通信のセキュリティ
- TLS 1.2 以上を必須にします。
- 社内プロキシを使用する場合は、許可するドメインを指定します:
lookups.twilio.comapi.twilio.com
CIS に準拠したホストの強化(該当する場合)
- CIS Ubuntu Linux 22.04 LTS Benchmark(v1.0.0)の関連コントロール:
- シークレットが全ユーザーから読み取り可能にならないようにします(SID を含む設定ファイルには
chmod 600を適用します)。 - 必要な宛先へのアウトバウンドトラフィックを制限します(送信先の許可リスト)。
- 監査ログを一元管理し、ログの完全性を保護します。
- シークレットが全ユーザーから読み取り可能にならないようにします(SID を含む設定ファイルには
Webhook のセキュリティ(複合システム)
Lookup 自体はアウトバウンドのみですが、Messaging/Voice の Webhook と組み合わせて使用します。
- ステータスコールバックで Twilio の署名(
X-Twilio-Signature)を検証します。 - Webhook の処理に冪等性キーを実装します。
- リトライに安全なハンドラーを適用します(Twilio は 2xx 以外の応答でリトライします)。
パフォーマンスのチューニング
1) Lookup の結果をキャッシュする(最も効果的)
期待される効果:
- 定常状態では Lookup 呼び出しを 60~95%削減します(変更頻度によって異なります)。
- ユーザー向けエンドポイントの p95 レイテンシを低減します(キャッシュヒット時は約 1~5ms、ネットワーク経由では 150~600ms)。
ガイドライン:
- 一般的なルーティングでは TTL を 7 日に設定します。
- 番号移行のリスクが重要な高リスクのフローでは、TTL を 1 日に設定します。
- 無効な番号を 1 時間ネガティブキャッシュし、悪用の繰り返しを減らします。
2) 必要なフィールドだけをリクエストする
期待される効果:
- コストとレイテンシを低減します。
- 必要がない限り
caller_nameは取得しないでください。
例:
- サインアップ:
line_type_intelligenceのみ。 - サポートエージェントの画面:必要に応じて
caller_nameを取得。
3) 同時実行制御とバックプレッシャー
- プロセスごとのセマフォを実装します(例:同時 Lookup 数を最大 50 件に制限)。
- バッチジョブ向けに、グローバルレートリミッター(Redis ベース)を追加します。
期待される効果:
20429応答を減らします。- 負荷がかかった状態でもレイテンシをより予測しやすくします。
4) タイムアウトとリトライ
推奨設定:
- タイムアウト:2.5s
- リトライ:2 回(ネットワーク/5xx/20429 の場合のみ)
- バックオフ:200ms、400ms + ジッター
期待される効果:
- テールレイテンシの増幅を抑えます。
- リトライストームを防ぎます。
高度なトピック
番号移行とキャリアの変動
- 番号移行によって、キャリア名や回線種別が変わる場合があります。
- 支払い処理やパスワードリセットなどの重要なアクションでは、キャッシュデータがしきい値(例:24h)より古い場合に Lookup を再確認します。
国別番号が曖昧な場合の処理
- ユーザーが
07700 900123と入力した場合は、国の情報(GB)が必要です。 - 次の情報を使用します。
- ユーザーが選択した国
- GeoIP から推定した国(慎重に扱います)
- アカウントプロフィールの国
- 国を誤って推定すると、番号を誤って正規化する可能性があります。UX では国を明示的に選択させることを推奨します。
VoIP に関する注意点
- プロバイダーによっては、
nonFixedVoipとvoipを区別します。 - ポリシーを明確に定めてください。
- リスクの低い通知では
nonFixedVoipを許可する - 脅威モデル上必要な場合は、MFA でブロックする
- リスクの低い通知では
フリーダイヤル番号と短縮コード
- Lookup は電話番号を対象とします。短縮コードは標準的な E.164 形式ではありません。
- メッセージングでは短縮コードを個別に扱い、Lookup に送信しないでください。
国際化と形式
- E.164 を正規形式として保存します。
national_formatは表示専用として保存し、送信には使用しないでください。
データ保持
- 電話番号に関するインテリジェンスは、個人データと見なされる場合があります。
- 保持ポリシーを適用します。
- 必要なデータ(回線種別、国)だけを、必要な期間だけ保持します。
- 分析用に E.164 のハッシュ化を検討します。
使用例
1) API エンドポイント:検証、正規化、保存
厳格なポリシーとキャッシュのスタブを使った Node.js の例:
import twilio from "twilio";
import pino from "pino";
const log = pino();
const client = twilio(process.env.TWILIO_ACCOUNT_SID, process.env.TWILIO_AUTH_TOKEN);
const ALLOW_COUNTRIES = new Set(["US", "CA"]);
const DENY_LINE_TYPES = new Set(["landline"]);
export async function normalizePhone({ input, countryCode }) {
const res = await client.lookups.v2
.phoneNumbers(input)
.fetch({ fields: "line_type_intelligence", countryCode });
const e164 = res.phoneNumber;
const cc = res.countryCode;
const lineType = res.lineTypeIntelligence?.type ?? "unknown";
if (!ALLOW_COUNTRIES.has(cc)) {
throw Object.assign(new Error(`country_not_allowed: ${cc}`), { status: 422 });
}
if (DENY_LINE_TYPES.has(lineType)) {
throw Object.assign(new Error(`line_type_not_supported: ${lineType}`), { status: 422 });
}
log.info({ e164, cc, lineType }, "phone_normalized");
return { e164, countryCode: cc, lineType };
}
2) SMS 送信前の判定と Voice へのフォールバック
判定ロジック:
- mobile/nonFixedVoip → SMS
- landline → Voice 通話
- voip → Verify または代替チャネルを必須にする(ポリシーによる)
疑似実装:
from twilio.rest import Client
import os
client = Client(os.environ["TWILIO_ACCOUNT_SID"], os.environ["TWILIO_AUTH_TOKEN"])
def route_notification(phone: str):
lookup = client.lookups.v2.phone_numbers(phone).fetch(fields="line_type_intelligence")
e164 = lookup.phone_number
lt = lookup.line_type_intelligence.get("type") if lookup.line_type_intelligence else "unknown"
if lt in ("mobile", "nonFixedVoip"):
msg = client.messages.create(
to=e164,
from_="+15005550006",
body="Your package is arriving today."
)
return {"channel": "sms", "sid": msg.sid}
if lt == "landline":
call = client.calls.create(
to=e164,
from_="+15005550006",
twiml="<Response><Say voice='Polly.Joanna'>Your package is arriving today.</Say></Response>"
)
return {"channel": "voice", "sid": call.sid}
raise ValueError(f"unsupported_line_type: {lt}")
3) レート制限とキャッシュを使った一括データ拡充ジョブ
Shell、curl、jq の例(簡易版です。本番環境では適切なワーカーを使用してください):
set -euo pipefail
INPUT_CSV="phones.csv" # one phone per line
FIELDS="line_type_intelligence"
COUNTRY="US"
while read -r phone; do
# naive throttle: 5 req/s
sleep 0.2
curl -sS -u "$TWILIO_ACCOUNT_SID:$TWILIO_AUTH_TOKEN" \
"https://lookups.twilio.com/v2/PhoneNumbers/${phone}?CountryCode=${COUNTRY}&Fields=${FIELDS}" \
| jq -c '{phone_number, country_code, line_type_intelligence}'
done < "$INPUT_CSV"
4) データ拡充したパラメーターを使った Studio Flow のトリガー
バックエンドで Lookup を行い、その後 Studio をトリガーします。
curl -sS -u "$TWILIO_ACCOUNT_SID:$TWILIO_AUTH_TOKEN" \
-X POST "https://studio.twilio.com/v2/Flows/FW0123456789abcdef0123456789abcdef/Executions" \
--data-urlencode "To=+14155552671" \
--data-urlencode "From=+15005550006" \
--data-urlencode "Parameters={\"line_type\":\"mobile\",\"carrier\":\"T-Mobile USA, Inc.\"}"
Studio では line_type に応じて分岐し、SMS と Voice のどちらを使用するか選択できます。
5) Verify の登録:MFA で VoIP をブロック
フロー:
line_type_intelligenceを使って Lookup を実行voip/nonFixedVoipの場合 → TOTP またはメールを必須にする- それ以外の場合は Verify SMS を送信
Python の例:
import os
from twilio.rest import Client
client = Client(os.environ["TWILIO_ACCOUNT_SID"], os.environ["TWILIO_AUTH_TOKEN"])
def start_mfa(phone: str):
lookup = client.lookups.v2.phone_numbers(phone).fetch(fields="line_type_intelligence")
e164 = lookup.phone_number
lt = lookup.line_type_intelligence.get("type") if lookup.line_type_intelligence else "unknown"
if lt in ("voip", "nonFixedVoip"):
return {"action": "require_totp", "reason": f"line_type={lt}"}
verification = client.verify.v2.services("YOUR_VERIFY_SERVICE_SID") \
.verifications.create(to=e164, channel="sms")
return {"action": "verify_sms", "sid": verification.sid, "status": verification.status}
6) サポートツール:必要に応じて発信者名を Lookup
エージェントがプロフィールを開いたときにのみ caller_name を取得します。
curl -sS -u "$TWILIO_ACCOUNT_SID:$TWILIO_AUTH_TOKEN" \
"https://lookups.twilio.com/v2/PhoneNumbers/+14155552671?Fields=caller_name" | jq '.caller_name'
クイックリファレンス
| タスク | コマンド/API | 主なフラグ/パラメーター |
|---|---|---|
| 基本的な照会 | GET /v2/PhoneNumbers/{num} | なし |
| 回線種別とキャリアを含む照会 | GET ...?Fields=line_type_intelligence | Fields |
| 発信者名を含む照会 | GET ...?Fields=caller_name | Fields |
| 国内番号の入力解析 | .../{num}?CountryCode=US | CountryCode |
| CLI で取得 | twilio api:lookups:v2:phone-numbers:fetch | --phone-number、--fields、--country-code、--profile |
| Node で取得 | client.lookups.v2.phoneNumbers(num).fetch() | { fields, countryCode } |
| Python で取得 | client.lookups.v2.phone_numbers(num).fetch() | fields=... |
グラフ上の関係
DEPENDS_ON
twilio-core-auth(Account SID/Auth Token または API Key の認証パターン)twilio-cli(任意。デバッグや運用ワークフロー用)secrets-management(Vault/AWS/GCP のシークレット注入)redis(任意。キャッシュとレート制限用)
COMPOSES
twilio-messaging(送信前の判定、Messaging Services の地域マッチング、STOP の処理、ステータス Webhook)twilio-voice(フォールバックルーティング、TwiML の生成、通話の録音/文字起こし)twilio-verify(E.164 正規化、VoIP の制限、不正利用対策)twilio-studio(Lookup の情報拡充に基づく Flow の分岐)sendgrid-email(電話番号が無効/到達不能な場合のフォールバックチャネル)
SIMILAR_TO
libphonenumber(ローカルでの解析/書式設定用。到達可能性やキャリアの判断における権威ある情報源ではありません)numverify/その他の電話番号インテリジェンスプロバイダー(機能は一部重複しますが、対応範囲や費用は異なります)HLR lookupサービス(一部の地域におけるキャリア/到達可能性の確認用。意味合いや合法性に関する制約は異なります)