Skip to contentSkip to navigationSkip to topbar
Rate this page:
On this page

Masked Phone Numbers with Ruby and Rails


This Ruby on Rails(link takes you to an external page) sample application is modeled after the amazing rental experience created by AirBnB(link takes you to an external page), but with more Klingons(link takes you to an external page).

Host users can offer rental properties which other guest users can reserve. The guest and the host can then anonymously communicate via a disposable Twilio phone number created just for a reservation. In this tutorial, we'll show you the key bits of code to make this work.

To run this sample app yourself, download the code and follow the instructions on GitHub(link takes you to an external page).

(warning)

Warning

Read how Lyft uses masked phone numbers to let customers securely contact drivers(link takes you to an external page)


Create a reservation

create-a-reservation page anchor

The first step in connecting a guest and host is creating a reservation. Here, we handle a form submission for a new reservation which contains the guest's name and phone number.

Reservation Creation Method

reservation-creation-method page anchor

app/controllers/reservations_controller.rb


_109
class ReservationsController < ApplicationController
_109
skip_before_filter :verify_authenticity_token, only: [:accept_or_reject, :connect_guest_to_host_sms, :connect_guest_to_host_voice]
_109
before_action :set_twilio_params, only: [:connect_guest_to_host_sms, :connect_guest_to_host_voice]
_109
before_filter :authenticate_user, only: [:index]
_109
_109
# GET /reservations
_109
def index
_109
@reservations = current_user.reservations.all
_109
end
_109
_109
# GET /reservations/new
_109
def new
_109
@reservation = Reservation.new
_109
end
_109
_109
def create
_109
@vacation_property = VacationProperty.find(params[:reservation][:property_id])
_109
@reservation = @vacation_property.reservations.create(reservation_params)
_109
_109
if @reservation.save
_109
flash[:notice] = "Sending your reservation request now."
_109
@reservation.host.check_for_reservations_pending
_109
redirect_to @vacation_property
_109
else
_109
flash[:danger] = @reservation.errors
_109
end
_109
end
_109
_109
# webhook for twilio incoming message from host
_109
def accept_or_reject
_109
incoming = params[:From]
_109
sms_input = params[:Body].downcase
_109
begin
_109
@host = User.find_by(phone_number: incoming)
_109
@reservation = @host.pending_reservation
_109
if sms_input == "accept" || sms_input == "yes"
_109
@reservation.confirm!
_109
else
_109
@reservation.reject!
_109
end
_109
_109
@host.check_for_reservations_pending
_109
_109
sms_reponse = "You have successfully #{@reservation.status} the reservation."
_109
respond(sms_reponse)
_109
rescue Exception => e
_109
puts "ERROR: #{e.message}"
_109
sms_reponse = "Sorry, it looks like you don't have any reservations to respond to."
_109
respond(sms_reponse)
_109
end
_109
end
_109
_109
# webhook for twilio to anonymously connect the two parties
_109
def connect_guest_to_host_sms
_109
# Guest -> Host
_109
if @reservation.guest.phone_number == @incoming_phone
_109
@outgoing_number = @reservation.host.phone_number
_109
_109
# Host -> Guest
_109
elsif @reservation.host.phone_number == @incoming_phone
_109
@outgoing_number = @reservation.guest.phone_number
_109
end
_109
_109
response = Twilio::TwiML::MessagingResponse.new
_109
response.message(:body => @message, :to => @outgoing_number)
_109
render text: response.to_s
_109
end
_109
_109
# webhook for twilio -> TwiML for voice calls
_109
def connect_guest_to_host_voice
_109
# Guest -> Host
_109
if @reservation.guest.phone_number == @incoming_phone
_109
@outgoing_number = @reservation.host.phone_number
_109
_109
# Host -> Guest
_109
elsif @reservation.host.phone_number == @incoming_phone
_109
@outgoing_number = @reservation.guest.phone_number
_109
end
_109
response = Twilio::TwiML::VoiceResponse.new
_109
response.play(url: "http://howtodocs.s3.amazonaws.com/howdy-tng.mp3")
_109
response.dial(number: @outgoing_number)
_109
_109
render text: response.to_s
_109
end
_109
_109
_109
private
_109
# Send an SMS back to the Subscriber
_109
def respond(message)
_109
response = Twilio::TwiML::MessagingResponse.new
_109
response.message(body: message)
_109
_109
render text: response.to_s
_109
end
_109
_109
# Never trust parameters from the scary internet, only allow the white list through.
_109
def reservation_params
_109
params.require(:reservation).permit(:name, :guest_phone, :message)
_109
end
_109
_109
# Load up Twilio parameters
_109
def set_twilio_params
_109
@incoming_phone = params[:From]
_109
@message = params[:Body]
_109
anonymous_phone_number = params[:To]
_109
@reservation = Reservation.where(phone_number: anonymous_phone_number).first
_109
end
_109
_109
end

