plugins/twilio-developer-kit/skills/twilio-verify-send-otp/SKILL.md
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 skillsauth add openai/plugins twilio-verify-send-otpInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
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 | email | Use email address in to |
| WhatsApp | 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-setuptools
Top-level workflow skill for USD performance diagnosis and optimization. Use for slow loading, high memory, low FPS, or 'optimize my scene' requests; delegates auth/runtime setup to Phase 0 owners.
data-ai
Use when the user mentions MagicPath, designs, UI components, themes, canvas selections, or repo-to-canvas UI work; run magicpath-ai to search, inspect, install, or author components.
documentation
Use as the top-level router for Omniverse Realtime Viewer USD app requests and focused viewer reference documents.
tools
Turn Notion specs into implementation plans, tasks, and progress tracking; use when implementing PRDs/feature specs and creating Notion plans + tasks from them.