Twilio Verify — Phone Number Verification with One-Time Passcodes
In this tutorial, you will learn about Twilio Verify. We cover key concepts, practical examples, and best practices to help you master this topic.
Twilio Verify is a purpose-built service for phone number verification that sends one-time passcodes (OTP) via SMS, voice, or email, with built-in Rate Limiting, fraud detection, and automatic code expiry.
What You'll Learn
- How to set up Twilio Verify Service
- How to send verification codes
- How to verify codes submitted by users
Why It Matters
Building verification in-house is complex: you must generate codes, send them reliably, handle expiry, prevent brute-force attacks, and manage rate limits. Twilio Verify handles all of this with configurable code length, TTL, and channel selection.
Real-World Use
DodaTech uses Twilio Verify for: new account phone verification, login two-factor authentication, password reset confirmation, and sensitive action confirmation. The Verify service handles rate limiting (max 5 attempts per code) and code expiry (10-minute TTL).
from twilio.rest import Client
TWILIO_ACCOUNT_SID = 'your_account_sid'
TWILIO_AUTH_TOKEN = 'your_auth_token'
VERIFY_SERVICE_SID = 'VAxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
client = Client(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN)
def send_verification_code(phone_number, channel='sms'):
"""Send verification code to a phone number"""
verification = client.verify \
.services(VERIFY_SERVICE_SID) \
.verifications \
.create(to=phone_number, channel=channel)
print(f"Verification sent: {verification.sid}")
print(f"Status: {verification.status}")
return verification.sid
def check_verification_code(phone_number, code):
"""Check if the submitted code is valid"""
verification_check = client.verify \
.services(VERIFY_SERVICE_SID) \
.verification_checks \
.create(to=phone_number, code=code)
if verification_check.status == 'approved':
print("Code is valid!")
return True
else:
print(f"Code invalid. Status: {verification_check.status}")
return False
Complete Verification Flow
from flask import Flask, request, jsonify, session
app = Flask(__name__)
@app.route('/api/verify/send', methods=['POST'])
def send_verification():
phone = request.json.get('phone')
channel = request.json.get('channel', 'sms')
if not phone:
return jsonify({'error': 'Phone number required'}), 400
try:
verification = client.verify \
.services(VERIFY_SERVICE_SID) \
.verifications \
.create(to=phone, channel=channel)
return jsonify({
'status': 'sent',
'sid': verification.sid,
'channel': channel
})
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/api/verify/check', methods=['POST'])
def check_verification():
phone = request.json.get('phone')
code = request.json.get('code')
if not phone or not code:
return jsonify({'error': 'Phone and code required'}), 400
try:
verification_check = client.verify \
.services(VERIFY_SERVICE_SID) \
.verification_checks \
.create(to=phone, code=code)
if verification_check.status == 'approved':
session['phone_verified'] = True
session['verified_phone'] = phone
return jsonify({'status': 'approved'})
else:
attempts = verification_check.status
return jsonify({
'status': 'failed',
'message': 'Invalid code',
'attempts_remaining': 5 - int(getattr(verification_check, 'attempts', 0))
}), 400
except Exception as e:
return jsonify({'error': str(e)}), 500
Rate Limiting and Fraud
def check_rate_limits(phone_number):
"""Check rate limits before sending verification"""
rate_limit = client.verify \
.services(VERIFY_SERVICE_SID) \
.rate_limits \
.list()
# Default limits: max 5 attempts per code, max 10 sends per hour per number
return {
'can_send': True, # Twilio handles rate limiting automatically
'max_attempts_per_code': 5,
'code_ttl_seconds': 600 # 10 minutes
}
def check_fraud(phone_number):
"""Use Twilio's fraud detection"""
try:
# Twilio Verify includes built-in fraud detection
# It flags suspicious patterns automatically
return {'fraud_risk': 'low'}
except Exception:
return {'fraud_risk': 'unknown'}
Common Mistakes
1. Not Validating Phone Numbers Before Sending
Send verification to invalid numbers causes API errors. Validate with Twilio Lookup first.
2. Allowing Unlimited Verification Attempts
Twilio Verify limits to 5 attempts per code. Implement your own rate limiting for sending new codes.
3. Using SMS as the Only Channel
SMS has delivery delays and failures. Offer voice call as a fallback channel for verification.
4. Not Checking verification_check.status Accurately
Only approved status means the code is valid. Other statuses indicate pending, expired, or max attempts reached.
5. Storing Verification Codes Client-Side
Never store or expose verification codes on the client. Always verify server-side.
Practice Questions
- What is a Verify Service SID?
- How do you send a verification code?
- What status indicates a valid code?
- How many attempts are allowed per code?
- How long is a verification code valid?
Answers
- The unique identifier for your Twilio Verify service. 2. Call client.verify.services(sid).verifications.create(). 3.
approved. 4. 5 attempts. 5. 10 minutes (configurable).
Challenge
Build a complete phone verification flow: send verification code via SMS with voice fallback, check the submitted code, implement rate limiting (max 3 sends per hour per number), handle code expiry, and mark the phone as verified in the user's account.
FAQ
Mini Project
Build a user signup flow with phone verification: registration form collects phone number, sends verification via SMS, user enters code, phone is verified. Include rate limiting, channel fallback (SMS to voice), fraud detection, and verified badge on user profile.
What's Next
- Learn about Twilio WhatsApp API for business messaging
- Explore Twilio Voice API for making and receiving calls
- Continue to Twilio Messaging Service for scaling SMS operations
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro