Oni Gbenga 👨🏽‍🎓CS 👨🏽‍💻Software & DevOps Engineer ☁️ AWS Consultant | 2x AWS Certified. Laravel,NodeJS,Go,React(Native),Docker,k8s

Phone Verification via Voice with Laravel, Twilio, S3 and AWS Polly

2 min read

Twilio offers an array of products and solutions that enable you to engage your customers.
One of which is Programmable Voice that allows you to make and
receive calls, as well as monitor calls, using the Voice
In this tutorial, you will learn how to use Twilio’s Programmable Voice API to implement a phone number
verification system that places a voice call to the user’s phone from your PHP Laravel Application. By the end
of the tutorial, you will have developed a PHP Laravel Application with an authentication system that can verify
a user’s phone number, by placing a call through to it.


  1. PHP environment running 7.3+
  2. Laravel
  3. A Twilio Account
  4. An AWS Account and S3 bucket

Setup Laravel and Twilio PHP SDK

Get Composer

Firstly, we need to download Composer, a PHP package manager. We will use Composer to install Laravel and other
dependencies. If you don’t already have it installed, you can download it here.

Install Laravel

We will be building a Laravel application in order to interact with the Twilio API. Create a fresh Laravel
application using composer by running the following command.

$ composer create-project --prefer-dist laravel/laravel myLaravelTwilioApp

Start Laravel

Now that we have Laravel installed, start Laravel on the local server:

$ cd myLaravelTwilioApp && php artisan serve

The Laravel Application should now be running on http://localhost:8000. Check it out in
your browser to see!

Download Twilio PHP SDK

Now that we have our Laravel app setup, we need a way to include the Twilio API within our project. The Twilio PHP SDK will help us interact with the
Twilio API from within our Laravel App. We will use Composer — the recommended method — to install the SDK.

$ composer require twilio/sdk

The Twilio PHP SDK is now ready for use. You can verify this by looking inside of your vendor folder at

Setup Twilio

If you haven’t already,  sign up for Twilio and get a voice-enabled phone number.
NOTE: If you have already signed up for Twilio and you have a Twilio phone number
that’s voice-enabled, you can skip this step and the following, “Get a Twilio Phone Number”.

Navigate to in your browser to get started with
a free Twilio account. Fill out the form accordingly and click “Get Started”.

In order to verify your identity, and to also make calls to it from Twilio, you will need to verify your
personal phone number. Provide your number and click “Verify”.

You should receive a code on your phone. Provide the code and proceed. Now that you’ve successfully verified
your phone number, you will be prompted to create a project. Select the “Learn and Explore” option, name
your project and click “Skip Remaining Steps”.
On successful creation of your project, you will be taken to your project dashboard.

Get a Twilio Phone Number

