Skip to contentSkip to navigationSkip to topbar
On this page

Masked Phone Numbers with C# and ASP.NET MVC


This ASP.NET(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)

Legal implications of managing communications between users

If you choose to manage communications between your users, including voice calls, text-based messages (e.g., SMS), and chat, you may need to comply with certain laws and regulations, including those regarding obtaining consent. Additional information regarding legal compliance considerations and best practices for using Twilio to manage and record communications between your users, such as when using Twilio Proxy, can be found here(link takes you to an external page).

Notice: Twilio recommends that you consult with your legal counsel to make sure that you are complying with all applicable laws in connection with communications you record or store using Twilio.

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 message.

Create a Reservation

create-a-reservation-1 page anchor

AirTNG.Web/Controllers/ReservationsController.cs

1
using System;
2
using System.Threading.Tasks;
3
using System.Web.Mvc;
4
using AirTNG.Web.Domain.PhoneNumber;
5
using AirTNG.Web.Domain.Reservations;
6
using AirTNG.Web.Models;
7
using AirTNG.Web.Models.Repository;
8
using AirTNG.Web.ViewModels;
9
using Microsoft.AspNet.Identity;
10
using Twilio.TwiML;
11
using Twilio.TwiML.Mvc;
12
13
namespace AirTNG.Web.Controllers
14
{
15
[Authorize]
16
public class ReservationsController : TwilioController
17
{
18
private readonly IVacationPropertiesRepository _vacationPropertiesRepository;
19
private readonly IReservationsRepository _reservationsRepository;
20
private readonly IUsersRepository _usersRepository;
21
private readonly INotifier _notifier;
22
private readonly IPurchaser _phoneNumberPurchaser;
23
24
public Func<string> UserId;
25
26
public ReservationsController() : this(
27
new VacationPropertiesRepository(),
28
new ReservationsRepository(),
29
new UsersRepository(),
30
new Notifier(),
31
new Purchaser()) { }
32
33
public ReservationsController(
34
IVacationPropertiesRepository vacationPropertiesRepository,
35
IReservationsRepository reservationsRepository,
36
IUsersRepository usersRepository,
37
INotifier notifier,
38
IPurchaser phoneNumberPurchaser)
39
{
40
_vacationPropertiesRepository = vacationPropertiesRepository;
41
_reservationsRepository = reservationsRepository;
42
_usersRepository = usersRepository;
43
_notifier = notifier;
44
_phoneNumberPurchaser = phoneNumberPurchaser;
45
UserId = () => User.Identity.GetUserId();
46
}
47
48
public async Task<ActionResult> Index()
49
{
50
var user = await _usersRepository.FindAsync(UserId());
51
var reservations = user.Reservations;
52
53
return View(reservations);
54
}
55
56
// GET: Reservations/Create
57
public async Task<ActionResult> Create(int id)
58
{
59
var vacationProperty = await _vacationPropertiesRepository.FindAsync(id);
60
var reservation = new ReservationViewModel
61
{
62
ImageUrl = vacationProperty.ImageUrl,
63
Description = vacationProperty.Description,
64
VacationPropertyId = vacationProperty.Id,
65
VacationPropertyDescription = vacationProperty.Description,
66
UserName = vacationProperty.Owner.Name,
67
UserPhoneNumber = vacationProperty.Owner.PhoneNumber,
68
};
69
70
return View(reservation);
71
}
72
73
// POST: Reservations/Create
74
[HttpPost]
75
public async Task<ActionResult> Create(ReservationViewModel model)
76
{
77
if (ModelState.IsValid)
78
{
79
var reservation = new Reservation
80
{
81
Message = model.Message,
82
UserId = UserId(), // This is the reservee user ID
83
VactionPropertyId = model.VacationPropertyId,
84
Status = ReservationStatus.Pending,
85
CreatedAt = DateTime.Now
86
};
87
88
await _reservationsRepository.CreateAsync(reservation);
89
await _reservationsRepository.LoadNavigationPropertiesAsync(reservation);
90
91
await _notifier.SendNotificationAsync(reservation);
92
93
return RedirectToAction("Index", "VacationProperties");
94
}
95
96
return View(model);
97
}
98
99
// POST Reservations/Handle
100
[HttpPost]
101
[AllowAnonymous]
102
public async Task<ActionResult> Handle(string from, string body)
103
{
104
string smsResponse;
105
106
try
107
{
108
var host = await _usersRepository.FindByPhoneNumberAsync(from);
109
var reservation = await _reservationsRepository.FindFirstPendingReservationByHostAsync(host.Id);
110
111
var smsRequest = body;
112
if (IsSmsRequestAccepted(smsRequest))
113
{
114
var purchasedPhoneNumber = _phoneNumberPurchaser.Purchase(host.AreaCode);
115
116
reservation.Status = ReservationStatus.Confirmed;
117
reservation.AnonymousPhoneNumber = purchasedPhoneNumber.PhoneNumber;
118
}
119
else
120
{
121
reservation.Status = ReservationStatus.Rejected;
122
}
123
124
await _reservationsRepository.UpdateAsync(reservation);
125
smsResponse =
126
string.Format("You have successfully {0} the reservation", reservation.Status);
127
}
128
catch (Exception)
129
{
130
smsResponse = "Sorry, it looks like you don't have any reservations to respond to.";
131
}
132
133
return TwiML(Respond(smsResponse));
134
}
135
136
private static TwilioResponse Respond(string message)
137
{
138
var response = new TwilioResponse();
139
response.Message(message);
140
141
return response;
142
}
143
144
private static bool IsSmsRequestAccepted(string smsRequest)
145
{
146
return smsRequest.Equals("accept", StringComparison.InvariantCultureIgnoreCase) ||
147
smsRequest.Equals("yes", StringComparison.InvariantCultureIgnoreCase);
148
}
149
}
150
}

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 finishing with the reservation, the host needs to confirm that the property was reserved. Learn how to automate this process in our first AirTNG tutorial, Workflow Automation(link takes you to an external page).

