Register by 10/16 for $250 off the on-site price.
Build the future of communications.
Start building for free

A Phone Number Proxy 📱↔️👻↔️📱

Girl Hiding Her Face

What would you do if you could have a dedicated phone number for anything you wanted? This blog post will show you how to create a phone number that hides your private phone number and acts as an intermediary between your phone number and other phone numbers. The use cases are many:

  • separate work and private numbers
  • international numbers while traveling (without buying a local sim)
  • an event hotline for Code of Conduct violations
  • an alias number for a Tinder date, which you can delete if things go badly…

Let’s assume we need a number for an event Code of Conduct hotline because we would prefer not to give attendees our private number. In this post, we will build just such a phone number proxy using:

Before we start, please ensure you have Python and ngrok installed. We will set up everything else as we go.

Environment and project setup

Install & configure virtualenv

We are going to use Virtualenv to set up a virtual environment for this project, in order to isolate the dependencies of this project from your other projects. Please create a file in your project directory named requirements.txt, with the following as its contents:

flask
twilio

These are the dependencies we would like to install in our virtual environment. Next we will install virtualenv to create and activate your virtual environment. Once this is done we will install the dependencies from the dependencies file you created above into your virtual environment. Run the following commands in your command-line:

# installs virtualenv (use pip3 if you are using python3)
pip install virtualenv
virtualenv venv  # sets up the environment
source venv/bin/activate  # activates the environment

# installs our dependencies (use pip3 if you are using python3)
pip install -r requirement.txt

Run ngrok

In a different terminal window, let’s start ngrok. Ngrok will allow us to expose your localhost at port 5000 to incoming requests. We will be using this to allow Twilio to communicate with our local python server. It is important that you do not close this window, so please have two terminal windows open – one for python and the other for ngrok.

ngrok http 5000

Once you type in the above your terminal window should look similar to this:

A screenshot of ngrok running in a terminal and displaying a forwarding URL of the form “http://016a0331.ngrok.io”.

Acquire a Twilio Number

Next, use the Twilio Console to acquire a Twilio number with calling and texting capability. We’ll use this as our event hotline.

A screen-capture of a user moving through the Twilio number acquisition process in the Twilio Console. After selecting voice capabilities and mobile as a type in the advanced settings, the user clicks on search and then reaches a screen with available numbers. On this screen the user purchases the first available number by clicking on buy.

Pull it all together

Now that we have our Twilio phone number, let’s set up a config file with all the important details so that we can keep them separately managed. Create a file called config.py with the following as its contents, replacing the placeholder phone numbers with your values:

TWILIO_NUMBER = "+447123456789"
PRIVATE_NUMBER = "+447987654321"

Next, create a file called server.py. We’ll be building this file up as we go along. (If you prefer to copy the entire file as one, you can find the complete file on GitHub.)

Let’s finish our setup by importing the libraries we’ll need and our configuration variables – please add these at the top of your file:

import re

from flask import Flask, request
from twilio.twiml.voice_response import Gather, VoiceResponse
from twilio.twiml.messaging_response import Message, MessagingResponse

from config import PRIVATE_NUMBER, TWILIO_NUMBER

Receive and send texts using your Twilio number

With everything set up, we want to make sure that

  • texts from an event attendee to our event hotline will reach our normal number
  • we can reply to them

After all, we do want to talk to our attendee, despite not wanting to give them our private phone number. Did I say attendee? I meant attendees! We obviously want to talk to more than one of them. However, all messages routed through our proxy will show up on our phone as being from the proxy number. To not get confused about which attendee sent us what, we will have to come up with a way to know who each text is from.

Encode & decode messages from multiple senders

To know who sent a certain text, we will use a protocol-like formatting that allows us to see both, so Hello World! becomes +447987654321: Hello World!.

Add the following function to the end of your server.py file:

def encode_message(msg, number):
    return "{}: {}".format(number, msg)

We will use the above function to get an encoded message when someone messages us. When we send a message from our phone, we will have to send it formatted the same way, and our server will decode it before it gets sent to the actual recipient. Add this decoding function to server.py:

def decode_message(msg):
    pattern = re.compile('^\+\d*')
    number = pattern.match(msg).group()
    body = msg[len(number) + 2:]
    return body, number

Integrate with Twilio Programmable SMS

Now we can use the above helper functions to integrate with Twilio. The code below will create the Flask app and set up the first endpoint. The Flask app is initialized in the first line and started in the last line with app.run(). In between, we add the /sms endpoint.

Later in this section, we will configure our Twilio number to send a request to the /sms endpoint upon receiving a text message. This request contains the message body and the number of the sender. In our endpoint code, we store both in variables. We use the sender number to determine whether the sender number is ours or an attendee’s and execute the respective clause in the if-else-statement.

If we are the sender, we expect the message body to be of the format +447987654321: Hello World!. In this case we decode the message body and then call the send_message. If the message comes from an attendee, it should be of the format Hello World!. We encode the message and send it to our number using the send_message function.

The send_message function takes as input a message and the designated recipient’s number. After creating a MessagingResponse object, we create the response with the given message and number by calling the message method on the object. Afterwards, we return the response object to finish the request.

Add the following lines to the bottom of your server.py file:

app = Flask(__name__)

@app.route('/sms', methods=['POST'])
def sms():
    from_number = request.form['From']
    msg_body = request.form['Body']

    if from_number == PRIVATE_NUMBER:  # if I am sending the sms
        msg, to_number = decode_message(msg_body)
        return send_message(msg, to_number)
    else:  # if an attendee is sending the sms
        msg = encode_message(msg_body, from_number)
        return send_message(msg, PRIVATE_NUMBER)

def send_message(msg, number):
    response = MessagingResponse()
    response.message(msg, to=number, from_=TWILIO_NUMBER)
    return str(response)

if __name__ == '__main__':
    app.run()

If you have been following along and copying the code into your server.py file, you can now save it. Run this code by typing python server.py into your terminal (python3 server.py if you are using python3). Once the code is running your server will be live at the forwarding URL ngrok displayed in your other terminal window.

Copy your server’s URL, and navigate to the dashboard page for your Twilio phone number (you can find this by navigating to the phone numbers overview and then selecting your Twilio number). You should see a form that looks like the one below. We need to paste the Ngrok URL with /sms appended to the end into “Messaging” > “A Message Comes In” as a webhook. While you’re at it, you can work ahead and fill in “Voice & Fax” > “A Call Comes In” with the URL and /call.
A screenshot of a user editing the settings in the dashboard of a Twilio phone number. The user adds “http://016a0331.ngrok.io/call” in "Voice & Fax" > "A Call Comes In" and “http://016a0331.ngrok.io/sms” in "Messaging" > "A Message Comes In".
Twilio will now know to send an HTTP POST request to your webserver when your Twilio number gets a message which will hit the endpoint we created above. You’re now ready to text attendees through your proxy and receive replies. Try it by asking a friend to send a text message to the Twilio number! Alternatively, acquire another Twilio number and send a text message using this number.

When your friend or your friendly second Twilio number sends a text to your Twilio proxy number, your normal number should receive an encoded message. Reply in the encoded format and see whether your friend gets your reply as well. If you sent the number with another Twilio number, you can see received messages in the console by navigating to “Messages Log” for your second Twilio number.

Call and Receive Calls Using Your Twilio Number

Now that texting works, we’ve almost got a fully functional proxy number! The only thing left is to set up calling.

Forward calls with TwiML

Let’s start with forwarding calls made to our Twilio number. When a call is received by our Twilio number, Twilio will make a request to our server’s /call endpoint (as we configured above). We have to create a response that instructs Twilio to forward the call to our private number when we receive that request.

TwiML is exactly such a response. TwiML is an XML encoded set of instructions that tell Twilio what to do when an incoming call (or SMS) is received.

In our case we want to redirect a received call to our personal number. The snippet below does this using the TwiML verb . We pass two arguments: the number to which we want to forward the call and caller_id. Passing caller_id as None will allow us to see the original caller on our phone. When we are making a call, we will pass the TWILIO_NUMBER as the caller_id. Thus we can program it so that when an attendee is calling you can see their number, and when you call an attendee, they see your TWILIO_NUMBER.

Copy the following function below your send_message function but above the if-statement:

def perform_call(number, caller_id=None):
    response = VoiceResponse()
    # Connect the dialed number to the incoming caller.
    response.dial(number, caller_id=caller_id)
    return str(response)

Make calls with Python

Next, we’ll add two endpoints to our server: /call and /aliasing.

/call: One endpoint to receive calls

A request to this endpoint will be made both when attendees call us as well as when we want to call attendees. Fortunately, when a request is received at this endpoint, the payload includes the number of the caller. The number of the caller will allow us to identify whether we are calling ourselves by checking whether the number is equal to our PRIVATE_NUMBER. If it’s not our number it is probably an attendee. In that case let’s simply pass the call on to the PRIVATE_NUMBER.

Add the following above the send_number function:

@app.route('/call', methods=['GET', 'POST'])
def call():
    from_number = request.form['From']

    if from_number != PRIVATE_NUMBER:  # if an attendee is calling
        return perform_call(PRIVATE_NUMBER)

If the number is our number we have some more work to do. The code below uses another TwiML verb, <Gather>, to deliver a warm welcome message asking us to dial the number of the person we wish to call, followed by the #-symbol. After we type in a number and press the #-symbol, the Gather implementation will perform the action that is specified when initializing the Gather object. In our case, it sends a request with the gathered data to the endpoint /aliasing. We will create the /aliasing endpoint in the next step.

Add the following else statement to your /call route:

    else:  # if I am calling
        response = VoiceResponse()
        g = Gather(action="/aliasing", finish_on_key="#", method="POST")
        g.say("Hello organizer. Dial the number you want to call followed by the hash symbol.")
        response.append(g)
        return str(response)

/aliasing: One endpoint to send calls

Within the aliasing endpoint, we will get the digits that we entered on the phone and perform a call to that number. In this case we will pass the TWILIO_NUMBER as the caller_id so that the person on the other end does not see our private number. Add the following right below the send_message function we just added.

@app.route("/aliasing", methods=['GET', 'POST'])
def aliasing():
    # Get the digit pressed by the user
    number = request.values.get('Digits')

    if number:
        return perform_call(number, TWILIO_NUMBER)
    return "Aliasing failed."

Test what we’ve built

Restart your server and that’s it! Dial your Twilio number from a friend’s phone and it should forward the call to your phone. Then try calling your Twilio number from your personal number, and you’ll be prompted to input a number you’d like to call. Input your friend’s number and you’ll see your Twilio number showing up on your friend’s screen.

You can see the complete project on GitHub. Go on and make as many numbers as you’d like! If you don’t need the number anymore or need to ghost, just delete the number from your Twilio account. If you’re interested in taking this project to the next level you could implement logic for a number that only redirects calls during work hours or even come up with semi-automated answers. To have this proxy number permanently running you will also want to move your project to a server instead of your local machine.

If you have any questions feel free to reach out at @naomi_pen or find me at naomi.codes.

Sign up and start building
Not ready yet? Talk to an expert.