Call Tracking with Ruby and Rails

Download the Code

This Ruby on Rails web application shows how you can use Twilio to track the effectiveness of different marketing channels.

Call tracking home page

This application has three main features:

  • It purchases phone numbers from Twilio to use in different marketing campaigns (like a billboard or a bus advertisement)
  • It forwards incoming calls for those phone numbers to a salesperson
  • It displays charts showing data about the phone numbers and the calls they receive

Check out how Whatclinic.com used Twilio to build a call tracking platform for healthcare providers.

In this tutorial, we'll point out the key bits of code that make this application work. Check out the project README on GitHub to see how to run the code yourself.

Search for available phone numbers

Call tracking requires us to search for and buy phone numbers on demand, associating a specific phone number with a lead source. This utility class uses the twilio-ruby helper library to search for phone numbers by area code and return a list of numbers that are available for purchase.

Loading Code Samples...
Language
class TwilioClient
  def self.available_phone_numbers(area_code)
    new.available_phone_numbers(area_code)
  end

  def self.purchase_phone_number(phone_number)
    new.purchase_phone_number(phone_number)
  end

  def initialize
    # To find TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN visit
    # https://www.twilio.com/user/account
    account_sid = ENV['TWILIO_ACCOUNT_SID']
    auth_token  = ENV['TWILIO_AUTH_TOKEN']
    @client = Twilio::REST::Client.new account_sid, auth_token
  end

  def available_phone_numbers(area_code = '415')
    client.available_phone_numbers('US').local.list(area_code: area_code).take(10)
  end

  def purchase_phone_number(phone_number)
    application_sid = ENV['TWIML_APPLICATION_SID'] || sid
    client.incoming_phone_numbers.
      create(phone_number: phone_number, voice_application_sid: application_sid)
  end

  private

  DEFAULT_APPLICATION_NAME = 'Call tracking app'

  def sid
    applications = @client.applications.list(friendly_name: DEFAULT_APPLICATION_NAME)
    if applications.any?
      applications.first.sid
    else
      @client.applications.create(friendly_name: DEFAULT_APPLICATION_NAME).sid
    end
  end

  attr_reader :client
end
lib/twilio_client.rb
Search Phone Numbers

lib/twilio_client.rb

Now let's see how we will display these numbers for the user to purchase them and enable their campaigns.

Display available phone numbers

We display a form to the user on the app's home page which allows them to search for a new phone number by area code. At controller level we use the TwilioClient we created earlier to actually search for numbers. This will render the view that contains a list of numbers they can choose to buy.

Loading Code Samples...
Language
class AvailablePhoneNumbersController < ApplicationController
  def index
    area_code = params["area-code"]
    # TwilioClient is a thin wrapper for Twilio::REST::Client
    @phone_numbers = ::TwilioClient.available_phone_numbers(area_code)
  end
end
app/controllers/available_phone_numbers_controller.rb
Available Phone Numbers

app/controllers/available_phone_numbers_controller.rb

We've seen how we can display available phone numbers for purchase with the help of the Twilio C# helper library. Now let's look at how we can buy an available phone number.

Buy a phone number

Our purchase_phone_number method takes a phone number as its sole parameter and uses our Twilio API client to purchase the available phone number our user chooses.

Loading Code Samples...
Language
class TwilioClient
  def self.available_phone_numbers(area_code)
    new.available_phone_numbers(area_code)
  end

  def self.purchase_phone_number(phone_number)
    new.purchase_phone_number(phone_number)
  end

  def initialize
    # To find TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN visit
    # https://www.twilio.com/user/account
    account_sid = ENV['TWILIO_ACCOUNT_SID']
    auth_token  = ENV['TWILIO_AUTH_TOKEN']
    @client = Twilio::REST::Client.new account_sid, auth_token
  end

  def available_phone_numbers(area_code = '415')
    client.available_phone_numbers('US').local.list(area_code: area_code).take(10)
  end

  def purchase_phone_number(phone_number)
    application_sid = ENV['TWIML_APPLICATION_SID'] || sid
    client.incoming_phone_numbers.
      create(phone_number: phone_number, voice_application_sid: application_sid)
  end

  private

  DEFAULT_APPLICATION_NAME = 'Call tracking app'

  def sid
    applications = @client.applications.list(friendly_name: DEFAULT_APPLICATION_NAME)
    if applications.any?
      applications.first.sid
    else
      @client.applications.create(friendly_name: DEFAULT_APPLICATION_NAME).sid
    end
  end

  attr_reader :client
