Skip to content

OTP Verification System (Twilio Verify)

This project demonstrates how to build a scalable OTP (One-Time Password) generation and verification system using Twilio Verify.

It follows a modular structure in Go and an equivalent implementation in Python using Flask.


Prerequisites (Twilio Setup)

Before running the project:

  1. Create a Twilio account.
  2. Get your Account SID and Auth Token from the Console.
  3. Navigate to Verify → Services and create a new service.
  4. Copy the generated Service SID.

Golang Implementation

This implementation uses:

  • gin framework
  • Official twilio-go SDK

Project Structure

/cmd/main.go
/cmd/.env
/api/config.go
/api/handler.go
/api/helper.go
/api/route.go
/api/service.go
/data/model.go

Environment Variables (cmd/.env)

TWILIO_ACCOUNT_SID=your_sid
TWILIO_AUTH_TOKEN=your_token
TWILIO_SERVICES_ID=your_verify_service_sid

Data Models (data/model.go)

package data

type OTPData struct {
    PhoneNumber string `json:"phone_number" validate:"required"`
}

type VerifyData struct {
    User string `json:"user" validate:"required"`
    Code string `json:"code" validate:"required"`
}

Twilio Service Layer (api/service.go)

package api

import (
    "github.com/twilio/twilio-go"
    verify "github.com/twilio/twilio-go/rest/verify/v2"
)

func twilioClient() *twilio.RestClient {
    return twilio.NewRestClientWithParams(twilio.ClientParams{
        Username: envAccountSID(),
        Password: envAuthToken(),
    })
}

func TwilioSendOTP(phoneNumber string) (*verify.VerifyV2Verification, error) {
    client := twilioClient()
    params := &verify.CreateVerificationParams{}
    params.SetTo(phoneNumber)
    params.SetChannel("sms")

    resp, err := client.VerifyV2.CreateVerification(envServicesID(), params)
    return resp, err
}

func TwilioVerifyOTP(phoneNumber string, code string) (*verify.VerifyV2VerificationCheck, error) {
    client := twilioClient()
    params := &verify.CreateVerificationCheckParams{}
    params.SetTo(phoneNumber)
    params.SetCode(code)

    resp, err := client.VerifyV2.CreateVerificationCheck(envServicesID(), params)
    return resp, err
}

Main Entry (cmd/main.go)

package main

import (
    "github.com/gin-gonic/gin"
    "your-module-path/api"
)

func main() {
    router := gin.Default()

    app := api.Config{Router: router}
    app.Routes()

    router.Run(":8000")
}

Python Implementation

This version uses:

  • Flask
  • Twilio Python Helper Library
  • python-dotenv

Installation

pip install flask twilio python-dotenv

app.py

import os
from flask import Flask, request, jsonify
from dotenv import load_dotenv
from twilio.rest import Client
from twilio.base.exceptions import TwilioRestException

load_dotenv()

app = Flask(__name__)

ACCOUNT_SID = os.getenv("TWILIO_ACCOUNT_SID")
AUTH_TOKEN = os.getenv("TWILIO_AUTH_TOKEN")
VERIFY_SERVICE_SID = os.getenv("TWILIO_SERVICES_ID")

client = Client(ACCOUNT_SID, AUTH_TOKEN)

@app.route('/otp', methods=['POST'])
def send_otp():
    data = request.get_json()
    phone_number = data.get('phone_number')

    try:
        verification = client.verify.v2.services(VERIFY_SERVICE_SID) \
            .verifications \
            .create(to=phone_number, channel='sms')

        return jsonify({
            "status": 200,
            "message": "OTP sent successfully",
            "data": verification.sid
        }), 200

    except TwilioRestException as e:
        return jsonify({"status": 500, "message": str(e)}), 500


@app.route('/verifyOTP', methods=['POST'])
def verify_otp():
    data = request.get_json()
    phone_number = data.get('user')
    code = data.get('code')

    try:
        verification_check = client.verify.v2.services(VERIFY_SERVICE_SID) \
            .verification_checks \
            .create(to=phone_number, code=code)

        if verification_check.status == "approved":
            return jsonify({
                "status": 200,
                "message": "OTP verified successfully"
            }), 200
        else:
            return jsonify({"status": 400, "message": "Invalid OTP"}), 400

    except TwilioRestException as e:
        return jsonify({"status": 500, "message": str(e)}), 500


if __name__ == '__main__':
    app.run(port=8000)

API Testing

Send OTP

curl -X POST http://localhost:8000/otp \
-H "Content-Type: application/json" \
-d '{"phone_number": "+1234567890"}'

Verify OTP

curl -X POST http://localhost:8000/verifyOTP \
-H "Content-Type: application/json" \
-d '{"user": "+1234567890", "code": "123456"}'

Response Flow

Successful OTP Send

{
  "status": 200,
  "message": "OTP sent successfully",
  "data": "verification_sid"
}

Successful Verification

{
  "status": 200,
  "message": "OTP verified successfully"
}

Invalid OTP

{
  "status": 400,
  "message": "Invalid OTP"
}