Skip to content

Twilio Verify — Phone Number Verification with One-Time Passcodes

DodaTech Updated 2026-06-28 4 min read

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

  1. What is a Verify Service SID?
  2. How do you send a verification code?
  3. What status indicates a valid code?
  4. How many attempts are allowed per code?
  5. How long is a verification code valid?

Answers

  1. 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

What is Twilio Verify?

A service for phone number verification using one-time passcodes via SMS, voice, or email.

How many channels does Twilio Verify support?

SMS, voice (phone call), email, and TOTP authenticator apps.

How long is a verification code valid?

Default 10 minutes, configurable in the Verify Service settings.

How many verification attempts are allowed?

Default 5 attempts per code. Additional attempts require a new code.

Does Twilio Verify include fraud detection?

Yes, it includes built-in fraud detection for suspicious verification patterns.

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