March 18, 2015
In my previous blog post, I talked about how to do phone verification using SMS and now in this blog post I'm going to talk about how to do voice based phone verification using Twilio.
Let's change the requirement a bit this time.
We already handled SMS verification, so let's add call related changes in
PhoneVerificationService class.
class PhoneVerificationService
  attr_reader :user, :verification_through
  VERIFICATION_THROUGH_SMS  = :sms
  VERIFICATION_THROUGH_CALL = :call
  def initialize options
    @user                 = User.find(options[:user_id])
    @verification_through = options[:verification_through] || VERIFICATION_THROUGH_SMS
  end
  def process
    if verification_through == VERIFICATION_THROUGH_SMS
      perform_sms_verification
    else
      make_call
    end
  end
  private
  def from
    #phone number given by twilio
    Settings.twilio_number_for_app
  end
  def to
    "+1#{user.phone_number}"
  end
  def body
    "Please reply with this code '#{user.phone_verification_code}'" <<
    "to verify your phone number"
  end
  def send_sms
    Rails.logger.info "SMS: From: #{from} To: #{to} Body: \"#{body}\""
    twilio_client.account.messages.create ( from: from, to: to, body: body)
  end
  def make_call
    Rails.logger.info "Call: From: #{from} To: #{to}"
    twilio_client.account.calls.create( from: from, to: to, url: callback_url)
  end
  def perform_sms_verification
    begin
      send_sms
    rescue Twilio::REST::RequestError => e
      return make_call if e.message.include?('is not a mobile number')
      raise e.message
    end
  end
  def callback_url
    Rails.application.routes.url_helpers
      .phone_verifications_voice_url(host: Settings.app_host,
                                     verification_code: user.phone_verification_code)
  end
  def twilio_client
    @twilio ||= Twilio::REST::Client.new(Settings.twilio_account_sid,
                                         Settings.twilio_auth_token)
  end
end
In PhoneVerificationService class we have added some major changes:
verification_through and in initialize method
we have set it.VERIFICATION_THROUGH_SMS & VERIFICATION_THROUGH_CALL.process method and now we check which verification process should
be taken place based on verification_through attribute.perform_sms_verification, make_call and callback_url.Let's go through these newly added methods.
In this method, first we try to send an SMS verification. If SMS verification fails with error message like 'is not a mobile number' then in the rescue block, we make a phone verification call or we raise the error.
In this method, we create an actual phone call and pass the all required info to twilio client object.
In this method, we set the callback_url which is
required for Twilio for making a call. When we call the user for
verification, our app needs to behave like a human and should ask to the user to
press 1 to complete the verification (i.e. In the form of TwiML).
This callback_url needs be to set in our app.
For callback_url, add route for voice action in config/routes.rb
post 'phone_verifications/voice' => 'phone_verifications#voice'
Add voice action and required code for it in PhoneVerificationsController.
class PhoneVerificationsController < ApplicationController
  skip_before_filter :verify_authenticity_token
  after_filter :set_header
  HUMAN_VOICE = 'alice'
  def voice
    verification_code = params[:verification_code]
    response = Twilio::TwiML::Response.new do |r|
      r.Gather numDigits: '1',
               action: "/phone_verifications/verify_from_voice?verification_code=#{verification_code}",
               method: 'post' do |g|
        g.Say 'Press 1 to verify your phone number.', voice: HUMAN_VOICE
      end
    end
    render_twiml response
  end
  def verify_from_message
    user = get_user_for_phone_verification
    user.mark_phone_as_verified! if user
    render nothing: true
  end
  private
  def get_user_for_phone_verification
    phone_verification_code = params['Body'].try(:strip)
    phone_number            = params['From'].gsub('+1', '')
    condition = { phone_verification_code: phone_verification_code,
                  phone_number: phone_number }
    User.unverified_phones.where(condition).first
  end
  def set_header
    response.headers["Content-Type"] = "text/xml"
  end
  def render_twiml(response)
    render text: response.text
  end
end
In this voice method, we have set up the Twilio response. When a call is made to
the user, this response will get converted into robotic human voice. render_twiml
method which sets twilio response in
text form is required for Twilio APIs. Set up the response header in the
set_header method which gets called in after_filter method.
In voice action method, we have setup the request url in the action key of Twilio
response object, that also needs to be set in your Twilio account. So when, user replies
back to the call query, Twilio will make a request to our app and adds its own some values
as parameters to the request. Using those parameters we handle the actual phone
verification in the app.
Open twilio account and under NUMBERS section/tab, click on your Twilio number. Then in Voice section, add request URL with HTTP POST method. Add URL like this.
http://your.ngrok.com/phone_verifications/verify_from_voice/
We need Ngrok to expose local url to external world. Read more about it in my previous blog post.
First add route for this action in config/routes.rb
post "phone_verifications/verify_from_voice" => "phone_verifications#verify_from_voice
Add this method, in your PhoneVerificationsController.
  def verify_from_voice
    response = Twilio::TwiML::Response.new do |r|
      if params['Digits'] == "1"
        user = get_user_for_phone_verification
        user.mark_phone_as_verified!
        r.Say 'Thank you. Your phone number has been verified successfully.',
               voice: HUMAN_VOICE
      else
        r.Say 'Sorry. Your phone number has not verified.',
               voice: HUMAN_VOICE
      end
    end
    render_twiml response
  end
Modify private method get_user_for_phone_verification to support voice verification
in PhoneVerificationsController.
  def get_user_for_phone_verification
    if params['Called'].present?
      phone_verification_code = params['verification_code']
      phone_number            = params['To']
    else
      phone_verification_code = params['Body'].try(:strip)
      phone_number            = params['From']
    end
    condition = { phone_verification_code: phone_verification_code,
                  phone_number: phone_number.gsub('+1', '') }
    User.unverified_phones.where(condition).first
  end
In the verify_from_voice method, we get parameters Digits, To & verification_code
from Twilio request. Using these parameters, we search for user in the database. If we get the
proper user then we mark user's phone number as verified phone number.
If this blog was helpful, check out our full blog archive.