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

Masked Phone Numbers with Node.js and Express


This Express(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 message and the guest's information which is grabbed from the logged in user.

routes/reservations.js


_108
var twilio = require('twilio');
_108
var MessagingResponse = require('twilio').twiml.MessagingResponse;
_108
var express = require('express');
_108
var router = express.Router();
_108
var Property = require('../models/property');
_108
var Reservation = require('../models/reservation');
_108
var User = require('../models/user');
_108
var notifier = require('../lib/notifier');
_108
var purchaser = require('../lib/purchaser');
_108
var middleware = require('../lib/middleware');
_108
_108
router.get('/', middleware.isAuthenticated, function (req, res) {
_108
var userId = req.user.id;
_108
Reservation.find({})
_108
.deepPopulate('property property.owner guest')
_108
.then(function (reservations) {
_108
var hostReservations = reservations.filter(function (reservation) {
_108
return reservation.property.owner.id === userId;
_108
});
_108
_108
res.render('reservations/index', { reservations: hostReservations, user: req.user });
_108
});
_108
});
_108
_108
// POST: /reservations
_108
router.post('/', function (req, res) {
_108
var propertyId = req.body.propertyId;
_108
var user = req.user;
_108
_108
Property.findOne({ _id: propertyId })
_108
.then(function (property) {
_108
var reservation = new Reservation({
_108
message: req.body.message,
_108
property: propertyId,
_108
guest: user.id
_108
});
_108
_108
return reservation.save();
_108
})
_108
.then(function () {
_108
notifier.sendNotification();
_108
res.redirect('/properties');
_108
})
_108
.catch(function(err) {
_108
console.log(err);
_108
});
_108
});
_108
_108
// POST: /reservations/handle
_108
router.post('/handle', twilio.webhook({validate: false}), function (req, res) {
_108
var from = req.body.From;
_108
var smsRequest = req.body.Body;
_108
_108
var smsResponse;
_108
_108
User.findOne({phoneNumber: from})
_108
.then(function (host) {
_108
return Reservation.findOne({status: 'pending'})
_108
.deepPopulate('property property.owner guest')
_108
})
_108
.then(function (reservation) {
_108
if (reservation === null) {
_108
throw 'No pending reservations';
_108
}
_108
_108
var hostAreaCode = reservation.property.owner.areaCode;
_108
_108
var phoneNumber = purchaser.purchase(hostAreaCode);
_108
var reservationPromise = Promise.resolve(reservation);
_108
_108
return Promise.all([phoneNumber, reservationPromise]);
_108
})
_108
.then(function (data) {
_108
var phoneNumber = data[0];
_108
var reservation = data[1];
_108
_108
if (isSmsRequestAccepted(smsRequest)) {
_108
reservation.status = "confirmed";
_108
reservation.phoneNumber = phoneNumber;
_108
} else {
_108
reservation.status = "rejected";
_108
}
_108
return reservation.save();
_108
})
_108
.then(function (reservation) {
_108
var message = "You have successfully " + reservation.status + " the reservation";
_108
respond(res, message);
_108
})
_108
.catch(function (err) {
_108
console.log(err);
_108
var message = "Sorry, it looks like you do not have any reservations to respond to";
_108
respond(res, message);
_108
});
_108
});
_108
_108
var isSmsRequestAccepted = function (smsRequest) {
_108
return smsRequest.toLowerCase() === 'accept';
_108
};
_108
_108
var respond = function(res, message) {
_108
var messagingResponse = new MessagingResponse();
_108
messagingResponse.message(message);
_108
_108
res.type('text/xml');
_108
res.send(messagingResponse.toString());
_108
}
_108
_108
module.exports = router;

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 was reserved. Learn how to automate this process in our first AirTNG tutorial, Workflow Automation(link takes you to an external page).

routes/reservations.js


_108
var twilio = require('twilio');
_108
var MessagingResponse = require('twilio').twiml.MessagingResponse;
_108
var express = require('express');
_108
var router = express.Router();
_108
var Property = require('../models/property');
_108
var Reservation = require('../models/reservation');
_108
var User = require('../models/user');
_108
var notifier = require('../lib/notifier');
_108
var purchaser = require('../lib/purchaser');
_108
var middleware = require('../lib/middleware');
_108
_108
router.get('/', middleware.isAuthenticated, function (req, res) {
_108
var userId = req.user.id;
_108
Reservation.find({})
_108
.deepPopulate('property property.owner guest')
_108
.then(function (reservations) {
_108
var hostReservations = reservations.filter(function (reservation) {
_108
return reservation.property.owner.id === userId;
_108
});
_108
_108
res.render('reservations/index', { reservations: hostReservations, user: req.user });
_108
});
_108
});
_108
_108
// POST: /reservations
_108
router.post('/', function (req, res) {
_108
var propertyId = req.body.propertyId;
_108
var user = req.user;
_108
_108
Property.findOne({ _id: propertyId })
_108
.then(function (property) {
_108
var reservation = new Reservation({
_108
message: req.body.message,
_108
property: propertyId,
_108
guest: user.id
_108
});
_108
_108
return reservation.save();
_108
})
_108
.then(function () {
_108
notifier.sendNotification();
_108
res.redirect('/properties');
_108
})
_108
.catch(function(err) {
_108
console.log(err);
_108
});
_108
});
_108
_108
// POST: /reservations/handle
_108
router.post('/handle', twilio.webhook({validate: false}), function (req, res) {
_108
var from = req.body.From;
_108
var smsRequest = req.body.Body;
_108
_108
var smsResponse;
_108
_108
User.findOne({phoneNumber: from})
_108
.then(function (host) {
_108
return Reservation.findOne({status: 'pending'})
_108
.deepPopulate('property property.owner guest')
_108
})
_108
.then(function (reservation) {
_108
if (reservation === null) {
_108
throw 'No pending reservations';
_108
}
_108
_108
var hostAreaCode = reservation.property.owner.areaCode;
_108
_108
var phoneNumber = purchaser.purchase(hostAreaCode);
_108
var reservationPromise = Promise.resolve(reservation);
_108
_108
return Promise.all([phoneNumber, reservationPromise]);
_108
})
_108
.then(function (data) {
_108
var phoneNumber = data[0];
_108
var reservation = data[1];
_108
_108
if (isSmsRequestAccepted(smsRequest)) {
_108
reservation.status = "confirmed";
_108
reservation.phoneNumber = phoneNumber;
_108
} else {
_108
reservation.status = "rejected";
_108
}
_108
return reservation.save();
_108
})
_108
.then(function (reservation) {
_108
var message = "You have successfully " + reservation.status + " the reservation";
_108
respond(res, message);
_108
})
_108
.catch(function (err) {
_108
console.log(err);
_108
var message = "Sorry, it looks like you do not have any reservations to respond to";
_108
respond(res, message);
_108
});
_108
});
_108
_108
var isSmsRequestAccepted = function (smsRequest) {
_108
return smsRequest.toLowerCase() === 'accept';
_108
};
_108
_108
var respond = function(res, message) {
_108
var messagingResponse = new MessagingResponse();
_108
messagingResponse.message(message);
_108
_108
res.type('text/xml');
_108
res.send(messagingResponse.toString());
_108
}
_108
_108
module.exports = router;

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

lib/purchaser.js


_26
var config = require('../config');
_26
var client = require('twilio')(config.accountSid, config.authToken);
_26
_26
var purchase = function (areaCode) {
_26
var phoneNumber;
_26
_26
return client.availablePhoneNumbers('US').local.list({
_26
areaCode: areaCode,
_26
voiceEnabled: true,
_26
smsEnabled: true
_26
}).then(function(searchResults) {
_26
if (searchResults.availablePhoneNumbers.length === 0) {
_26
throw { message: 'No numbers found with that area code' };
_26
}
_26
_26
return client.incomingPhoneNumbers.create({
_26
phoneNumber: searchResults.availablePhoneNumbers[0].phoneNumber,
_26
voiceApplicationSid: config.applicationSid,
_26
smsApplicationSid: config.applicationSid
_26
});
_26
}).then(function(number) {
_26
return number.phone_number;
_26
});
_26
}
_26
_26
exports.purchase = purchase;

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

routes/commuter.js


_71
var twilio = require('twilio');
_71
var VoiceResponse = require('twilio').twiml.VoiceResponse;
_71
var MessagingResponse = require('twilio').twiml.MessagingResponse;
_71
var express = require('express');
_71
var router = express.Router();
_71
var Reservation = require('../models/reservation');
_71
_71
// POST: /commuter/use-sms
_71
router.post('/use-sms', twilio.webhook({ validate: false }), function (req, res) {
_71
from = req.body.From;
_71
to = req.body.To;
_71
body = req.body.Body;
_71
_71
gatherOutgoingNumber(from, to)
_71
.then(function (outgoingPhoneNumber) {
_71
var messagingResponse = new MessagingResponse();
_71
messagingResponse.message({ to: outgoingPhoneNumber }, body);
_71
_71
res.type('text/xml');
_71
res.send(messagingResponse.toString());
_71
})
_71
});
_71
_71
// POST: /commuter/use-voice
_71
router.post('/use-voice', twilio.webhook({ validate: false }), function (req, res) {
_71
from = req.body.From;
_71
to = req.body.To;
_71
body = req.body.Body;
_71
_71
gatherOutgoingNumber(from, to)
_71
.then(function (outgoingPhoneNumber) {
_71
var voiceResponse = new VoiceResponse();
_71
voiceResponse.play('http://howtodocs.s3.amazonaws.com/howdy-tng.mp3');
_71
voiceResponse.dial(outgoingPhoneNumber);
_71
_71
res.type('text/xml');
_71
res.send(voiceResponse.toString());
_71
})
_71
});
_71
_71
var gatherOutgoingNumber = function (incomingPhoneNumber, anonymousPhoneNumber) {
_71
var phoneNumber = anonymousPhoneNumber;
_71
_71
return Reservation.findOne({ phoneNumber: phoneNumber })
_71
.deepPopulate('property property.owner guest')
_71
.then(function (reservation) {
_71
var hostPhoneNumber = formattedPhoneNumber(reservation.property.owner);
_71
var guestPhoneNumber = formattedPhoneNumber(reservation.guest);
_71
_71
// Connect from Guest to Host
_71
if (guestPhoneNumber === incomingPhoneNumber) {
_71
outgoingPhoneNumber = hostPhoneNumber;
_71
}
_71
_71
// Connect from Host to Guest
_71
if (hostPhoneNumber === incomingPhoneNumber) {
_71
outgoingPhoneNumber = guestPhoneNumber;
_71
}
_71
_71
return outgoingPhoneNumber;
_71
})
_71
.catch(function (err) {
_71
console.log(err);
_71
});
_71
}
_71
_71
var formattedPhoneNumber = function(user) {
_71
return "+" + user.countryCode + user.areaCode + user.phoneNumber;
_71
};
_71
_71
module.exports = router;

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 gatherOutgoingNumber helper method.

routes/commuter.js


_71
var twilio = require('twilio');
_71
var VoiceResponse = require('twilio').twiml.VoiceResponse;
_71
var MessagingResponse = require('twilio').twiml.MessagingResponse;
_71
var express = require('express');
_71
var router = express.Router();
_71
var Reservation = require('../models/reservation');
_71
_71
// POST: /commuter/use-sms
_71
router.post('/use-sms', twilio.webhook({ validate: false }), function (req, res) {
_71
from = req.body.From;
_71
to = req.body.To;
_71
body = req.body.Body;
_71
_71
gatherOutgoingNumber(from, to)
_71
.then(function (outgoingPhoneNumber) {
_71
var messagingResponse = new MessagingResponse();
_71
messagingResponse.message({ to: outgoingPhoneNumber }, body);
_71
_71
res.type('text/xml');
_71
res.send(messagingResponse.toString());
_71
})
_71
});
_71
_71
// POST: /commuter/use-voice
_71
router.post('/use-voice', twilio.webhook({ validate: false }), function (req, res) {
_71
from = req.body.From;
_71
to = req.body.To;
_71
body = req.body.Body;
_71
_71
gatherOutgoingNumber(from, to)
_71
.then(function (outgoingPhoneNumber) {
_71
var voiceResponse = new VoiceResponse();
_71
voiceResponse.play('http://howtodocs.s3.amazonaws.com/howdy-tng.mp3');
_71
voiceResponse.dial(outgoingPhoneNumber);
_71
_71
res.type('text/xml');
_71
res.send(voiceResponse.toString());
_71
})
_71
});
_71
_71
var gatherOutgoingNumber = function (incomingPhoneNumber, anonymousPhoneNumber) {
_71
var phoneNumber = anonymousPhoneNumber;
_71
_71
return Reservation.findOne({ phoneNumber: phoneNumber })
_71
.deepPopulate('property property.owner guest')
_71
.then(function (reservation) {
_71
var hostPhoneNumber = formattedPhoneNumber(reservation.property.owner);
_71
var guestPhoneNumber = formattedPhoneNumber(reservation.guest);
_71
_71
// Connect from Guest to Host
_71
if (guestPhoneNumber === incomingPhoneNumber) {
_71
outgoingPhoneNumber = hostPhoneNumber;
_71
}
_71
_71
// Connect from Host to Guest
_71
if (hostPhoneNumber === incomingPhoneNumber) {
_71
outgoingPhoneNumber = guestPhoneNumber;
_71
}
_71
_71
return outgoingPhoneNumber;
_71
})
_71
.catch(function (err) {
_71
console.log(err);
_71
});
_71
}
_71
_71
var formattedPhoneNumber = function(user) {
_71
return "+" + user.countryCode + user.areaCode + user.phoneNumber;
_71
};
_71
_71
module.exports = router;

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

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


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

IVR: Phone Tree

Easily route callers to the right people and information with an 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. Learn how to create your own survey in the language and framework of your choice.

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: