Proxy your phone number with Twilio Programmable Voice and Twilio Functions

July 20, 2021
Written by
Reviewed by

Header

Using Twilio's Super Network, you can quickly buy phone numbers from around the world. You can even specify a specific area code to buy a phone number that is local to a certain state, territory, or region.

With Twilio Voice, you can quickly integrate voice communication into your applications. For the application in this tutorial, you'll use Twilio phone numbers, Twilio Voice, and Twilio Functions to create a proxy phone number!

What do I mean by a proxy phone number? Before explaining that, I will discuss a similar concept. In web development, there's a concept of "reverse proxies". When you send HTTP requests to a reverse proxy, the reverse proxy forwards the traffic to a web server on a private network. This way the web server still serves a website but isn't exposed directly to the internet adding benefits like security, performance enhancements, and much more.

Similarly, a proxy phone number is a public phone number hiding your real phone number that ideally, you would want to keep private. When the proxy phone number is dialed, the call will be forwarded to your private phone number. After you dial your proxy phone number, you can instruct it to dial another phone number.

The result is that your private phone number is never revealed to the person calling your proxy phone number, or the person you are calling through your proxy phone number.

In addition to proxying phone calls, you can also proxy text messages which you'll also implement in this tutorial.

So why is this useful? Privacy.

  • You may not want to hand over your real phone number to marketeers, when signing up for services, etc. Instead, give out your proxy phone number and if you desire, swap to a new proxy phone number as needed.
  • Maybe you are a streamer who takes phone calls while on air, but you don't want to share your private phone number with your audience.
  • Maybe you enjoy demoing Twilio and you don't want to accidentally dox yourself in the process 😉

So how do you set this up? Start with buying a phone number from Twilio.

Prerequisites

The only things you'll need for this tutorial are:

To keep things as approachable as possible, you'll stick to using the Twilio Console exclusively (management web interface).You can switch to the beta console by pressing Try beta Console at the top left of the screen.

You can switch back and forth if necessary, but the experience should be largely the same.

Buy a Twilio phone number

After creating a Twilio account, you will be taken to your account dashboard. If you don't have a Twilio phone number already, click on the Get a Trial Number button.

Get Trial Number Image

Accept the phone number suggested by Twilio by clicking the Choose this Number button.

Choose Number Image

Alternatively, you can navigate to Phone Numbers > Manage > Buy a number. Here you can look for different numbers and quickly buy them:

Buy Phone Number Image

Now that you have a phone number, you can move on to configuring where Twilio gets instructions when receiving a text message or phone call.

Configure your Twilio phone number as a proxy

To turn your phone number into a proxy, you need to attach some logic to it. You can do this by configuring webhooks on your Twilio phone number.

Twilio Proxy Diagram

When your Twilio phone number is rung, Twilio will send an HTTP request to your webhook to ask for instructions. Your webhook responds with TwiML which is essentially Twilio flavored XML. This TwiML will instruct Twilio how to handle the phone call.

Here's a sample TwiML provided by Twilio:

<Response>
    <Say voice="alice">Thanks for the call. Configure your number's voice U R L to change this message.</Say>
    <Pause length="1"/>
    <Say voice="alice">Let us know if we can help you in any way during your development.</Say>
</Response>

When someone calls your phone number, and Twilio receives this TwiML as a response, the caller will hear the contents of the "Say" node in "Alice's" voice, followed by a 1-second pause, and then followed by the content of the second "Say" node.

There are a lot more capabilities available Voice TwiML, check out the TwiML for Programmable Voice documentation for more information.

In this tutorial, you will use Twilio Functions to implement your webhooks. Twilio Functions is a serverless platform based on NodeJS and provides convenient integrations with Twilio out of the box.

Of course, there are servers involved somewhere, but the servers are owned, managed, and scaled by Twilio so you don't have to worry about that yourself.

Create stub Twilio Functions

You'll need to configure two webhook URLs on your Twilio phone number: one for Voice and one for Messaging. Let's create stubs in Twilio Functions so you can configure the webhook URLs with these stubs before implementing the functionality.