Part of our reservation system is receiving reservation requests from potential renters. However, these reservations need to be confirmed. Let's see how we would handle this step.


Before the reservation is finalized, the host needs to confirm that the property is still available. Learn how to automate this process in our first AirTNG tutorial, Workflow Automation.

Once the reservation is confirmed, we need to create a Twilio number that the guest and host can use to communicate in the provision_phone_number method.

Confirm Reservation Method

confirm-reservation-method page anchor

app/models/reservation.rb


_86
class Reservation < ActiveRecord::Base
_86
validates :name, presence: true
_86
validates :guest_phone, presence: true
_86
_86
enum status: [ :pending, :confirmed, :rejected ]
_86
_86
belongs_to :vacation_property
_86
belongs_to :user
_86
_86
def notify_host(force = false)
_86
# Don't send the message if we have more than one and we aren't being forced
_86
if self.host.pending_reservations.length > 1 and !force
_86
return
_86
else
_86
message = "You have a new reservation request from #{self.name} for #{self.vacation_property.description}:
_86
_86
'#{self.message}'
_86
_86
Reply [accept] or [reject]."
_86
_86
self.host.send_message_via_sms(message)
_86
end
_86
end
_86
_86
def host
_86
@host = User.find(self.vacation_property[:user_id])
_86
end
_86
_86
def guest
_86
@guest = User.find_by(phone_number: self.guest_phone)
_86
end
_86
_86
def confirm!
_86
provision_phone_number
_86
self.update!(status: 1)
_86
end
_86
_86
def reject!
_86
self.update!(status: 0)
_86
end
_86
_86
def notify_guest
_86
if self.status_changed? && (self.status == :confirmed || self.status == :rejected)
_86
message = "Your recent request to stay at #{self.vacation_property.description} was #{self.status}."
_86
self.guest.send_message_via_sms(message)
_86
end
_86
end
_86
_86
def send_message_to_guest(message)
_86
message = "From #{self.host.name}: #{message}"
_86
self.guest.send_message_via_sms(message, self.phone_number)
_86
end
_86
_86
def send_message_to_host(message)
_86
message = "From guest #{self.guest.name}: #{message}"
_86
self.host.send_message_via_sms(message, self.phone_number)
_86
end
_86
_86
private
_86
_86
def provision_phone_number
_86
@client = Twilio::REST::Client.new(ENV['TWILIO_ACCOUNT_SID'], ENV['TWILIO_AUTH_TOKEN'])
_86
begin
_86
# Lookup numbers in host area code, if none than lookup from anywhere
_86
@numbers = @client.api.available_phone_numbers('US').local.list(area_code: self.host.area_code)
_86
if @numbers.empty?
_86
@numbers = @client.api.available_phone_numbers('US').local.list()
_86
end
_86
_86
# Purchase the number & set the application_sid for voice and sms, will
_86
# tell the number where to route calls/sms
_86
@number = @numbers.first.phone_number
_86
@client.api.incoming_phone_numbers.create(
_86
phone_number: @number,
_86
voice_application_sid: ENV['ANONYMOUS_APPLICATION_SID'],
_86
sms_application_sid: ENV['ANONYMOUS_APPLICATION_SID']
_86
)
_86
_86
# Set the reservation.phone_number
_86
self.update!(phone_number: @number)
_86
_86
rescue Exception => e
_86
puts "ERROR: #{e.message}"
_86
end
_86
end
_86
end

Once the reservation is confirmed, we need to purchase a Twilio number that the guest and host can use to communicate.


Purchase a Twilio Number

purchase-a-twilio-number page anchor

