How to proxy inbound phone calls using Twilio


Because we're a marketplace for RV rentals, we need to let customers call rental agencies directly but we also need to be able to track each time a customer calls one of the hundreds of RV rental locations we work with. In other words, we need a proxy phone system.

The finished product will use a single toll-free number and long list of extension numbers. The extension numbers will be generated from the primary key of each row in our agency database model.

When a customer calls in they'll be prompted to enter the extension number (agency ID) and their call will be forwarded on to the RV rental agency. If the customer makes a mistake and enters an invalid extension, we'll forward the call to a general customer service number.

What users will see when we're done

Routes

We'll need four routes to handle the various paths calls can take.

namespace :phone do
  match :gather_extension, via: [:get, :post]
  match :dial_agency, via: [:get, :post]
  match :dial_support, via: [:get, :post]
  match :invalid_extension, via: [:get, :post]
end  

Twilio setup

We also need to buy a number through Twilio and give it a destination URL that is our "gather_extension" route. For us, that url would be http://rvmenu.com/phone/gather_extension.xml

The XML extension is important; don't forget to include it since Rails won't match .xml routes by default.

Controller

Here's the full controller with inline explanations.

class PhoneController < ApplicationController
  respond_to :xml

  # Twilio won't be sending the form authenticity tokens so skip them
  skip_before_filter :verify_authenticity_token  

  def gather_extension
    # A simple view will prompt the user for the extension number
  end

  def dial_agency
    # The extension number they enter (params['Digits']) is the model ID.
    # The extension will arrive zero-padded so we have to convert to an integer
    # Example: extension 0023 will be ID: 23
    # Location is the rental agency model that has the agency's destination phone number
    @location = Location.where(:id => params['Digits'].to_i).where('phone is not null').first

    # If no matching record is found, redirect to the invalid_extension handler
    return redirect_to :action => :invalid_extension, :format => :xml unless @location

    # Log the inbound phone call data.  params['From'] is the caller ID number sent by Twilio
    @location.contacts.create(classification: 'phone', phone: params['From'])
  end

  def dial_support
    # Log the inbound phone call data.  params['From'] is the caller ID number sent by Twilio
    Contact.create(classification: 'phone', phone: params['From'], comments: 'dial_support')
  end

  def invalid_extension
    # Log the inbound phone call data.  params['From'] is the caller ID number sent by Twilio
    Contact.create(classification: 'phone', phone: params['From'], comments: 'invalid_extension')
  end

end

Views

gather_extension.xml.builder

Here's where we gather the extension number from the customer. In our case, we have less than 1,000 agencies so we can get by with a three-digit extension. Thus we set :numDigits => 3. Once the customer has entered three digits, Twilio will send a POST request to :action => '/phone/dial_agency.xml' with the entered digits as parameters. If the customer exceeds the :timeout => 15 then they'll drop through to xml.Redirect '/phone/dial_support.xml'

xml.Response do
  xml.Gather :method => "GET", :timeout => 15, :numDigits => 3, :finishOnKey=>"#", :action => '/phone/dial_agency.xml' do
    xml.Say 'Hi, thanks for calling RV Menu. Please enter the extension number at any time.',  :voice => 'woman'
  end
  xml.Redirect '/phone/dial_support.xml'
end
dial_agency.xml.builder

The user has entered an extension number and the controller has supplied us with the Location model that has the agency's phone number so we can just forward the call to the agency using the dial verb.

xml.Response do
   xml.Dial do
      xml.Number @location.phone
  end
end
invalid_extension.xml.builder

If the user entered an invalid extension we'll forward to our customer service number and record the call.

xml.Response do
  xml.Say "That extension doesn't seem to work.  Transferring to customer service.",  :voice => 'woman'
  xml.Dial(:record => 'true') do
    xml.Number '313-485-1949'
  end
end
dial_support.xml.builder

This view is almost the same as invalid_extension; it gets rendered if the customer exceeds the extension timeout.

xml.Response do
  xml.Say "Transferring to customer service.",  :voice => 'woman'
  xml.Dial(:record => 'true') do
    xml.Number '313-485-1949'
  end
end

Generating the extension number

The last piece of the puzzle is showing the phone number and extension wherever an agency is shown throughout the site. The only gotcha here is that the model ID number needs to be zero-padded to the right number of digits. sprintf does just that.

(888) 913-6677 x <%= sprintf('%03d', @location.id) %>

Wrapping up

Once things are up and running, you can replace the Say verbs with your own recordings using the Play verbs for a more professional customer experience.
Real Time Analytics