From twilio-developer-kit
Send and verify one-time passcodes (OTPs) via Twilio Verify over SMS, RCS, voice, email, or WhatsApp. Covers creating a Verify Service, sending tokens, checking submitted codes, automatic WhatsApp-to-SMS fallback, and service configuration. TOTP is supported via the Factors API (a separate family from channel-based OTP). Use this skill to add phone or email verification or two-factor authentication to any application.
npx claudepluginhub twilio/ai --plugin twilio-developer-kitThis skill uses the workspace's default tool permissions.
Use **Twilio Verify** to manage the full OTP lifecycle: code generation, delivery, expiry, rate limiting, and Fraud Guard protection. Use the **Programmable Messaging API** to build your own OTP message infrastructure and access features such as SMS Pumping Protection.
Guides Next.js Cache Components and Partial Prerendering (PPR): 'use cache' directives, cacheLife(), cacheTag(), revalidateTag() for caching, invalidation, static/dynamic optimization. Auto-activates on cacheComponents: true.
Guides building MCP servers enabling LLMs to interact with external services via tools. Covers best practices, TypeScript/Node (MCP SDK), Python (FastMCP).
Share bugs, ideas, or general feedback.
Use Twilio Verify to manage the full OTP lifecycle: code generation, delivery, expiry, rate limiting, and Fraud Guard protection. Use the Programmable Messaging API to build your own OTP message infrastructure and access features such as SMS Pumping Protection.
| Twilio Verify | Programmable Messaging API | |
|---|---|---|
| Code generation + expiry | Built-in (10min default, configurable). Also supports custom codes. | Build yourself |
| Rate limiting | Built-in (per-phone, per-service) | Build yourself |
| Fraud protection | Fraud Guard (geo-permissions, rate anomaly) | SMS Pumping Protection |
| A2P registration | Exempt — no 10DLC needed | Required — must register campaign |
| Multi-channel | One API, change channel param (SMS/Voice/Email/WhatsApp/RCS) | Separate integration per channel |
| Cost | Per confirmed verification + channel fee | Per-message pricing + build cost |
| Delivery confirmation | Yes — via List Attempts or Events API | Yes (via StatusCallback) |
When Programmable Messaging is justified: You need full control over message content, custom delivery logic, or SMS Pumping Protection features. For standard OTP/2FA flows, use Verify.
Verify supports SMS, voice, email, WhatsApp, and RCS — only the channel parameter changes per delivery method. TOTP (authenticator apps) is supported via the Verify Factors API, a separate implementation from channel-based OTP.
twilio-account-setup
— Verify requires no separate product activation — just create a Service belowTWILIO_ACCOUNT_SIDTWILIO_AUTH_TOKENVERIFY_SERVICE_SID (created in Quickstart step 1)
— See twilio-iam-auth-setup for credential setup and best practicespip install twilio / npm install twiliotwilio-whatsapp-manage-sendersStep 1 — Create a Verify Service (one-time)
Python
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="My App Verification"
)
print(service.sid) # VAxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx — save as VERIFY_SERVICE_SID
Node.js
const twilio = require("twilio");
const client = twilio(process.env.TWILIO_ACCOUNT_SID, process.env.TWILIO_AUTH_TOKEN);
const service = await client.verify.v2.services.create({
friendlyName: "My App Verification",
});
console.log(service.sid); // VAxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Store the Service SID — reuse it for all verifications, do not recreate it each time.
Step 2 — Send a verification token
Python
verification = client.verify.v2 \
.services(os.environ["VERIFY_SERVICE_SID"]) \
.verifications \
.create(to="+15558675310", channel="sms")
print(verification.status) # pending
Node.js
const verification = await client.verify.v2
.services(process.env.VERIFY_SERVICE_SID)
.verifications.create({ to: "+15558675310", channel: "sms" });
console.log(verification.status); // pending
Step 3 — Check the submitted code
Python
check = client.verify.v2 \
.services(os.environ["VERIFY_SERVICE_SID"]) \
.verification_checks \
.create(to="+15558675310", code="123456")
if check.status == "approved":
print("Verified!")
else:
print("Invalid or expired code")
Node.js
const check = await client.verify.v2
.services(process.env.VERIFY_SERVICE_SID)
.verificationChecks.create({ to: "+15558675310", code: "123456" });
if (check.status === "approved") {
console.log("Verified!");
} else {
console.log("Invalid or expired code");
}
| Channel | channel value | Notes |
|---|---|---|
| SMS | sms | Default, widest coverage |
| Voice call | voice | Reads code aloud |
email | Use email address in to | |
whatsapp | Requires own WhatsApp sender (see below) | |
| RCS | rcs | Rich messaging, Android devices |
TOTP (authenticator apps): Supported via the Verify Factors API — a separate implementation from channel-based OTP. See Verify TOTP docs.
Change channel to "whatsapp" — the send/check flow is identical to SMS.
Requires: A registered production WhatsApp sender. As of March 2024, Twilio no longer provides a shared sender for Verify. See
twilio-whatsapp-manage-senders.
Python
verification = client.verify.v2 \
.services(os.environ["VERIFY_SERVICE_SID"]) \
.verifications \
.create(to="+15558675310", channel="whatsapp")
Node.js
const verification = await client.verify.v2
.services(process.env.VERIFY_SERVICE_SID)
.verifications.create({ to: "+15558675310", channel: "whatsapp" });
Python
verification = client.verify.v2 \
.services(os.environ["VERIFY_SERVICE_SID"]) \
.verifications \
.create(
to="+15558675310",
channel="whatsapp",
channel_configuration={
"whatsapp": {"enabled": True},
"sms": {"enabled": True} # falls back to SMS if WhatsApp undelivered
}
)
Node.js
const verification = await client.verify.v2
.services(process.env.VERIFY_SERVICE_SID)
.verifications.create({
to: "+15558675310",
channel: "whatsapp",
channelConfiguration: {
whatsapp: { enabled: true },
sms: { enabled: true },
},
});
With fallback enabled, your UI can say "a verification code was sent" without specifying the channel.
Python
service = client.verify.v2.services.create(
friendly_name="My App",
code_length=6, # 4–10 digits (default: 6)
lookup_enabled=True, # Validate number before sending
do_force_check_once=True, # Code can only be checked once
ttl=600, # Code expiry in seconds (default: 600)
)
Node.js
const service = await client.verify.v2.services.create({
friendlyName: "My App",
codeLength: 6,
lookupEnabled: true,
doForceCheckOnce: true,
ttl: 600,
});
| Status | Meaning |
|---|---|
approved | Code is correct |
pending | Code is wrong or not yet submitted |
expired | Code has expired (default TTL: 10 minutes) |
canceled | Verification was canceled |
Primary debugging tool: Console > Verify > Logs (per-Service). Shows every verification attempt, delivery status, channel used, and error codes. Check here first before writing custom monitoring code.
| Code | Meaning | Fix |
|---|---|---|
| 60200 | Invalid parameter | Check to format and channel value |
| 60202 | Max send attempts reached | Wait before retrying |
| 60203 | Max check attempts reached | Issue a new verification |
| 60212 | Service not found | Verify VERIFY_SERVICE_SID is correct |
| 60410 | Geo-permission not enabled | Enable country in Console |
Built-in protections (no custom code needed):
lookup_enabled=True)International OTP traffic warning: International numbers are high-risk for SMS pumping — fraudsters trigger OTPs to premium-rate destinations to generate revenue. Verify's Fraud Guard handles this automatically when enabled. If you're building custom OTP with Programmable Messaging instead, enable SMS Pumping Protection on your Messaging Service (see twilio-messaging-services). Always restrict geo-permissions to only countries where you have real users.
channel_configuration for WhatsApp→SMS only.verification_checks. Rate-limited: 60/min, 180/hr, 250/day.auto channel not universally available — Returns error 60200 on accounts without Fraud Guard enabled.channel: 'email' without a configured Mailer returns error 60217.status: "pending", not an error. You must check status === "approved" explicitly.approved, subsequent checks return 404.twilio-whatsapp-manage-senderstwilio-lookup-phone-intelligencetwilio-iam-auth-setup