Here we use a Twilio REST API Client(link takes you to an external page) to search for and buy a new phone number to associate with the reservation. When we buy the number, we designate a Twilio application that will handle webhook(link takes you to an external page) requests when the new number receives an incoming call or text.

We then save the new phone number on our Reservation model, so when our app receives calls or texts to this number, we'll know which reservation the call or text belongs to.

Provision Phone Number Method

provision-phone-number-method page anchor

app/models/reservation.rb


_86
class Reservation < ActiveRecord::Base
_86
validates :name, presence: true
_86
validates :guest_phone, presence: true
_86
_86
enum status: [ :pending, :confirmed, :rejected ]
_86
_86
belongs_to :vacation_property
_86
belongs_to :user
_86
_86
def notify_host(force = false)
_86
# Don't send the message if we have more than one and we aren't being forced
_86
if self.host.pending_reservations.length > 1 and !force
_86
return
_86
else
_86
message = "You have a new reservation request from #{self.name} for #{self.vacation_property.description}:
_86
_86
'#{self.message}'
_86
_86
Reply [accept] or [reject]."
_86
_86
self.host.send_message_via_sms(message)
_86
end
_86
end
_86
_86
def host
_86
@host = User.find(self.vacation_property[:user_id])
_86
end
_86
_86
def guest
_86
@guest = User.find_by(phone_number: self.guest_phone)
_86
end
_86
_86
def confirm!
_86
provision_phone_number
_86
self.update!(status: 1)
_86
end
_86
_86
def reject!
_86
self.update!(status: 0)
_86
end
_86
_86
def notify_guest
_86
if self.status_changed? && (self.status == :confirmed || self.status == :rejected)
_86
message = "Your recent request to stay at #{self.vacation_property.description} was #{self.status}."
_86
self.guest.send_message_via_sms(message)
_86
end
_86
end
_86
_86
def send_message_to_guest(message)
_86
message = "From #{self.host.name}: #{message}"
_86
self.guest.send_message_via_sms(message, self.phone_number)
_86
end
_86
_86
def send_message_to_host(message)
_86
message = "From guest #{self.guest.name}: #{message}"
_86
self.host.send_message_via_sms(message, self.phone_number)
_86
end
_86
_86
private
_86
_86
def provision_phone_number
_86
@client = Twilio::REST::Client.new(ENV['TWILIO_ACCOUNT_SID'], ENV['TWILIO_AUTH_TOKEN'])
_86
begin
_86
# Lookup numbers in host area code, if none than lookup from anywhere
_86
@numbers = @client.api.available_phone_numbers('US').local.list(area_code: self.host.area_code)
_86
if @numbers.empty?
_86
@numbers = @client.api.available_phone_numbers('US').local.list()
_86
end
_86
_86
# Purchase the number & set the application_sid for voice and sms, will
_86
# tell the number where to route calls/sms
_86
@number = @numbers.first.phone_number
_86
@client.api.incoming_phone_numbers.create(
_86
phone_number: @number,
_86
voice_application_sid: ENV['ANONYMOUS_APPLICATION_SID'],
_86
sms_application_sid: ENV['ANONYMOUS_APPLICATION_SID']
_86
)
_86
_86
# Set the reservation.phone_number
_86
self.update!(phone_number: @number)
_86
_86
rescue Exception => e
_86
puts "ERROR: #{e.message}"
_86
end
_86
end
_86
end

Now that each reservation has a Twilio Phone Number, we can see how the application will look up reservations as guest or host calls come in.


Find a reservation when a guest or host calls

find-a-reservation-when-a-guest-or-host-calls page anchor

In our controller, we create a filter which gets executed every time Twilio asks our application how to handle an incoming call or text. This filter finds and stores the correct reservation (the one associated with the anonymous number) as an instance variable that will be used as we connect the guest and host via voice or SMS.

app/controllers/reservations_controller.rb


_109
class ReservationsController < ApplicationController
_109
skip_before_filter :verify_authenticity_token, only: [:accept_or_reject, :connect_guest_to_host_sms, :connect_guest_to_host_voice]
_109
before_action :set_twilio_params, only: [:connect_guest_to_host_sms, :connect_guest_to_host_voice]
_109
before_filter :authenticate_user, only: [:index]
_109
_109
# GET /reservations
_109
def index
_109
@reservations = current_user.reservations.all
_109
end
_109
_109
# GET /reservations/new
_109
def new
_109
@reservation = Reservation.new
_109
end
_109
_109
def create
_109
@vacation_property = VacationProperty.find(params[:reservation][:property_id])
_109
@reservation = @vacation_property.reservations.create(reservation_params)
_109
_109
if @reservation.save
_109
flash[:notice] = "Sending your reservation request now."
_109
@reservation.host.check_for_reservations_pending
_109
redirect_to @vacation_property
_109
else
_109
flash[:danger] = @reservation.errors
_109
end
_109
end
_109
_109
# webhook for twilio incoming message from host
_109
def accept_or_reject
_109
incoming = params[:From]
_109
sms_input = params[:Body].downcase
_109
begin
_109
@host = User.find_by(phone_number: incoming)
_109
@reservation = @host.pending_reservation
_109
if sms_input == "accept" || sms_input == "yes"
_109
@reservation.confirm!
_109
else
_109
@reservation.reject!
_109
end
_109
_109
@host.check_for_reservations_pending
_109
_109
sms_reponse = "You have successfully #{@reservation.status} the reservation."
_109
respond(sms_reponse)
_109
rescue Exception => e
_109
puts "ERROR: #{e.message}"
_109
sms_reponse = "Sorry, it looks like you don't have any reservations to respond to."
_109
respond(sms_reponse)
_109
end
_109
end
_109
_109
# webhook for twilio to anonymously connect the two parties
_109
def connect_guest_to_host_sms
_109
# Guest -> Host
_109
if @reservation.guest.phone_number == @incoming_phone
_109
@outgoing_number = @reservation.host.phone_number
_109
_109
# Host -> Guest
_109
elsif @reservation.host.phone_number == @incoming_phone
_109
@outgoing_number = @reservation.guest.phone_number
_109
end
_109
_109
response = Twilio::TwiML::MessagingResponse.new
_109
response.message(:body => @message, :to => @outgoing_number)
_109
render text: response.to_s
_109
end
_109
_109
# webhook for twilio -> TwiML for voice calls
_109
def connect_guest_to_host_voice
_109
# Guest -> Host
_109
if @reservation.guest.phone_number == @incoming_phone
_109
@outgoing_number = @reservation.host.phone_number
_109
_109
# Host -> Guest
_109
elsif @reservation.host.phone_number == @incoming_phone
_109
@outgoing_number = @reservation.guest.phone_number
_109
end
_109
response = Twilio::TwiML::VoiceResponse.new
_109
response.play(url: "http://howtodocs.s3.amazonaws.com/howdy-tng.mp3")
_109
response.dial(number: @outgoing_number)
_109
_109
render text: response.to_s
_109
end
_109
_109
_109
private
_109
# Send an SMS back to the Subscriber
_109
def respond(message)
_109
response = Twilio::TwiML::MessagingResponse.new
_109
response.message(body: message)
_109
_109
render text: response.to_s
_109
end
_109
_109
# Never trust parameters from the scary internet, only allow the white list through.
_109
def reservation_params
_109
params.require(:reservation).permit(:name, :guest_phone, :message)
_109
end
_109
_109
# Load up Twilio parameters
_109
def set_twilio_params
_109
@incoming_phone = params[:From]
_109
@message = params[:Body]
_109
anonymous_phone_number = params[:To]
_109
@reservation = Reservation.where(phone_number: anonymous_phone_number).first
_109
end
_109
_109
end

Next, let's see how to connect the guest and the host via SMS.


Connect the Guest and the Host via SMS

connect-the-guest-and-the-host-via-sms page anchor

Our Twilio application should be configured to send HTTP requests to this controller method on any incoming text message. Our app responds with TwiML to tell Twilio what to do in response to the message.

If the initial message sent to the anonymous number was made by the host, we forward it on to the guest. But if the message was sent by the guest, we forward it to the host.

app/controllers/reservations_controller.rb