If you don’t have a Twilio phone number that’s voice-enabled, you need to purchase one.
Navigate to the “Get a Number” page (
and check that you want a  voice capable number in your search.

You will be provided with a list of voice-enabled Twilio phone numbers available for purchase.

Make a choice from the available numbers list by clicking “Buy”. It will then be attached to your account.
You have now successfully set up a Twilio account and also purchased a Twilio voice-enabled phone number.

Make a Test Call to Your Phone

To confirm our Voice API is ready for use, we will make a test call to the phone number we initially
verified. Create a Verification Controller in your Laravel Application by running the following command
in your terminal:

$ php artisan make:controller VerifyController

Now edit the VerifyController::call() function to look like this:

namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Twilio\Rest\Client;

class VerifyController extends Controller
  public function call(){
   // Your Account SID and Auth Token from
   $account_sid = env('TWILIO_ACCOUNT_SID');
   $auth_token = env('TWILIO_AUTH_TOKEN');
   // In production, these should be environment variables. E.g.:
   // $auth_token = $_ENV["TWILIO_ACCOUNT_SID"]
   // A Twilio number you own with Voice capabilities
   $twilio_number = env('TWILIO PHONE NUMBER');

   // Where to make a voice call (your cell phone?)
   $to_number = ('YOUR VERIFIED PHONE NUMBER');
   $client = new Client($account_sid, $auth_token);

           "url" => ""

NOTE: Be sure to replace your Twilio credentials with the
Replace the <YOUR VERIFIED PHONE NUMBER> placeholder with your verified phone number.
Create a Route /call with the GET access methodThe /call route routes the GET
request to the VerifyController::call()that makes a test call to the number value assigned to
the $twilio_number variable.
Route::get('/call', 'VerifyController@call');

Now navigate to http://localhost:8000/call in your browser. You should get a
call on your previously verified personal number.

Create Verification Middleware

We need to create a middleware that will validate, upon login, that a user’s phone number is verified. If
valid, the request is processed, if not, the user will be redirected to a page to verify their phone number.

Create Laravel Authentication Scaffold

First, we need to create an authentication scaffold for our Laravel App. Make sure you have provided Laravel
the right database credentials for your local database, through the .env file in your project

$ php artisan make:auth

Before migrating the database files, we need to update the default Laravel users table columns to include
additional columns for phone (user’s phone number) and phone_verified_at (the time
the phone number was verified). We will do this by editing the
database/migrations/2014_10_12_000000_create_users_table.php file and adding the following
to the column declarations:

Migrate the database files to create our users table with the updated columns:
Be sure to update the database credentials in the .env file before running the migration
$ php artisan migrate

Update the fillable property of the User model app/User.php to look something like this:
protected $fillable = [
   'name', 'email', 'password', 'phone', 'phone_verified_at'

The above update adds the phone and phone_verified_at properties to the fillable
properties of the User model. That is, they can be assigned values by Laravel.
Now update the RegisterController in the App/Http/Controllers/Auth folder. This
ensures the phone data is stored in the User table, in the database.
protected function create(array $data)
  return User::create([
      'name' => $data['name'],
      'email' => $data['email'],
      'phone' => $data['phone'],
      'password' => Hash::make($data['password']),

Update the register.blade.php view with an input element for user phone number at the
registration point:
<div class="form-group row">
   <label for="phone" class="col-md-4 col-form-label text-md-right">{{ __('Phone Number') }}label>

   <div class="col-md-6">

   <input id="phone" type="number" class="form-control{{ $errors->has('phone') ? ' is-invalid' : '' }}" name="phone" value="{{ old('phone') }}" required>

   @if ($errors->has('phone'))

       <span class="invalid-feedback" role="alert">
           <strong>{{ $errors->first('phone') }}strong>

Create Laravel Middleware

Middleware helps filter HTTP requests to the Laravel Application. We will create a middleware that checks
that the user making a certain request has their phone number verified.

$ php artisan make:middleware EnsurePhoneIsVerified

This will create middleware in the
App/Http/Middleware folder with the name EnsurePhoneIsVerified.php
Now lets register the EnsurePhoneIsVerified.php middleware in our
App/Http/kernel.php file. By listing the middleware class in the $routeMiddleware property
of the App/Http/kernel.php class, the EnsurePhoneIsVerified.php middleware will be
enabled to run during all HTTP requests to the selected routes within Laravel.

   namespace App\Http;
   use Illuminate\Foundation\Http\Kernel as HttpKernel;
   class Kernel extends HttpKernel{   
       protected $routeMiddleware = [ 
       'verified-phone' => \App\Http\Middleware\EnsurePhoneIsVerified::class,

Open the middleware EnsurePhoneIsVerified.php, it should look like this:
public function handle($request, Closure $next)
   if (is_null(\Auth::user()->phone_verified_at)) {
    return redirect()->route('verify-phone', \Auth::user()->email);
   return $next($request);

This is a BeforeMiddleware that runs before a user’s request is processed. The middleware redirects the user
to the verify-phone route if their phone number is yet to be verified.
Be sure to add $this->middleware(‘verified-phone’); to the construct method of
the HomeController in order to trigger the verified-phone middleware.
Let’s create a route to the appropriate VerifyController method that will display the verification page.
This is the page the EnsurePhoneIsVerified.php middleware will redirect
unverified users to.
Route::get('/verify-phone', 'VerifyController@index')->name('verify-phone');

The VerifyController index method should look something like this:
public function index()
   $user = \Auth::user();
   return view('auth.verify-phone-index', compact('user'));

It displays the view file in resources/views/auth/verify-index.blade.php. The
verify-index.blade.php file displays a form with a “Call me with code” button. Let’s create
this file and add the following code:
    <div class="container">
      <div class="row justify-content-center">
        <div class="col-md-8">
          <div class="card">
            @if (session('status'))
              <div class="alert alert-success">
                {{ session('status') }}
         <div class="card-header">{{ __('Verify Your Phone Number') }}div>
     <div class="card-body">
     {{ __('We will call you phone number with a verification code.') }}
       <form method="POST" action="{{ route('call-phone') }}">
          <div class="form-group row mb-0">
            <div class="col-md-8 offset-md-4">
              <button type="submit" class="btn btn-primary">
                {{ __('Call me with Code') }}

The form points to a call-phone route which points to the VerifyController@callPhone function
that places a call through to the currently logged in, unverified user’s phone number. Let’s add the
call-phone route:
Route::post('/verify-phone', 'VerifyController@callPhone')->name('call-phone');

Now create a callPhone function in the VerifyController controller:
public function callPhone(Request $request)
   $user = \Auth::user();
   $account_sid = env('TWILIO ACCOUNT SID');
   $auth_token = env('TWILIO AUTH TOKEN');
   $twilio_number = ('TWILIO PHONE NUMBER');
   $to_number = $user->phone;
   $client = new Client($account_sid, $auth_token);
           "url" => ""
   return redirect()->back();

The code above places a dummy call to the phone number attached to the user with the Twilio Voice API.

Generate MP3 Speech with verification code using AWS Polly

Now that our Laravel App can prompt unverified users to verify their phone numbers, and also place a dummy
phone call to them, we need to provide the Voice API with relevant speech containing the verification code
of the user. This is where AWS Polly comes in, an AWS
Service that turns text into life-like speech.

Install the AWS PHP SDK using composer

We will use Composer to install the AWS PHP SDK on
our Laravel Application:

$ composer require aws/aws-sdk-php-laravel

Once that’s done, register the AWS Service Provider in the providers key in config/app.php

'providers' => [
      // ...

Also find the aliases key and add:
'aliases' => [
      // ...
      'AWS' => Aws\Laravel\AwsFacade::class,

Next you need to create an IAM user with access to
AWS Polly
and S3 and also an Access Key ID and a Secret Key for that user. Once complete,
add the generated Access Key ID and Secret Key to the .env environment file for Laravel:

Generating Text Speech with AWS PollyFirst we need to add two functions to the VerifyController:
function to generate a verification codeA function to convert text to speechAdd the
generateVerificationCode() function to the VerifyController.
private function generateVerificationCode(): string
  $count = 0;
  $code = '';
  while ($count < 5) {
      $code .= mt_rand(0, 9);
  return chunk_split( $code, 1, ' ' );

Now add the convertTextToSpeech() function to the VerifyController
private function convertTextToSpeech(string $text)
  $polly = \AWS::createClient('polly');
  $result_polly = $polly->synthesizeSpeech([
      "OutputFormat" => "mp3",
      "Text" => $text,
      "TextType" => "text",
      "VoiceId" => "Amy"
  $resultData_polly = $result_polly->get("AudioStream")->getContents();
  $file_name = time()."-polly.mp3";
  $s3 = \AWS::createClient('s3');
  $result_s3 = $s3->putObject(array(
      'Bucket'     => '',
      'Key'        => $file_name,
      "ACL" => "public-read",
      "Body" => $resultData_polly,
      "ContentType" => "audio/mpeg"
  return $result_s3['ObjectURL'];

The convertTextToSpeech() function takes a string as an argument. Then it
initiates AWS Polly and creates a speech based on the text. The resulting speech is then
saved into an S3 bucket with public read access. Finally, it returns the URL of the S3 bucket containing the

Call User with Code

Now we’ll update the CallPhone function which will generate a verification code:

public function callPhone(Request $request)
  $user = \Auth::user();

  $account_sid = '';
  $auth_token = '';
  $twilio_number = "";

  $code = $this->generateVerificationCode();
  $request->session()->put('verification-code', $code);
  $message = "Hello. Your verification code is: {$code}. Goodbye.";

  $file_url = $this->convertTextToSpeech($message);
  $to_number = "".ltrim($user->phone, $user->phone[0]);
  $client = new Client($account_sid, $auth_token);
          "method" => "GET",
          "url" => $file_url
  return redirect()->back();

We have updated the callPhone() function to generate a verification code, save the code in the
user’s session, generate a message and to convert this message into a speech.
The file URL of the generated speech is then sent to Twilio Voice API when initiating a call. This is the
speech that gets played to the user.
NOTE: You need to be a paid user if you want Twilio Voice API to play any speech other
than the default one. You will also then be able to make calls to other numbers apart from yours.

Create a function to receive and verify sent code

We need to create an additional page where the user will provide the verification code read to them on the
phone. We will also create a function that takes this code, checks its authenticity and verifies the
user accordingly.
First, edit the callPhone() function to display a form, instead of redirecting back:

// return redirect()->back();
return view('auth.verify-phone-form');

Create the resources/views/auth/verify-phone-form.blade.php file. This page displays a form
where the user will provide the verification code read to them during the phone call:

         <div class="container">
           <div class="row justify-content-center">
            <div class="col-md-8">
              <div class="card">
                <div class="card-header">{{ __('Verify Your Phone Number') }}div>
                 <div class="card-body">
                   {{ __('Enter the Verification code read to you on the phone.') }}
               <form method="POST" action="{{ route('verify-code') }}">
                  <div class="form-group row">
                   <label for="code" class="col-md-4 col-form-label text-md-right">{{ __('Code') }}label>
                     <div class="col-md-6">
           <input id="code" type="number" class="form-control{{        $errors->has('code') ? ' is-invalid' : '' }}" name="code" value="" required>
                 <div class="form-group row mb-0">
                    <div class="col-md-6 offset-md-4">
                      <button type="submit" class="btn btn-primary">
                        {{ __('Verify Phone Number') }}

Now create a verify-code route in routes/web.php:
Route::post('/verify-code', 'VerifyController@verify')->name('verify-code');

Create a verify() function in VerifyController:
public function verify(Request $request)
  $user = \Auth::user();
  $code = $request->session()->get('verification-code');
  if( $code === $request->code){
      $user->phone_verified_at = date("Y-m-d H:i:s");
      return redirect()->route('home')->with('status','Phone number successfully verified!');
  return redirect()->back()->with('status', 'Code incorrect!');

The verify function receives the sent code previously saved in the user's session, then checks if
it matches the code provided by the user. If there’s a match, it updates the user’s
phone_verified_at column in their record with the current timestamp, signifying the user
is now verified. It then redirects them to the home page with a success message. If the code provided
does not match the one sent, the user is redirected back with a failure message.


To test the system, open the website on your localhost:

  1. Attempt to register or login
  2. You will be taken to a verification page, click on the “Call me with code” button
  3. Wait for Twilio to call your phone playing the speech containing the code
  4. Once you’ve gotten the code, provide it in the form, then click the “Verify Phone Number” button.


You have successfully completed this tutorial. You can now:

  1. Integrate Twilio with a Laravel Application
  2. Consume the Twilio Voice RESTFul API
  3. Make calls to users using the Voice API
  4. Implement, with Twilio Voice, phone number verification on your PHP Laravel App

Next, you should try to use TwiML in the stead of AWS Polly to read out the text to your user during outbound calls.

Oni Gbenga 👨🏽‍🎓CS 👨🏽‍💻Software & DevOps Engineer ☁️ AWS Consultant | 2x AWS Certified. Laravel,NodeJS,Go,React(Native),Docker,k8s

Leave a Reply

Your email address will not be published. Required fields are marked *

Never miss a story from us, get weekly updates in your inbox.