Twilio Verifyは、利用者の電話番号やメールアドレスにワンタイムコードを送り、入力されたコードを照合して本人確認を行うAPIです。コードの生成・保管・再送制御・SMS Pumping対策をTwilio側が受け持つため、アプリケーションは「送る」「照合する」の2回のAPI呼び出しでコード認証を組み込めます。二要素認証にするには、パスワードなど別種の認証要素と組み合わせます。この記事では、日本宛SMSと音声の1件あたり実費、日本の携帯にコードが届かない原因、Node.jsとPythonの実装、Webhookの署名検証までを、2026年11月1日時点の公式ドキュメントと手元での実行結果にもとづいて整理します。
まとめ
Twilio Verifyが確かめるのは「その電話番号(またはメールアドレス)にコードが届き、入力できる人物か」です。声紋を照合する生体認証ではありません。コード送信APIのchannelに指定できる値はsms、call、email、whatsapp、sna、autoの6つです。
料金は認証成功ごとに0.05ドルと、チャネルごとの料金の合計です。日本宛SMSはlong codeで1セグメント0.0890ドルなので、1回で成功すれば1件およそ0.139ドルになります。コードの有効期限は既定で10分ですが、サポートに依頼すれば2分から24時間の範囲で変更できます。送信と照合はそれぞれ5回までで、上限を超えるとエラー60203・60202が返ります。
日本で見落とされやすいのは到達性です。通常の国際ゲートウェイ経由のSMSは、ドコモの「国際SMS拒否」やソフトバンクの「海外電話番号メールの拒否」を設定した利用者には届きません。電話番号の所持確認を必須にするサービスでは、同じ番号への音声通話に切り替える導線を用意してください。メール認証では電話番号の所持確認を代替できません。
Twilio Verifyが受け持つ範囲とチャネルの提供段階
channelに指定できる6つの値と声紋認証との違い
「音声認証」という語は、声の特徴量を照合する声紋認証と、電話でコードを読み上げる電話認証の両方を指します。Verifyが提供するのは後者だけです。Verification APIのリファレンスでは、コード送信時のChannelはemail、sms、whatsapp、call、sna、autoのいずれかと定義されており、callは合成音声でコードを読み上げるだけです。声紋認証が要件なら、話者認識エンジンを別に調達することになります。音声通話そのものをAIと組み合わせる構成はTwilio APIで音声AIを動かす4ルート|設定手順・日本料金・日本語精度で扱っています。
RCSはchannelの値ではありません。smsを指定したとき、対応端末・提供地域などの条件を満たし、認証成功率と成功あたりの配信コストが同等以上と判断された場合にTwilioがRCS配信へ切り替える動作で、RCSの説明ページは「届いたRCSはSMSとして課金し、送信したが届かなかったRCSは課金しない」としています。TOTPとPushもchannelの値ではなく、Factor(認証要素)とChallengeを使う別系統のAPIです。
SNA・auto・Passkeys・Pushの提供段階
公式のチャネル一覧には9種類が並びますが、すべてが申し込みなしで使えるわけではありません。設計前に提供段階を確認してください。
| 方式 | 呼び出し方 | 提供段階(2026-11-01) |
|---|---|---|
| SMS・Voice・WhatsApp・Email | channel指定 | 一般提供 |
| Silent Network Auth | channel=sna | ベータ・9か国・要申請 |
| 自動選択 | channel=auto | Pilot・要営業連絡 |
| Passkeys | Passkeys用API | Private Beta |
| Push・TOTP | Factor/Challenge API | 一般提供 |
SNAの概要ページは提供国を「米国、カナダ、英国、ドイツ、フランス、スペインなど9か国」と書き、キャリア登録の審査に2〜4週間かかるとしています。日本が対象かは名前が挙がっていないため、営業への確認が前提です。autoはSNAを試してSMSへフォールバックする設定なので、SNAが使えない地域では実質的にSMSと同じになります。Verify PasskeysはPrivate BetaでSLAがありません。パスキーを本番の主要な認証にするなら、現時点では自前のWebAuthn実装か認証基盤のパスキー機能を選ぶほうが確実です。パスキーの仕組みはパスキー認証とは?仕組み・パスワードとの違い・企業の導入判断を解説で整理しています。
Verify APIの実装手順(Node.jsとPython)
Verify Serviceの作成とコード長の設定
Verify Serviceは認証の設定単位で、コード長やテンプレートがこの単位で決まります。送信用の電話番号を自分で購入する必要はありません。code_lengthは4〜10の整数で指定できます。4桁は総当たりに弱いため、下の例のように6桁以上を明示します。
import os
from twilio.rest import Client
client = Client(os.environ["TWILIO_ACCOUNT_SID"], os.environ["TWILIO_AUTH_TOKEN"])
service = client.verify.v2.services.create(
friendly_name="MyApp Login",
code_length=6,
do_not_share_warning_enabled=True, # 「コードを他人に教えない」旨を本文に追加
lookup_enabled=True, # skip_sms_to_landlines の前提条件
skip_sms_to_landlines=True,
)
print(service.sid) # VA で始まる Service SID
skip_sms_to_landlinesは単独では効かず、Service APIのリファレンスに「Requires lookup_enabled」と明記されています。名前空間はclient.verify.v2.servicesです。twilio-python 9.12.0(2026年10月12日公開)で旧来のclient.verify.servicesを参照すると「services is deprecated. Use v2.services instead.」という警告が出ることを手元で確認しました。
Node.jsによるコード送信・照合の実装
サーバー側で次の2関数を用意し、登録フォームとコード入力画面から呼びます。twilio-node 6.2.0を使用し、コードは.mjsファイルとして実行します。確認済みなのはSDKの初期化とダミー認証情報によるエラー応答で、実際のSMS到達と認証成功は未検証です。
import twilio from "twilio";
const client = twilio(process.env.TWILIO_ACCOUNT_SID, process.env.TWILIO_AUTH_TOKEN);
const service = client.verify.v2.services(process.env.TWILIO_VERIFY_SERVICE_SID);
// 1. コードを送る(to は E.164 形式)
export async function sendCode(phone) {
try {
const v = await service.verifications.create({ to: phone, channel: "sms" });
return v.status; // "pending"
} catch (e) {
if (e.code === 60203) return "too_many_sends"; // 同じ番号への送信が5回を超えた
throw e;
}
}
// 2. 利用者が入力したコードを照合する
export async function checkCode(phone, code) {
try {
const r = await service.verificationChecks.create({ to: phone, code });
return r.status === "approved" ? "ok" : "retry";
} catch (e) {
if (e.code === 60202) return "too_many_checks"; // 照合が5回を超えた
if (e.status === 404) return "expired"; // 期限切れ・承認済み・上限到達で削除済み
throw e;
}
}
電話番号はE.164形式で渡します。090-1234-5678なら先頭の0を外して+819012345678です。ダミーの認証情報で実行すると、どちらの関数もHTTP 401・エラーコード20003のRestExceptionを投げました。認証情報の誤りは上の分岐に入らず、そのまま呼び出し元へ上がります。Pythonでも呼び出しは同じ形で、client.verify.v2.services(SID).verifications.create(to=..., channel="sms")とverification_checks.create(to=..., code=...)の2つです。
有効期限と試行回数の上限
Rate Limits and Timeoutsによると、コードの有効期限は既定で10分です。サポートに依頼すれば、Serviceごとに2分から24時間の範囲で変更できます。APIのパラメータでは変えられないため、短い期限が規程で決まっている業務システムは、開発初期にサポートチケットを出しておきます。
同じページには、有効期限内に再送を求めても同じコードが届くとも書かれています。「再送したら新しいコードになる」前提で画面の文言を作ると、利用者が古いSMSのコードを無効だと思い込みます。
- 送信:認証が完了しないまま5回を超えると、エラー60203(HTTP 429)
- 照合:5回を超えると、エラー60202(HTTP 429)。期限切れを待って新しい認証を始める
- 期限切れ・承認済み・照合上限のいずれかでVerification SIDは削除され、以後のVerificationCheckは404
- ステータス照会:1分60回・1時間180回・1日250回まで
公式の開発ベストプラクティスは、再送を1電話番号あたり30秒に1回に抑え、指数バックオフをかけるよう勧めています。再送ボタンのクールダウンは、そのまま通信費とSMS Pumping対策になります。
iOS・Androidアプリから使うときの構成
Auth Tokenをアプリに埋め込むと、アプリを解析されて誰でもSMSを送れる状態になります。アプリは自社APIへ電話番号とコードを送るだけにして、Verifyを呼ぶのはサーバーに限定します。入力欄の自動補完は、iOSならテキストフィールドのtextContentTypeにoneTimeCodeを、Webならautocomplete="one-time-code"を指定します。AndroidのSMS Retriever APIを使う場合は、送信時にAppHashを渡すとSMS本文の末尾にハッシュが付きます。
日本向けの料金:SMS・音声の1件あたり実費
成功課金0.05ドルとチャネル料金の二階建て
Verifyの料金ページは、各チャネルを「認証成功ごとに0.05ドル+チャネルごとの料金」で示しています。SMSや音声のコード認証では、承認されなければ0.05ドルはかかりませんが、SMSの送信料は認証が失敗しても発生します。Pushだけは拒否されたときにも認証料金がかかります。
| チャネル | 認証料金 | チャネル料金 |
|---|---|---|
| SMS | 0.05ドル | 米国0.0083ドル/日本は国別料金 |
| 0.05ドル | 米国0.0034ドル(料金表) | |
| Voice・Email | 0.05ドル | 料金表に記載なし |
| Push | 0.05ドル(承認・拒否時) | 認証料に含む |
| TOTP | 0.05ドル(承認時) | 認証料に含む |
| SNA | 0.05ドル | 国別(営業に照会) |
英語版の料金ページはFAQ欄でWhatsAppを「認証テンプレート1件0.0147ドル(米国)」と書いており、同じページの表(0.0034ドル)と食い違っています。WhatsAppを主軸にするなら、少量で送って請求明細の単価を確かめてください。PasskeysはFAQで「Pilot program」とされ、単価は掲載されていません。
日本宛SMSの概算料金と音声料金の確認条件
日本宛SMSの料金表では、long codeが1セグメント0.0890ドル、英数字Sender IDが0.089ドルです。成功課金と合わせて1件およそ0.139ドル、月1万件の認証成功で約1,390ドルが目安です。日本語を含むSMSは通常70文字までが1セグメントで、連結時は1セグメントあたり67文字になります。SMS送信料はセグメント数に比例しますが、認証成功料0.05ドルは倍になりません。同じ料金表には、Failedで終わったメッセージに1通0.001ドルの処理料がかかるとの注記もあります。
音声は、Verify料金表の音声欄が「検証成功ごとに0.05ドル」だけで、通話料が別途乗るかが読み取れません。日本向け音声料金は発信が固定・国内0.0746ドル/分、携帯0.1850ドル/分です。このProgrammable Voiceの単価をVerifyの音声認証にそのまま適用できるかは、掲載情報だけでは確定できません。音声を主軸にするなら、導入前に営業へ課金条件を確認し、少量の試験送信で請求明細と照合してください。CPaaS各社の日本向け単価はVonageとTwilio比較|日本の料金・提供体制・SMS制約とAPIの違い【2026年10月】で比べています。
Firebase Authenticationの電話番号認証との違い
SMSの単価だけならFirebaseのほうが安く済みます。Identity Platformの料金表では、電話番号認証・多要素認証のSMSは日本宛1通0.03ドルで、1日に送る最初の10通は課金されません。FirebaseでこのSMS認証を使うにはBlazeプランが必要です。
| 比較軸 | Twilio Verify | Firebase電話番号認証 |
|---|---|---|
| 日本宛SMS1件 | 約0.139ドル(成功時) | 0.03ドル |
| SMS以外の経路 | 音声・WhatsApp・メール | SMSのみ |
| ユーザー管理 | 持たない(自前) | Firebaseが持つ |
| SMS Pumping対策 | Fraud Guard | reCAPTCHA SMS defense連携(Preview・要設定) |
判断の分かれ目はユーザー管理をどこに置くかです。既に自前の会員データベースがあり、電話番号の確認だけを足したいならVerifyが素直です。ゼロから作るアプリで、ログインやセッションまで任せたいならFirebaseを選びます。Firebase側の導入手順はFirebase Authenticationとは?料金・認証方式・導入手順とMFA・IDトークン検証【2026年10月】にまとめています。
日本の携帯にOTPが届かない原因と送信元の設定
国際ゲートウェイと国内ゲートウェイの違い
TwilioのJapan SMS Guidelinesは、日本向けSMSに国際と国内の2つのゲートウェイがあると説明しています。国際ゲートウェイは登録不要で、long codeと英数字Sender IDのどちらも使えます。国内ゲートウェイは登録が必須で、long codeかshort codeで配信され、専有か共有の送信元を選べます。利用には営業への連絡が必要で、送信元の登録には5週間かかると書かれています。何もしなければ国際ゲートウェイ経由になる、と考えておくのが安全です。
受信側の拒否設定と本文の制約
国際ゲートウェイ経由のSMSが届かない代表的な原因は、受信者側のキャリア設定です。ドコモのSMS拒否設定には「国際SMS拒否」があり、設定すると海外事業者の利用者から送られたSMSは届きません。ソフトバンクにも海外電話番号メールの拒否があり、初期状態は「受け取る」ですが、利用者が自分で拒否に切り替えられます。このほか、ガイドラインと料金まわりには次の制約があります。
- 本文に電話番号を含めることは不可(ガイドラインに明記)
- KDDI網宛で5セグメントを超えるSMSは配信が遅れることがある
- SMS機能のないデータ専用SIMには届かない
- 禁止業種(金融の貸付・ギャンブル・アダルト・政治・宗教など)の内容は送れない
届かないときの切り分け手順
まず配信状態を取ります。Verify EventsはEvent Streamsを通じて認証とメッセージの状態をWebhookやAmazon Kinesisへ送る仕組みで、SMS・WhatsApp・RCS・Voiceに対応しています。メッセージ状態はsent、delivered、read、undelivered、failedの5種類で、readはSMSでは返りません。deliveredなのに届かないと言われたら端末側(迷惑SMSフォルダ・フィルタ)、undeliveredやfailedなら拒否設定や番号の誤りを疑います。利用者に案内する画面には「SMSが届かない場合は音声通話で受け取る」ボタンを置き、channel="call"で再送できるようにしておくと、問い合わせが減ります。
Webhookの署名検証(X-Twilio-Signature)
署名の計算規則とJSONボディの扱い
Verify Eventsや音声の着信処理など、Twilioからリクエストを受ける公開エンドポイントはX-Twilio-Signatureで検証します。Webhookのセキュリティによれば、署名はAuth Tokenを鍵にしたHMAC-SHA1です。対象はクエリ文字列まで含む完全なURLに、POSTパラメータを名前順に並べて名前と値を連結したものを足した文字列で、結果をBase64にします。Content-Typeがapplication/jsonのときは、本文のSHA-256を16進表記したbodySHA256がURLのクエリに付きます。
twilio-python 9.12.0で次の4点を確かめました。正しいURLとフォームなら一致し、URLのスキームをhttpに変えると不一致、末尾にスラッシュを足しても不一致でした。JSONは本文の文字列を渡せば一致し、同じ本文をdictにして渡すと不一致、本文を1か所書き換えると不一致です。twilio-node 6.2.0でもJSONはvalidateRequestWithBodyを使うと同じ結果になりました。
Flaskでの実装とプロキシ配下で403になる原因
import os
from functools import wraps
from urllib.parse import urlsplit, urlunsplit
from flask import Flask, request, abort
from twilio.request_validator import RequestValidator
app = Flask(__name__)
validator = RequestValidator(os.environ["TWILIO_AUTH_TOKEN"])
def validate_twilio(f):
@wraps(f)
def wrapper(*args, **kwargs):
signature = request.headers.get("X-Twilio-Signature", "")
# 信頼するプロキシが外部由来の同名ヘッダーを除去・上書きする構成に限定
proto = request.headers.get("X-Forwarded-Proto", request.scheme)
url = urlunsplit((proto,) + tuple(urlsplit(request.url))[1:])
# JSON は本文の文字列をそのまま渡す(dict にすると bodySHA256 を照合しない)
params = request.get_data(as_text=True) if request.is_json else request.form
if not validator.validate(url, params, signature):
abort(403)
return f(*args, **kwargs)
return wrapper
@app.route("/twilio/status", methods=["POST"])
@validate_twilio
def status():
return "", 204
Flask 3.1のテストクライアントで、X-Forwarded-Proto: https付きのリクエストは204、同じ署名でヘッダーを外すと403になりました。TLSをロードバランサやngrok・Cloud Runで終端する構成では、アプリに届くURLがhttp://になるため、スキームを組み直さないと署名が必ず食い違います。FlaskのProxyFixを挟んでいる場合はrequest.urlが既にhttpsなので、二重に補正しないでください。それでも403が続くときは、Twilioのコンソールに登録したURLと受信したURLをログに出し、末尾のスラッシュやクエリの違いを突き合わせます。Webhookの仕組み自体はIncoming Webhookとは?Slack・Discord・Teams・Google Chatの使い方と違い【2026年版】で解説しています。
SMS Pumping対策:Fraud GuardとRiskCheck
SMS Pumpingは、高額な着信料を得られる番号帯を登録フォームへ大量に入力し、認証SMSを送らせて通信費を吸い上げる手口です。被害は侵入ではなく請求額として現れるため、月次の明細まで気づきにくいのが厄介です。
Fraud Guardのドキュメントでは、コンソールでVerify Serviceを作成するときに有効・無効を選び、既存のServiceはSMSタブの保護レベルで変更します。レベルはBasic・Standard・Maxの3段階で、既定はStandardです。保護対象はSMSだけで、音声チャネルは対象外と明記されています。音声を併用するなら、送信先の国を絞るGeo Permissionsと、自社側の試行回数制限で補います。海外の電話番号を認証する必要がなければ、Geo Permissionsで送信先を日本に限定します。国内番号への大量送信は防げないため、試行回数制限も併用します。
コード送信時のRiskCheckパラメータ(SMSのみ)は、Fraud GuardやGeo Permissionsをリクエスト単位で切り替えます。既定はenableで、社内テスト番号など保護を外したい送信だけdisableにできます。「リスクの高いリクエストだけ追加認証を求める」ような判定そのものはVerifyにはないため、リスクベース認証はログイン側でIPや端末の変化を見て、Verifyを呼ぶかどうかを決める形で組みます。
Verifyを採用すべきでない条件と電話番号ログインの設計
自作や他方式を選ぶべきケース
TOTPだけが要件なら、Verifyを使う理由は薄くなります。認証アプリの6桁コードはRFC 6238で仕様が公開されており、Pythonのpyotpなどで完結します。アルゴリズムと実装はOTPとは?HOTP/TOTPの生成アルゴリズムと実装・SMS OTPの限界を実装視点で解説で扱っています。
月10万件規模の認証成功があり、社内にSMS配信の運用経験があるなら、成功課金だけで月5,000ドルに達するため内製と比べる価値があります。内製との比較では、成功課金だけでなく、不正送信対策・再送制御の開発費と継続運用費を見積もります。月10万件は比較例であり、損益分岐点ではありません。日本の国内ゲートウェイでの配信が必須(国際SMSを拒否する利用者を取りこぼせない)なら、Verifyの標準の流れに乗せる前に営業と国内ゲートウェイの条件を詰める必要があります。
電話番号をログインIDにする設計の注意
電話番号へのコード送信だけでログインを完了させる設計では、Verifyが保証するのはコードを受け取れたことまでです。SIMの乗っ取りや転送設定を突破されると成立しないため、送金や個人情報の変更など被害の大きい操作には、パスキーなど端末に結び付いた要素を重ねます。番号の再割り当てで別人に届く可能性もあるので、長期間ログインのないアカウントは電話番号だけで復旧させず、メールなど別の連絡先を最初の登録時に確認しておきます。会員データベースと認証フローをまとめて設計する場合は、会員管理システム開発のように認証と会員管理を一体で組む進め方もあります。
よくある質問
Twilio Verifyとは何ですか?
電話番号やメールアドレスにワンタイムコードを送り、入力されたコードを照合して本人確認を行うTwilioのAPIです。コードの生成・保管・有効期限の管理・不正送信の検知をTwilioが受け持つため、アプリケーションは送信と照合の2つのAPIでコード認証を組み込めます。二要素認証には、パスワードなど別種の認証要素との組み合わせが必要です。指定できるチャネルはSMS、音声通話、メール、WhatsApp、Silent Network Auth、自動選択の6つです。
Twilio Verifyの料金はいくらですか?
認証成功ごとに0.05ドルと、チャネルごとの料金がかかります。日本宛SMSはlong codeで1セグメント0.0890ドルなので、1回で成功すれば1件およそ0.139ドルです。SMS送信料は認証が失敗しても発生し、再送すれば通数分かかります。音声は料金表から通話料の扱いが読み取れないため、試験送信の請求明細で確認してください。
認証コードの有効期限は変更できますか?
できます。既定は10分で、Twilioのサポートに依頼するとServiceごとに2分から24時間の範囲で変更できます。APIのパラメータでは変えられません。有効期限内に再送を求めると同じコードが届き、期限切れ後にコードを照合すると404が返ります。
日本の携帯にSMSが届かないのはなぜですか?
原因の一つは受信者側のキャリア設定です。通常の国際ゲートウェイ経由のSMSは、ドコモの「国際SMS拒否」やソフトバンクの「海外電話番号メールの拒否」を設定した端末には届きません。本文に電話番号を入れている、データ専用SIMである、といった原因もあります。Verify Eventsで配信状態を確認し、届かない利用者には音声通話での再送を案内してください。
Firebase Authenticationとどちらを選ぶべきですか?
SMSの単価だけならFirebase(日本宛1通0.03ドル・1日10通まで無料)が安く、ログインやユーザー管理まで任せたい新規アプリに向きます。既存の会員データベースに電話番号確認だけを足したい場合や、音声・WhatsApp・メールへの切り替えとSMS Pumping対策を一緒に使いたい場合はTwilio Verifyが向きます。