end
lib/twilio_client.rb
Purchase Phone Number

lib/twilio_client.rb

If you don't know where you can get this application SID, don't panic, the next step will show you how.

Set webhook URLs in a TwiML Application

When we purchase a phone number, we specify a voice application SID. This is an identifier for a TwiML application, which you can create through the REST API or your Twilio Console.

Create TwiML App

Associate a phone number with a lead source

Once we search for and buy a Twilio number, we need to associate it with a lead source in our database. This is the core of a call tracking application. Any phone calls to our new Twilio number will be attributed to this source.

Loading Code Samples...
Language
class LeadSourcesController < ApplicationController
  before_filter :find_lead_source, only: [:edit, :update]

  def edit
  end

  def create
    phone_number = params[:format]
    twilio_number = TwilioClient.purchase_phone_number(phone_number)
    lead_source = LeadSource.create(name: '', incoming_number: twilio_number.friendly_name)

    message  = "Phone number #{twilio_number.friendly_name} has been purchased. Please add a name for this lead source"
    redirect_to edit_lead_source_path(lead_source), notice: message
  end

  def update
    if @lead_source.update_attributes(lead_sources_params)
      redirect_to root_url, notice: 'Lead source successfully updated.'
    end
  end

  private

  def find_lead_source
    @lead_source = LeadSource.find(params[:id])
  end

  def lead_sources_params
    params.require(:lead_source).permit(:name, :forwarding_number)
  end
end
app/controllers/lead_sources_controller.rb
Lead Source Controller

app/controllers/lead_sources_controller.rb

So far our method for creating a Lead Source and associating a Twilio phone number with it is pretty straightforward. Now let's have a closer look at our Lead Source model which will store this information.

The LeadSource model

The LeadSource model associates a Twilio number to a named lead source (like "Wall Street Journal Ad" or "Dancing guy with sign"). It also tracks a phone number to which we'd like all the calls redirected, like your sales or support help line.

Loading Code Samples...
Language
class LeadSource < ActiveRecord::Base
  has_many :leads

  def self.count_leads
    joins(:leads).group(:name).order(:name).count
  end

  def to_str
    "#{name || "(no yet named)"} - #{incoming_number}"
  end
end
app/models/lead_source.rb
Lead Source Model

app/models/lead_source.rb

As the application will be collecting leads and associating them to each LeadSource or campaign, it is necessary to have a Lead model as well to keep track of each Lead as it comes in and associate it to the LeadSource.

The Lead model

A Lead represents a phone call generated by a LeadSource. Each time somebody calls a phone number associated with a LeadSource, we'll use the Lead model to record some of the data Twilio gives us about their call.

Loading Code Samples...
Language
class Lead < ActiveRecord::Base
  belongs_to :lead_source

  def self.count_by_city
    group(:city).count
  end

  def to_str
    "#{city}, #{state}"
  end
end
app/models/lead.rb
Lead Model

app/models/lead.rb

The backend part of the code which creates a LeadSource as well as a Twilio Number is complete. The next part of the application will be the webhooks that will handle incoming calls and forward them to the appropriate sales team member. Let's us see the way these webhooks are built.

Forward calls and create leads

Whenever a customer calls one of our Twilio numbers, Twilio will send a POST request to the URL associated with this action (should be /call-tracking/forward-call).

We use the incoming call data to create a new Lead for a LeadSource, then return TwiML that connects our caller with the forwarding_number of our LeadSource.

Loading Code Samples...
Language
class CallTrackingController < ApplicationController
  skip_before_action :verify_authenticity_token

  def forward_call
    lead = Lead.create(lead_params)
    render text: twilio_response
  end

  private

  def twilio_response
    phone_number = lead_source.forwarding_number

    response = Twilio::TwiML::VoiceResponse.new
    response.dial(number: phone_number)
    response.to_s
  end

  def lead_params
    {
      lead_source: lead_source,
      phone_number: params[:Caller],
      city: params[:FromCity],
      state: params[:FromState],
    }
  end

  def lead_source
    incoming_number = GlobalPhone.parse(params[:Called]).national_format
    LeadSource.find_by_incoming_number(incoming_number)
  end