Navigate to Functions > Services and click on the Create Service button. Enter "proxy" for the service name. Twilio will add a random part to the subdomain to ensure the subdomain is unique.

Click the Next button.

Modal to create a Twilio Functions service

Create a function by clicking on the Add button and selecting Add Function in the context menu.

Name your function "/voice".

Add function image

Create another function, but name it "/message".

Click the Deploy All button at the bottom of the page.

Deploy All Image

Lastly, click on the three dots next to your function names and copy both URLs somewhere for future use.

Copy URL Image

Configure webhook URLs

Now that you have the voice and message stub functions, you can configure their URLs on your Twilio phone number. Navigate to Phone Numbers > Manage > Active numbers and click on your phone number.

Twilio Console listing active phone number in the Twilio Account. Pointer is clicking on the active phone link.

This will bring you to the configuration page of your Twilio phone number. Scroll down to the Voice & Fax section and paste in the voice function URL in the text field under the A CALL COMES IN label.

Next, scroll down to the Messaging section and paste in the message function URL in the text field under the A MESSAGE COMES IN label.

Twilio phone number settings with webhook URLs configured

Finally, hit the Save button.

Now that you have configured the webhook URLs, you can call the Twilio phone number to verify the webhook.

If your account is in trial mode, you'll first hear "You have a trial account, you can remove this message at any time by upgrading to a full account. Press any key to execute your code."

Press any dial key on your phone so that Twilio will execute your webhook. Now you'll hear "Hello world".

You will not get a response when you send an SMS to the phone number, because the message function returns TwiML for Twilio Voice instead of messaging out of the box. You'll update this code later.

Implement voice proxy

Navigate back to your proxy function on Twilio Functions and click on Environment Variables under settings. Here you need to add two variables to store your proxy phone number and your private phone number (be sure to use the E.164 format when you add your numbers):

KEY

VALUE

PrivatePhoneNumber

[YOUR PRIVATE PHONE NUMBER]

ProxyPhoneNumber

[YOUR TWILIO PHONE NUMBER]

After adding these environment variables, they will be available on the context object.

Navigate back to the "/voice" function, update it with the following code and click save:

exports.handler = function (context, event, callback) {
  const privatePhoneNumber = context.PrivatePhoneNumber;
  const twiml = new Twilio.twiml.VoiceResponse();

  if (event.From === privatePhoneNumber) {
    const gather = twiml.gather({ action: '/dialNumber' });
    gather.say('Welcome to your Twilio proxy phone number. Who would you like to call?');

    // fallback message in case no response is given
    twiml.say('We didn\'t receive any input. Goodbye!');
    return callback(null, twiml);
  }
  else {
    // forward call to your private phone number
    twiml.dial(privatePhoneNumber);
    return callback(null, twiml);
  }
};

Here's what the code does:

  • Takes the private phone number you configured in the environment variables and stores it in the privatePhoneNumber variable.
  • Creates a new TwiML voice response object. With this object, you can construct the desired TwiML response.
  • The event.From property will hold the phone number of the person calling your Twilio phone number.
  • If the phone call comes from your private phone number, you will construct TwiML to prompt you for the phone number to dial.
    When you enter the phone number by pressing the digits on your phone, the pressed digits will be passed to another webhook at the relative URL /dialNumber.
    If no response is given, the following message will play: "We didn't receive any input. Goodbye!".
  • If the phone call comes from any other phone number, the TwiML constructed will instruct Twilio to dial your private phone number.

In addition to the /voice function, you need to create a new function called /dialNumber which will dial the number you pass onto it by pressing the digits on your phone.

After creating the new function, delete the existing code and add the following code, then click Save :

exports.handler = function (context, event, callback) {
  const proxyPhoneNumber = context.ProxyPhoneNumber;
  const phoneNumberToCall = event.Digits;
  const twiml = new Twilio.twiml.VoiceResponse();
  twiml.say(`Starting call to ${phoneNumberToCall.split('').join(' ')}`);

  // callerId required to make the phone call "originate" from proxyPhoneNumber
  twiml.dial({ callerId: proxyPhoneNumber }, phoneNumberToCall);

  return callback(null, twiml);
};