AirTNG.Web/Controllers/ReservationsController.cs

1
using System;
2
using System.Threading.Tasks;
3
using System.Web.Mvc;
4
using AirTNG.Web.Domain.PhoneNumber;
5
using AirTNG.Web.Domain.Reservations;
6
using AirTNG.Web.Models;
7
using AirTNG.Web.Models.Repository;
8
using AirTNG.Web.ViewModels;
9
using Microsoft.AspNet.Identity;
10
using Twilio.TwiML;
11
using Twilio.TwiML.Mvc;
12
13
namespace AirTNG.Web.Controllers
14
{
15
[Authorize]
16
public class ReservationsController : TwilioController
17
{
18
private readonly IVacationPropertiesRepository _vacationPropertiesRepository;
19
private readonly IReservationsRepository _reservationsRepository;
20
private readonly IUsersRepository _usersRepository;
21
private readonly INotifier _notifier;
22
private readonly IPurchaser _phoneNumberPurchaser;
23
24
public Func<string> UserId;
25
26
public ReservationsController() : this(
27
new VacationPropertiesRepository(),
28
new ReservationsRepository(),
29
new UsersRepository(),
30
new Notifier(),
31
new Purchaser()) { }
32
33
public ReservationsController(
34
IVacationPropertiesRepository vacationPropertiesRepository,
35
IReservationsRepository reservationsRepository,
36
IUsersRepository usersRepository,
37
INotifier notifier,
38
IPurchaser phoneNumberPurchaser)
39
{
40
_vacationPropertiesRepository = vacationPropertiesRepository;
41
_reservationsRepository = reservationsRepository;
42
_usersRepository = usersRepository;
43
_notifier = notifier;
44
_phoneNumberPurchaser = phoneNumberPurchaser;
45
UserId = () => User.Identity.GetUserId();
46
}
47
48
public async Task<ActionResult> Index()
49
{
50
var user = await _usersRepository.FindAsync(UserId());
51
var reservations = user.Reservations;
52
53
return View(reservations);
54
}
55
56
// GET: Reservations/Create
57
public async Task<ActionResult> Create(int id)
58
{
59
var vacationProperty = await _vacationPropertiesRepository.FindAsync(id);
60
var reservation = new ReservationViewModel
61
{
62
ImageUrl = vacationProperty.ImageUrl,
63
Description = vacationProperty.Description,
64
VacationPropertyId = vacationProperty.Id,
65
VacationPropertyDescription = vacationProperty.Description,
66
UserName = vacationProperty.Owner.Name,
67
UserPhoneNumber = vacationProperty.Owner.PhoneNumber,
68
};
69
70
return View(reservation);
71
}
72
73
// POST: Reservations/Create
74
[HttpPost]
75
public async Task<ActionResult> Create(ReservationViewModel model)
76
{
77
if (ModelState.IsValid)
78
{
79
var reservation = new Reservation
80
{
81
Message = model.Message,
82
UserId = UserId(), // This is the reservee user ID
83
VactionPropertyId = model.VacationPropertyId,
84
Status = ReservationStatus.Pending,
85
CreatedAt = DateTime.Now
86
};
87
88
await _reservationsRepository.CreateAsync(reservation);
89
await _reservationsRepository.LoadNavigationPropertiesAsync(reservation);
90
91
await _notifier.SendNotificationAsync(reservation);
92
93
return RedirectToAction("Index", "VacationProperties");
94
}
95
96
return View(model);
97
}
98
99
// POST Reservations/Handle
100
[HttpPost]
101
[AllowAnonymous]
102
public async Task<ActionResult> Handle(string from, string body)
103
{
104
string smsResponse;
105
106
try
107
{
108
var host = await _usersRepository.FindByPhoneNumberAsync(from);
109
var reservation = await _reservationsRepository.FindFirstPendingReservationByHostAsync(host.Id);
110
111
var smsRequest = body;
112
if (IsSmsRequestAccepted(smsRequest))
113
{
114
var purchasedPhoneNumber = _phoneNumberPurchaser.Purchase(host.AreaCode);
115
116
reservation.Status = ReservationStatus.Confirmed;
117
reservation.AnonymousPhoneNumber = purchasedPhoneNumber.PhoneNumber;
118
}
119
else
120
{
121
reservation.Status = ReservationStatus.Rejected;
122
}
123
124
await _reservationsRepository.UpdateAsync(reservation);
125
smsResponse =
126
string.Format("You have successfully {0} the reservation", reservation.Status);
127
}
128
catch (Exception)
129
{
130
smsResponse = "Sorry, it looks like you don't have any reservations to respond to.";
131
}
132
133
return TwiML(Respond(smsResponse));
134
}
135
136
private static TwilioResponse Respond(string message)
137
{
138
var response = new TwilioResponse();
139
response.Message(message);
140
141
return response;
142
}
143
144
private static bool IsSmsRequestAccepted(string smsRequest)
145
{
146
return smsRequest.Equals("accept", StringComparison.InvariantCultureIgnoreCase) ||
147
smsRequest.Equals("yes", StringComparison.InvariantCultureIgnoreCase);
148
}
149
}
150
}

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 C# Helper Library(link takes you to an external page) to search for and buy a new phone number to associate with the reservation. When we purchase 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.