_109
class ReservationsController < ApplicationController
_109
skip_before_filter :verify_authenticity_token, only: [:accept_or_reject, :connect_guest_to_host_sms, :connect_guest_to_host_voice]
_109
before_action :set_twilio_params, only: [:connect_guest_to_host_sms, :connect_guest_to_host_voice]
_109
before_filter :authenticate_user, only: [:index]
_109
_109
# GET /reservations
_109
def index
_109
@reservations = current_user.reservations.all
_109
end
_109
_109
# GET /reservations/new
_109
def new
_109
@reservation = Reservation.new
_109
end
_109
_109
def create
_109
@vacation_property = VacationProperty.find(params[:reservation][:property_id])
_109
@reservation = @vacation_property.reservations.create(reservation_params)
_109
_109
if @reservation.save
_109
flash[:notice] = "Sending your reservation request now."
_109
@reservation.host.check_for_reservations_pending
_109
redirect_to @vacation_property
_109
else
_109
flash[:danger] = @reservation.errors
_109
end
_109
end
_109
_109
# webhook for twilio incoming message from host
_109
def accept_or_reject
_109
incoming = params[:From]
_109
sms_input = params[:Body].downcase
_109
begin
_109
@host = User.find_by(phone_number: incoming)
_109
@reservation = @host.pending_reservation
_109
if sms_input == "accept" || sms_input == "yes"
_109
@reservation.confirm!
_109
else
_109
@reservation.reject!
_109
end
_109
_109
@host.check_for_reservations_pending
_109
_109
sms_reponse = "You have successfully #{@reservation.status} the reservation."
_109
respond(sms_reponse)
_109
rescue Exception => e
_109
puts "ERROR: #{e.message}"
_109
sms_reponse = "Sorry, it looks like you don't have any reservations to respond to."
_109
respond(sms_reponse)
_109
end
_109
end
_109
_109
# webhook for twilio to anonymously connect the two parties
_109
def connect_guest_to_host_sms
_109
# Guest -> Host
_109
if @reservation.guest.phone_number == @incoming_phone
_109
@outgoing_number = @reservation.host.phone_number
_109
_109
# Host -> Guest
_109
elsif @reservation.host.phone_number == @incoming_phone
_109
@outgoing_number = @reservation.guest.phone_number
_109
end
_109
_109
response = Twilio::TwiML::MessagingResponse.new
_109
response.message(:body => @message, :to => @outgoing_number)
_109
render text: response.to_s
_109
end
_109
_109
# webhook for twilio -> TwiML for voice calls
_109
def connect_guest_to_host_voice
_109
# Guest -> Host
_109
if @reservation.guest.phone_number == @incoming_phone
_109
@outgoing_number = @reservation.host.phone_number
_109
_109
# Host -> Guest
_109
elsif @reservation.host.phone_number == @incoming_phone
_109
@outgoing_number = @reservation.guest.phone_number
_109
end
_109
response = Twilio::TwiML::VoiceResponse.new
_109
response.play(url: "http://howtodocs.s3.amazonaws.com/howdy-tng.mp3")
_109
response.dial(number: @outgoing_number)
_109
_109
render text: response.to_s
_109
end
_109
_109
_109
private
_109
# Send an SMS back to the Subscriber
_109
def respond(message)
_109
response = Twilio::TwiML::MessagingResponse.new
_109
response.message(body: message)
_109
_109
render text: response.to_s
_109
end
_109
_109
# Never trust parameters from the scary internet, only allow the white list through.
_109
def reservation_params
_109
params.require(:reservation).permit(:name, :guest_phone, :message)
_109
end
_109
_109
# Load up Twilio parameters
_109
def set_twilio_params
_109
@incoming_phone = params[:From]
_109
@message = params[:Body]
_109
anonymous_phone_number = params[:To]
_109
@reservation = Reservation.where(phone_number: anonymous_phone_number).first
_109
end
_109
_109
end

Let's see how to connect the guest and the host via phone call next.


Connect the Guest and Host via Phone Call

connect-the-guest-and-host-via-phone-call page anchor

Our Twilio application will send HTTP requests to this method on any incoming voice call. Our app responds with TwiML instructions that tell Twilio to Play an introductory MP3 audio file and then Dial either the guest or host, depending on who initiated the call.

app/controllers/reservations_controller.rb