Here's what the /dialNumber function does:

  • Grabs your Twilio phone number, aka your proxy phone number, which you have configured in the environment variables and stores it in the proxyPhoneNumber variable.
  • Grabs the digits you entered on your phone when asked which number to call, and stores those digits in the phoneNumberToCall variable.
  • Create a new TwiML voice response object. With this object, you can  construct the desired TwiML response.
  • Before initiating the call to the desired phone number, the phone number will be repeated to you.
    The .split('').join(' ') is adding a space between every digit. This ensures that Twilio will pronounce the number as "seven one three ..." instead of "seven billion thirty-one million ...".
  • Lastly, the TwiML will instruct Twilio to dial the phoneNumberToCall and specify that the call should be made from the callerId which is set to your Twilio phone number.

Before testing out your new function, you’ll need to redeploy your proxy service with the new code by clicking Deploy All.

To test out the functionality, you will have to call another phone number that you own or someone you know owns. You will have to verify the phone number first if you are using a trial account.

You can follow the steps outlined by Twilio in this article to verify the phone number. Once you have verified the phone number, you can try out your proxy phone number.

Call your proxy phone number from your private phone number, and enter the digits of the phone number you want to call. Listen to a recording of me calling someone whom you can always rely on.
(Audio fragment by Rick Astley)

To test the functionality from the opposite direction, have the second phone number call your Twilio phone number and it will be forwarded to your private phone!

Implement message proxy

You can build a similar proxy functionality for text messaging, but you have two approaches here. Either you use TwiML to instruct Twilio to forward the messages, or you use the Twilio Client to send the message through Twilio's API.

The simplest approach is to use TwiML. Update the '/message' function with the JavaScript below and click Save:

exports.handler = function (context, event, callback) {
  const proxyPhoneNumber = context.ProxyPhoneNumber;
  const privatePhoneNumber = context.PrivatePhoneNumber;
  const twiml = new Twilio.twiml.MessagingResponse();
  let body = event.Body;

  if (event.From === privatePhoneNumber) {
    if (!body.toLowerCase().startsWith("to ")) {
      twiml.message("Please start your text with \"To [phone-number]:\" followed by your message");
      return callback(null, twiml);
    }

    // example body: to +1123-456-7890: Hi
    const targetPhoneNumber = body.substring(3, body.indexOf(':'));
    const targetMessage = body.substring(body.indexOf(':') + 1).trim();

    twiml.message({ from: proxyPhoneNumber, to: targetPhoneNumber }, targetMessage);
    twiml.message("Message sent");
    return callback(null, twiml);
  }
  else {
    twiml.message({ from: proxyPhoneNumber, to: privatePhoneNumber }, `SMS from: ${event.From}, Body: ${event.Body}`);
    return callback(null, twiml);
  }
};

The code does the following:

  • Grabs the proxy phone number and the private phone number from the context (env variables) and stores them in the proxyPhoneNumber and privatePhoneNumber variable.
  • Creates a new TwiML messaging response object. With this object, you can construct the desired TwiML response.
  • Grabs the body of the incoming text message and stores it in the body variable.
  • The event.From property holds the phone number of the person messaging your Twilio phone number.
  • If the message originated from your private phone number:
    • Verify that the message starts with "to".
      The expected body for texts coming from your phone number is "To [phone number]: [Body]".
      With this structure, you can easily extract the body you wish to send and which phone number you want to send it to.
      If the body doesn't start with "to", respond with information on the expected body structure. Otherwise, move on.
    • Knowing that the phone number will be between "To " and ":", you can extract the target phone number using body.substring(3, body.indexOf(':')).
    • Knowing that the target body will be after the colon character ":", you can extract the message using body.substring(body.indexOf(':') + 1).trim().
    • The first twiml.message invocation will construct TwiML to send a message with the target text message to the target phone number.
    • The second twiml.message will respond to your private phone number with "Message sent".
  • If the message does not originate from your private phone number:
    • TwiML is constructed to forward the incoming message to your private phone number.