AirTNG.Web/Domain/PhoneNumber/Purchaser.cs

1
using System.Linq;
2
using AirTNG.Web.Domain.Twilio;
3
using Twilio;
4
5
namespace AirTNG.Web.Domain.PhoneNumber
6
{
7
public interface IPurchaser
8
{
9
IncomingPhoneNumber Purchase(string areaCode);
10
}
11
12
public class Purchaser : IPurchaser
13
{
14
private readonly TwilioRestClient _client;
15
16
public Purchaser() : this(new TwilioRestClient(Credentials.AccountSID, Credentials.AuthToken)) { }
17
18
public Purchaser(TwilioRestClient client)
19
{
20
_client = client;
21
}
22
23
/// <summary>
24
/// Purchase the first available phone number.
25
/// </summary>
26
/// <param name="areaCode">The area code</param>
27
/// <returns>The purchased phone number</returns>
28
public IncomingPhoneNumber Purchase(string areaCode)
29
{
30
var phoneNumberOptions = new PhoneNumberOptions
31
{
32
PhoneNumber = SearchForFirstAvailablePhoneNumber(areaCode),
33
VoiceApplicationSid = Credentials.ApplicationSID
34
};
35
36
return _client.AddIncomingPhoneNumber(phoneNumberOptions);
37
}
38
39
private string SearchForFirstAvailablePhoneNumber(string areaCode)
40
{
41
var searchParams = new AvailablePhoneNumberListRequest
42
{
43
AreaCode = areaCode,
44
VoiceEnabled = true,
45
SmsEnabled = true
46
};
47
48
return _client
49
.ListAvailableLocalPhoneNumbers("US", searchParams)
50
.AvailablePhoneNumbers
51
.First() // We're only interested in the first available phone number.
52
.PhoneNumber;
53
}
54
}
55
}

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.