_109
class ReservationsController < ApplicationController
_109
skip_before_filter :verify_authenticity_token, only: [:accept_or_reject, :connect_guest_to_host_sms, :connect_guest_to_host_voice]
_109
before_action :set_twilio_params, only: [:connect_guest_to_host_sms, :connect_guest_to_host_voice]
_109
before_filter :authenticate_user, only: [:index]
_109
_109
# GET /reservations
_109
def index
_109
@reservations = current_user.reservations.all
_109
end
_109
_109
# GET /reservations/new
_109
def new
_109
@reservation = Reservation.new
_109
end
_109
_109
def create
_109
@vacation_property = VacationProperty.find(params[:reservation][:property_id])
_109
@reservation = @vacation_property.reservations.create(reservation_params)
_109
_109
if @reservation.save
_109
flash[:notice] = "Sending your reservation request now."
_109
@reservation.host.check_for_reservations_pending
_109
redirect_to @vacation_property
_109
else
_109
flash[:danger] = @reservation.errors
_109
end
_109
end
_109
_109
# webhook for twilio incoming message from host
_109
def accept_or_reject
_109
incoming = params[:From]
_109
sms_input = params[:Body].downcase
_109
begin
_109
@host = User.find_by(phone_number: incoming)
_109
@reservation = @host.pending_reservation
_109
if sms_input == "accept" || sms_input == "yes"
_109
@reservation.confirm!
_109
else
_109
@reservation.reject!
_109
end
_109
_109
@host.check_for_reservations_pending
_109
_109
sms_reponse = "You have successfully #{@reservation.status} the reservation."
_109
respond(sms_reponse)
_109
rescue Exception => e
_109
puts "ERROR: #{e.message}"
_109
sms_reponse = "Sorry, it looks like you don't have any reservations to respond to."
_109
respond(sms_reponse)
_109
end
_109
end
_109
_109
# webhook for twilio to anonymously connect the two parties
_109
def connect_guest_to_host_sms
_109
# Guest -> Host
_109
if @reservation.guest.phone_number == @incoming_phone
_109
@outgoing_number = @reservation.host.phone_number
_109
_109
# Host -> Guest
_109
elsif @reservation.host.phone_number == @incoming_phone
_109
@outgoing_number = @reservation.guest.phone_number
_109
end
_109
_109
response = Twilio::TwiML::MessagingResponse.new
_109
response.message(:body => @message, :to => @outgoing_number)
_109
render text: response.to_s
_109
end
_109
_109
# webhook for twilio -> TwiML for voice calls
_109
def connect_guest_to_host_voice
_109
# Guest -> Host
_109
if @reservation.guest.phone_number == @incoming_phone
_109
@outgoing_number = @reservation.host.phone_number
_109
_109
# Host -> Guest
_109
elsif @reservation.host.phone_number == @incoming_phone
_109
@outgoing_number = @reservation.guest.phone_number
_109
end
_109
response = Twilio::TwiML::VoiceResponse.new
_109
response.play(url: "http://howtodocs.s3.amazonaws.com/howdy-tng.mp3")
_109
response.dial(number: @outgoing_number)
_109
_109
render text: response.to_s
_109
end
_109
_109
_109
private
_109
# Send an SMS back to the Subscriber
_109
def respond(message)
_109
response = Twilio::TwiML::MessagingResponse.new
_109
response.message(body: message)
_109
_109
render text: response.to_s
_109
end
_109
_109
# Never trust parameters from the scary internet, only allow the white list through.
_109
def reservation_params
_109
params.require(:reservation).permit(:name, :guest_phone, :message)
_109
end
_109
_109
# Load up Twilio parameters
_109
def set_twilio_params
_109
@incoming_phone = params[:From]
_109
@message = params[:Body]
_109
anonymous_phone_number = params[:To]
_109
@reservation = Reservation.where(phone_number: anonymous_phone_number).first
_109
end
_109
_109
end

That's it! We've just implemented anonymous communications that allow your customers to connect while protecting their privacy with the help of the Twilio Ruby Helper Library.


If you're a Ruby developer working with Twilio, you might want to check out these other tutorials.

Part 1 of this Tutorial: Workflow Automation

Increase your rate of response by automating the workflows that are key to your business.

Appointment Reminders

Send your customers a text message when they have an upcoming appointment - this tutorial shows you how to do it from a background job.

Did this help?

did-this-help page anchor

Thanks for checking out this tutorial! If you have any feedback to share with us, we'd love to hear it. Tweet @twilio(link takes you to an external page) to let us know what you think.


Rate this page: