This is an exmaple of the reasonable amount of work required to migrate an existing voice and messaging application from Twilio to Bandwidth.
The original Airtng demo can be found over at Twilio's website -or- Github Profile.
Masked Numbers help protect your customers' privacy by creating a seamless interaction by provisioning Bandwidth numbers on the fly. Route all voice calls and messages through your very own 3rd party. This allows you to control the interaction between your customers while putting your customer's privacy first.
Migration to Bandwidth for Masked Calling requires a bit more work and some understanding of the functional differences between the two APIs.
This guide covers:
- Configuring Callbacks with Bandwidth Applications instead of TwiML Apps
- Ordering a Bandwidth Phone Number
- Sending text messages with Bandwidth
- Working with Bandwidth XML (BXML) instead of TwiML
Similarrly to Twilio, You will need to configure Bandwidth to send requests to your application when SMSs are received.
You will need to provision at least one Bandwidth number so the application's users can make property reservations.
You can buy a number on the My Numbers Tab within your dashboard
Once you have a number you need to configure it to work with your application.
ngrok http 3000Keep in mind that our endpoint is:
http://example.ngrok.io/reservations/handleWe need to configure where to send SMS webooks for the phone number we just ordered.
Open the My Apps Tab within your dashboard. Create a new application and set the Http Callback Method to GET and set the Messaging URL to the Ngrok url.
Once the Bandwidth Application is created, you need to add the phone number ordered above to the Bandwidth Application
We now need to create a different Bandwidth Application (instead of a TwiML App) to manage the newly created Masked Numbers.
This application will provide our masked number app with both SMS webooks and Voice webooks.
Create the application and take note of the Application ID as we will use that when ordering new phone numbers.
# Your SMS Webhook URL
http://example.ngrok.io/commuter/use-sms
# Your Voice Webhook URL
http://example.ngrok.io/commuter/use-voiceAny file with:
//Twilio Code
var client = require('twilio')(config.accountSid, config.authToken);Should be updated with the Bandwidth Library and client
//Bandwidth Code
var bandwidth = require('node-bandwidth');
var client = new bandwidth({
userId: config.userId,
apiToken: config.apiToken,
apiSecret: config.apiSecret
});notifier.js sends the property owner a text message to confirm or deny a new booking. All messages are sent throught the env_var BANDWIDTH_PHONE_NUMBER;
//Twilio Code
client.messages.create({
to: phoneNumber(owner),
from: config.phoneNumber,
body: buildMessage(reservation)
})
.then(function(res) {
console.log(res.body);
})Becomes
//Bandwidth Code
client.Message.send({
to: phoneNumber(owner),
from: config.phoneNumber,
text: buildMessage(reservation)
})
.then(function (message) {
console.log(message.id);
})Bandwidth's API uses text instead of body for message content. Bandwidth's SDK also returns a message response instead of a body.
purchaser.js is responsible for searching and ordering phone numbers. Each time a new user comes on board, they're allocated a new phone number to mask their actual phone number.
The Twilio code to search for numbers:
//Twilio Code
client.availablePhoneNumbers('US').local.get({
areaCode: areaCode,
voiceEnabled: true,
smsEnabled: true
})Becomes
//Bandwidth Code
client.AvailableNumber.search("local", {
areaCode : areaCode,
quantity : 1
})All of Bandwidth's phone numbers are fully voice and sms enabled by default, so we only need to search local by areaCode.
The return types for searching numbers are different. But still very similar.
//Twilio Code
function(searchResults) {
if (searchResults.availablePhoneNumbers.length === 0) {
throw { message: 'No numbers found with that area code' };
}
return client.incomingPhoneNumbers.create({
phoneNumber: searchResults.availablePhoneNumbers[0].phoneNumber,
voiceApplicationSid: config.applicationSid,
smsApplicationSid: config.applicationSid
});
})
.then(function(number) {
return number.phone_number;
});Becomes
//Bandwidth Code
function (numbers) {
if(numbers.length === 0) {
throw { message: 'No numbers found with that area code' };
}
phoneNumber = numbers[0].number;
return client.PhoneNumber.create({
number: phoneNumber,
applicationId: config.applicationId
});
})
.then(function (number) {
console.log(number.id);
return phoneNumber;
});Note that Bandwidth phone numbers can ONLY be assigned to a single Bandwidth Application for both voice and text.
However, you can specify different callback URLS for messaging and voice within a single Bandwidth Applicaiton.
commuter.js contains the logic to actually mask the phone numbers. commuter.js used TwiML to control the call, we'll walk through how to change to BXML.
Since BXML requires your application setup to receive GET requests from Bandwidth, we need to change the routes from router.post to router.get. We also no longer need to validate Twilio webhooks, since we're replacing the Twilio client with Bandwidth.
// SMS Route
router.post('/use-sms', twilio.webhook({ validate: false }), function (req, res) {...})
// Voice Route
router.post('/use-voice', twilio.webhook({ validate: false }), function (req, res) {...})Becomes
// SMS Route
router.get('/use-sms', function (req, res) {...})
// Voice Route
router.get('/use-voice', function (req, res) {...})Again, since Bandwidth sends a GET request to our server, we'll need to update how the values are refernced.
// SMS Values
var from = req.body.From;
var to = req.body.To;
var body = req.body.Body;
// Voice Values
var from = req.body.From;
var to = req.body.To;Becomes
// SMS Values
var from = req.query.from;
var to = req.query.to;
var body = req.query.text;
// Voice Values
var from = req.query.from;
var to = req.query.to;
var eventType = req.query.eventType;NOTE Bandwidth only needs to process BXML on the answer event. We need to ignore other incoming events like incomingCall and Hangup (for this scenario)
BXML and TwiML both provide the same features, but are a bit different.
var twiml = new twilio.TwimlResponse();
twiml.message(body, { to: outgoingPhoneNumber });
res.type('text/xml');
res.send(twiml.toString());Becomes
var bxml = new bandwidth.BXMLResponse();
bxml.sendMessage(body, {
to: outgoingPhoneNumber,
from: to
});
res.type('text/xml');
res.send(bxml.toString());NOTE Bandwidth BXML requires both a to and from number. It doesn't default any values.
TwiML uses the <dial> and <play> verbs to transfer a call and play a media file. Bandwidth uses <transfer> and <playAudio> verbs.
var twiml = new twilio.TwimlResponse();
twiml.play('http://howtodocs.s3.amazonaws.com/howdy-tng.mp3');
twiml.dial(outgoingPhoneNumber);
res.type('text/xml');
res.send(twiml.toString());Becomes
if (eventType === 'answer') {
var bxml = new bandwidth.BXMLResponse();
bxml.playAudio('http://howtodocs.s3.amazonaws.com/howdy-tng.mp3');
bxml.transfer({
transferTo: outgoingPhoneNumber,
transferCallerId: to
});
res.type('text/xml');
res.send(bxml.toString());
}
else {
res.sendStatus(200);
}NOTE Bandwidth BXML Requires transferCallerId to display anything besides the original callerid for the <transfer> verb. Also, we only respond to the answer eventType. The other call events, are just sent 200 OK.
reservations.js includes the code to handle responses sent to the property owner to either approve or deny the reservation request.
We'll need to do the same thing we did for commuter.js SMS TwiML Response: Update POST to GET and change the body to text.
router.post('/handle', twilio.webhook({validate: false}), function (req, res) {...})router.get('/handle', function (req, res) {...})var from = req.body.From;
var smsRequest = req.body.Body;var from = req.query.from;
var to = req.query.to;
var smsRequest = req.query.text;Since Twilio has different default behaviour for TwiML than Bandwidth does for BXML, we need to keep track of the from, to, and text fields from the incoming message.
// Be sure to swap the direction
var smsResponse = {
to: from,
from: to
};Within the respond function, we need to modify to accept a JSON object instead of just a string message.
var respond = function(res, message) {
var twiml = new twilio.TwimlResponse();
twiml.message(message);
res.type('text/xml');
res.send(twiml.toString());
}Becomes
var respond = function(res, smsResponse) {
var bxml = new bandwidth.BXMLResponse();
bxml.sendMessage(smsResponse.message, {
to: smsResponse.to,
from: smsResponse.from
});
res.type('text/xml');
res.send(bxml.toString());
}For a detailed side-by-side comparison. Take a look at the Pull Request on Github