end
app/controllers/call_tracking_controller.rb
Call Forwarding

app/controllers/call_tracking_controller.rb

Once we have forwarded calls and created leads, we will have a lot of incoming calls that will create leads, and that will be data for us but we need to transform that data into information in order to get benefits from it. So, let's see how we get statistics from these sources on the next step.

Get statistics about our lead sources

One useful statistic we can get from our data is how many calls each LeadSource has received. We use the Active Record Query Interface to make a list containing each LeadSource and a count of its Lead models.

Loading Code Samples...
Language
class LeadSource < ActiveRecord::Base
  has_many :leads

  def self.count_leads
    joins(:leads).group(:name).order(:name).count
  end

  def to_str
    "#{name || "(no yet named)"} - #{incoming_number}"
  end
end
app/models/lead_source.rb
Leads By Source Stats

app/models/lead_source.rb

Up until this point, we have been focusing on the backend code to our application. Which is ready to start handling incoming calls or leads. Next, let's turn our attention to the client side. Which, in this case, is a simple Javascript application, along with Chart.js which will render these stats in an appropriate way.

Visualize our statistics with Chart.js

Back on the home page, we fetch call tracking statistics in JSON from the server using jQuery We display the stats in colorful pie charts we create with Chart.js.

Call tracking charts

Loading Code Samples...
Language
$(document).ready(function() {
  $.get('/statistics/leads_by_source/', function(data) {
    CallTrackingGraph("#leads-by-source", data).draw();
  });

  $.get('/statistics/leads_by_city/', function(data) {
    CallTrackingGraph("#leads-by-city", data).draw();
  });
});

CallTrackingGraph = function(selector, data) {
  function getContext() {
    return $(selector).get(0).getContext("2d");
  }

  return {
    draw: function() {
      var context = getContext(selector);
      new Chart(context).Pie(data);
    }
  }
}
app/assets/javascripts/call_tracking.js
Pie Chart from Stats

app/assets/javascripts/call_tracking.js

That's it! Our Ruby on Rails application is now ready to purchase new phone numbers, forward incoming calls, and record some statistics for our business.

Where to next?

If you're a Ruby on Rails developer working with Twilio, you might also enjoy these tutorials:

Click To Call with Ruby and Rails

Put a button on your web page that connects visitors to live support or sales people via telephone.

IVR: Phone Tree with Ruby and Rails

Connect your callers to an IVR (interactive voice response), allowing them to make selections about how their call should be routed.

Did this help?

Thanks for checking this tutorial out! If you have any feedback to share with us please contact us on Twitter, we'd love to hear it.

Agustin Camino
Jose Oliveros
David Prothero
Andrew Baker

Need some help?

We all do sometimes; code is hard. Get help now from our support team, or lean on the wisdom of the crowd browsing the Twilio tag on Stack Overflow.

1 / 1
Loading Code Samples...
class TwilioClient
  def self.available_phone_numbers(area_code)
    new.available_phone_numbers(area_code)
  end

  def self.purchase_phone_number(phone_number)
    new.purchase_phone_number(phone_number)
  end

  def initialize
    # To find TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN visit
    # https://www.twilio.com/user/account
    account_sid = ENV['TWILIO_ACCOUNT_SID']
    auth_token  = ENV['TWILIO_AUTH_TOKEN']
    @client = Twilio::REST::Client.new account_sid, auth_token
  end

  def available_phone_numbers(area_code = '415')
    client.available_phone_numbers('US').local.list(area_code: area_code).take(10)
  end

  def purchase_phone_number(phone_number)
    application_sid = ENV['TWIML_APPLICATION_SID'] || sid
    client.incoming_phone_numbers.
      create(phone_number: phone_number, voice_application_sid: application_sid)
  end

  private

  DEFAULT_APPLICATION_NAME = 'Call tracking app'

  def sid
    applications = @client.applications.list(friendly_name: DEFAULT_APPLICATION_NAME)
    if applications.any?
      applications.first.sid
    else
      @client.applications.create(friendly_name: DEFAULT_APPLICATION_NAME).sid
    end
  end

  attr_reader :client
end
class AvailablePhoneNumbersController < ApplicationController
  def index
    area_code = params["area-code"]
    # TwilioClient is a thin wrapper for Twilio::REST::Client
    @phone_numbers = ::TwilioClient.available_phone_numbers(area_code)
  end
end
class TwilioClient
  def self.available_phone_numbers(area_code)
    new.available_phone_numbers(area_code)
  end

  def self.purchase_phone_number(phone_number)
    new.purchase_phone_number(phone_number)
  end

  def initialize
    # To find TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN visit
    # https://www.twilio.com/user/account
    account_sid = ENV['TWILIO_ACCOUNT_SID']
    auth_token  = ENV['TWILIO_AUTH_TOKEN']
    @client = Twilio::REST::Client.new account_sid, auth_token
  end

  def available_phone_numbers(area_code = '415')
    client.available_phone_numbers('US').local.list(area_code: area_code).take(10)
  end

  def purchase_phone_number(phone_number)
    application_sid = ENV['TWIML_APPLICATION_SID'] || sid
    client.incoming_phone_numbers.
      create(phone_number: phone_number, voice_application_sid: application_sid)
  end

  private

  DEFAULT_APPLICATION_NAME = 'Call tracking app'

  def sid
    applications = @client.applications.list(friendly_name: DEFAULT_APPLICATION_NAME)
    if applications.any?
      applications.first.sid
    else
      @client.applications.create(friendly_name: DEFAULT_APPLICATION_NAME).sid
    end
  end

  attr_reader :client
end
class LeadSourcesController < ApplicationController
  before_filter :find_lead_source, only: [:edit, :update]

  def edit
  end

  def create
    phone_number = params[:format]
    twilio_number = TwilioClient.purchase_phone_number(phone_number)
    lead_source = LeadSource.create(name: '', incoming_number: twilio_number.friendly_name)

    message  = "Phone number #{twilio_number.friendly_name} has been purchased. Please add a name for this lead source"
    redirect_to edit_lead_source_path(lead_source), notice: message
  end

  def update
    if @lead_source.update_attributes(lead_sources_params)
      redirect_to root_url, notice: 'Lead source successfully updated.'
    end
  end

  private

  def find_lead_source
    @lead_source = LeadSource.find(params[:id])
  end

  def lead_sources_params
    params.require(:lead_source).permit(:name, :forwarding_number)
  end
end
class LeadSource < ActiveRecord::Base
  has_many :leads

  def self.count_leads
    joins(:leads).group(:name).order(:name).count
  end

  def to_str
    "#{name || "(no yet named)"} - #{incoming_number}"
  end
end
class Lead < ActiveRecord::Base
  belongs_to :lead_source

  def self.count_by_city
    group(:city).count
  end

  def to_str
    "#{city}, #{state}"
  end
end
class CallTrackingController < ApplicationController
  skip_before_action :verify_authenticity_token

  def forward_call
    lead = Lead.create(lead_params)
    render text: twilio_response
  end

  private

  def twilio_response
    phone_number = lead_source.forwarding_number

    response = Twilio::TwiML::VoiceResponse.new
    response.dial(number: phone_number)
    response.to_s
  end

  def lead_params
    {
      lead_source: lead_source,
      phone_number: params[:Caller],
      city: params[:FromCity],
      state: params[:FromState],
    }
  end

  def lead_source
    incoming_number = GlobalPhone.parse(params[:Called]).national_format
    LeadSource.find_by_incoming_number(incoming_number)
  end
end
class LeadSource < ActiveRecord::Base
  has_many :leads

  def self.count_leads
    joins(:leads).group(:name).order(:name).count
  end

  def to_str
    "#{name || "(no yet named)"} - #{incoming_number}"
  end
end
$(document).ready(function() {
  $.get('/statistics/leads_by_source/', function(data) {
    CallTrackingGraph("#leads-by-source", data).draw();
  });

  $.get('/statistics/leads_by_city/', function(data) {
    CallTrackingGraph("#leads-by-city", data).draw();
  });
});

CallTrackingGraph = function(selector, data) {
  function getContext() {
    return $(selector).get(0).getContext("2d");
  }

  return {
    draw: function() {
      var context = getContext(selector);
      new Chart(context).Pie(data);
    }
  }
}