The alternative using the Twilio Client looks like this:

exports.handler = function (context, event, callback) {
  const client = context.getTwilioClient();
  const proxyPhoneNumber = context.ProxyPhoneNumber;
  const privatePhoneNumber = context.PrivatePhoneNumber;
  const twiml = new Twilio.twiml.MessagingResponse();
  let body = event.Body;

  if (event.From === privatePhoneNumber) {
    if (!body.toLowerCase().startsWith("to ")) {
      twiml.message("Please start your text with \"To [phone-number]:\" followed by your message");
      return callback(null, twiml);
    }

    // example body: to +1123-456-7890: Hi
    const targetPhoneNumber = body.substring(3, body.indexOf(':'));
    const targetMessage = body.substring(body.indexOf(':') + 1).trim();

    client.messages.create({
      body: targetMessage,
      from: proxyPhoneNumber,
      to: targetPhoneNumber
    })
      .then(() => {
        twiml.message("Message sent");
        return callback(null, twiml);
      })
      .catch(error => {
        console.error(error);
        twiml.message("An error occurred, check the Twilio Console for more details.");
        return callback(error, twiml);
      });
  }
  else {
    client.messages.create({
      body: `SMS from: ${event.From}, Body: ${event.Body}`,
      from: proxyPhoneNumber,
      to: privatePhoneNumber
    })
      .then(() => callback())
      .catch(error => {
        console.error(error);
        return callback(error, null);
      });
  }
};

The logic is very similar, but instead of using TwiML, the Twilio client is used which you can get by calling context.getTwilioClient().

Some of the TwiML code has been replaced with client.messages.create({}).

The benefit of using the client is that you have callbacks for both success and errors when sending the messages.

To test this out, click the Deploy All button at the bottom of the page and send a message from your private phone number to your Twilio phone number in the correct format: "To [target phone number]: [Body]".

You will have to verify the target phone number first if you are using a trial account. You can follow the steps outlined by Twilio in this article to verify the phone number. Once you have verified the phone number, the message will be sent to the target phone number.

To test out receiving texts, have the other phone number text your Twilio phone number and you should receive a text message looking like this: "SMS From: [their phone number], Body: [their text message body]"

Enhancements and security warnings

To determine whether the call originated from your private phone number or someone else's phone number, the webhook checks whether the from property equals to your private phone number stored in the environment variables. As an additional verification method, you could add your own passcode functionality which you can prompt when dialing or texting your proxy phone.

This solution works to protect a single phone number with a single Twilio proxy phone number. When you want to proxy multiple phone numbers or create proxies as needed by your application, you want a more scalable solution. Twilio has got your back with Twilio Proxy. Using Twilio Proxy you can programmatically create a proxy session between two phone numbers and let participants communicate over a proxy instead of a direct connection.

Summary

In this blog post, you learned how to turn a Twilio phone number into a proxy phone number. You can provide the proxy phone number to prevent leaking your private phone number and thus protecting your privacy. By using Twilio Functions as webhooks for your Twilio phone number, you added logic to proxy phone calls and text messages to your private phone number.

Additional resources

Check out the following resources for more information on the topics and tools presented in this tutorial:

TwiML for Programmable Voice - learn more programmable voice capabilities available for developers in the TwiML language

TwiML Message - learn more programmable voice capabilities available for developers in the TwiML language

Twilio Functions - learn more about Twilio's serverless NodeJS offering

Twilio Proxy – Use this dedicated proxy product by Twilio to enable seamless message and voice proxies for your entire workforce

Niels Swimberghe is a Belgian Full Stack Developer and blogger working in the USA. Get in touch with Niels on Twitter @RealSwimburger and follow Niels’ blog on .NET, Azure, and web development at swimburger.net.