When someone sends an SMS or calls one of the Twilio numbers you have configured, Twilio makes a request to the URL you set in the TwiML app. In this request, Twilio includes some useful information including:

  • The From number that initially called or sent an SMS.
  • The To Twilio number that triggered this request.

Take a look at Twilio's SMS Documentation and Twilio's Voice Documentation for a full list of the parameters you can use.

In our controller, we use the to parameter sent by Twilio to find a reservation that has the number we bought stored in it, as this is the number both hosts and guests will call and send SMS to.

AirTNG.Web/Controllers/PhoneExchangeController.cs

1
using System.Threading.Tasks;
2
using System.Web.Mvc;
3
using AirTNG.Web.Models.Repository;
4
using Twilio.TwiML;
5
using Twilio.TwiML.Mvc;
6
7
namespace AirTNG.Web.Controllers
8
{
9
public class PhoneExchangeController : TwilioController
10
{
11
private readonly IReservationsRepository _repository;
12
13
public PhoneExchangeController() : this(new ReservationsRepository()) { }
14
15
public PhoneExchangeController(IReservationsRepository repository)
16
{
17
_repository = repository;
18
}
19
20
// POST: PhoneExchange/InterconnectUsingSms
21
[HttpPost]
22
public async Task<ActionResult> InterconnectUsingSms(string from, string to, string body)
23
{
24
var outgoingPhoneNumber = await GatherOutgoingPhoneNumberAsync(from, to);
25
26
var response = new TwilioResponse();
27
response.Message(body, new {to = outgoingPhoneNumber});
28
29
return TwiML(response);
30
}
31
32
// POST: PhoneExchange/InterconnectUsingVoice
33
[HttpPost]
34
public async Task<ActionResult> InterconnectUsingVoice(string from, string to)
35
{
36
var outgoingPhoneNumber = await GatherOutgoingPhoneNumberAsync(from, to);
37
38
var response = new TwilioResponse();
39
response.Play("http://howtodocs.s3.amazonaws.com/howdy-tng.mp3");
40
response.Dial(outgoingPhoneNumber);
41
42
return TwiML(response);
43
}
44
45
private async Task<string> GatherOutgoingPhoneNumberAsync(
46
string incomingPhoneNumber, string anonymousPhoneNumber)
47
{
48
var outgoingPhoneNumber = string.Empty;
49
var reservation = await _repository.FindByAnonymousPhoneNumberAsync(anonymousPhoneNumber);
50
51
// Connect from Guest to Host
52
if (reservation.Guest.PhoneNumber.Equals(incomingPhoneNumber))
53
{
54
outgoingPhoneNumber = reservation.Host.PhoneNumber;
55
}
56
57
// Connect from Host to Guest
58
if (reservation.Host.PhoneNumber.Equals(incomingPhoneNumber))
59
{
60
outgoingPhoneNumber = reservation.Guest.PhoneNumber;
61
}
62
63
return outgoingPhoneNumber;
64
}
65
}
66
}

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


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 sent by the host, we forward it on to the guest. Conversely, if the original message was sent by the guest, we forward it to the host.

To find the outgoing number we'll use the GatherOutgoingPhoneNumberAsync helper method.

AirTNG.Web/Controllers/PhoneExchangeController.cs

1
using System.Threading.Tasks;
2
using System.Web.Mvc;
3
using AirTNG.Web.Models.Repository;
4
using Twilio.TwiML;
5
using Twilio.TwiML.Mvc;
6
7
namespace AirTNG.Web.Controllers
8
{
9
public class PhoneExchangeController : TwilioController
10
{
11
private readonly IReservationsRepository _repository;
12
13
public PhoneExchangeController() : this(new ReservationsRepository()) { }
14
15
public PhoneExchangeController(IReservationsRepository repository)
16
{
17
_repository = repository;
18
}
19
20
// POST: PhoneExchange/InterconnectUsingSms
21
[HttpPost]
22
public async Task<ActionResult> InterconnectUsingSms(string from, string to, string body)
23
{
24
var outgoingPhoneNumber = await GatherOutgoingPhoneNumberAsync(from, to);
25
26
var response = new TwilioResponse();
27
response.Message(body, new {to = outgoingPhoneNumber});
28
29
return TwiML(response);
30
}
31
32
// POST: PhoneExchange/InterconnectUsingVoice
33
[HttpPost]
34
public async Task<ActionResult> InterconnectUsingVoice(string from, string to)
35
{
36
var outgoingPhoneNumber = await GatherOutgoingPhoneNumberAsync(from, to);
37
38
var response = new TwilioResponse();
39
response.Play("http://howtodocs.s3.amazonaws.com/howdy-tng.mp3");
40
response.Dial(outgoingPhoneNumber);
41
42
return TwiML(response);
43
}
44
45
private async Task<string> GatherOutgoingPhoneNumberAsync(
46
string incomingPhoneNumber, string anonymousPhoneNumber)
47
{
48
var outgoingPhoneNumber = string.Empty;
49
var reservation = await _repository.FindByAnonymousPhoneNumberAsync(anonymousPhoneNumber);
50
51
// Connect from Guest to Host
52
if (reservation.Guest.PhoneNumber.Equals(incomingPhoneNumber))
53
{
54
outgoingPhoneNumber = reservation.Host.PhoneNumber;
55
}
56
57
// Connect from Host to Guest
58
if (reservation.Host.PhoneNumber.Equals(incomingPhoneNumber))
59
{
60
outgoingPhoneNumber = reservation.Guest.PhoneNumber;
61
}
62
63
return outgoingPhoneNumber;
64
}
65
}
66
}

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


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.

AirTNG.Web/Controllers/PhoneExchangeController.cs

1
using System.Threading.Tasks;
2
using System.Web.Mvc;
3
using AirTNG.Web.Models.Repository;
4
using Twilio.TwiML;
5
using Twilio.TwiML.Mvc;
6
7
namespace AirTNG.Web.Controllers
8
{
9
public class PhoneExchangeController : TwilioController
10
{
11
private readonly IReservationsRepository _repository;
12
13
public PhoneExchangeController() : this(new ReservationsRepository()) { }
14
15
public PhoneExchangeController(IReservationsRepository repository)
16
{
17
_repository = repository;
18
}
19
20
// POST: PhoneExchange/InterconnectUsingSms
21
[HttpPost]
22
public async Task<ActionResult> InterconnectUsingSms(string from, string to, string body)
23
{
24
var outgoingPhoneNumber = await GatherOutgoingPhoneNumberAsync(from, to);
25
26
var response = new TwilioResponse();
27
response.Message(body, new {to = outgoingPhoneNumber});
28
29
return TwiML(response);
30
}
31
32
// POST: PhoneExchange/InterconnectUsingVoice
33
[HttpPost]
34
public async Task<ActionResult> InterconnectUsingVoice(string from, string to)
35
{
36
var outgoingPhoneNumber = await GatherOutgoingPhoneNumberAsync(from, to);
37
38
var response = new TwilioResponse();
39
response.Play("http://howtodocs.s3.amazonaws.com/howdy-tng.mp3");
40
response.Dial(outgoingPhoneNumber);
41
42
return TwiML(response);
43
}
44
45
private async Task<string> GatherOutgoingPhoneNumberAsync(
46
string incomingPhoneNumber, string anonymousPhoneNumber)
47
{
48
var outgoingPhoneNumber = string.Empty;
49
var reservation = await _repository.FindByAnonymousPhoneNumberAsync(anonymousPhoneNumber);
50
51
// Connect from Guest to Host
52
if (reservation.Guest.PhoneNumber.Equals(incomingPhoneNumber))
53
{
54
outgoingPhoneNumber = reservation.Host.PhoneNumber;
55
}
56
57
// Connect from Host to Guest
58
if (reservation.Host.PhoneNumber.Equals(incomingPhoneNumber))
59
{
60
outgoingPhoneNumber = reservation.Guest.PhoneNumber;
61
}
62
63
return outgoingPhoneNumber;
64
}
65
}
66
}

That's it! We've just implemented anonymous communications that allow your customers to connect while protecting their privacy.


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

IVR: Screening and Recording

Save time and remove distractions by adding call screening and recording to your IVR (interactive voice response) system

Automated Survey

Instantly collect structured data from your users with a survey conducted over a voice call or SMS text messages.

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.