Getting Started with Number Masking

Often there is a requirement, where you want to connect two people on a call without revealing their original phone numbers. Some common examples of this are in two sided marketplaces such as ride sharing companies, stay booking sites and E-commerce websites.

This process of establishing a call between two users without revealing their contact information is what is termed as number masking.

A simple Number Masking example

In this example we will build a simple Number Masking application for a Pizza delivery company. Assume that the company does not want to reveal the numbers of the agents to the customers and vice versa. We will be implementing the following example.

  1. There is a Plivo number owned by the company. Whenever the customer calls this number, he will be connected to an agent who is free.
  2. When the Agent calls the Plivo number, his call will be connected to the customer.
  3. In both of the above cases the customer will not know the real phone number of the agent and the agent will not know the phone number of the customer.

Implementing Number Masking

Step 1: Let us first create a couple of agents using the following code snippet.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from flask import Flask, Response, request


app = Flask(__name__)


# Let's assume I have two delivery agents with me, and both of them are
# available for pizza delivery now. As and when there is a new order, we
# then map the customer to the delivery agent.


agent_customer_mapping = {
    '1415000001': {
        'customer_number': None,
        'customer_masked_number': None
    },
    '1415000000': {
        'customer_number': None,
        'customer_masked_number': None
    }
}
agent_customer_reverse_mapping = {}

Step 2: Let us implement a couple of functions that would return an XML that we want for speaking and dialing out to a number.

1
2
3
4
5
6
7
8
9
10
11
12
def speak(text):
    return Response(
        '<Response><Speak>{text}</Speak></Response>'.format(text=text),
        mimetype='text/xml')


def call_and_connect_number(caller_id, to_number):
    return Response(
        "<Response><Dial callerId='{from_number}'><Number>{to_number}</Number>"
        "</Dial></Response>".format(
            from_number=caller_id, to_number=to_number),
        mimetype='text/xml')

Step 3: Let us next implement a function to handle inbound calls on the Plivo phone number. When an incoming call is received on the Plivo number it can either be from the customer or from an agent.

If the call is from an Agent

  1. We check if there are any customers assigned to the agent. In case there are no customers assigned we Speak a message stating that no customer is assigned.
  2. In case there is a customer assigned, then we initiate a call to the customer with the masked number

If the call is from a Customer

  1. We check if the customer has an existing order with the company and has an agent assigned.
  2. In case an agent is assigned, we will forward the call to the agent with the masked caller ID. Otherwise if there is no agent assigned, we are checking if there are any available agents and routing the call to the assigned agent by masking the caller ID.
  3. In case there are no agents, then we Speak a message stating that No agents are available.

Here is the code snippet for the above behaviour.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
def handle_inbound_calls(from_number, masked_number):
    # Let's first check if the from_number is an agent number
    if from_number in agent_customer_mapping:
        customer_number = agent_customer_mapping[from_number][
            'customer_number']
        if not customer_number:
            return speak(
                'No customer is currently assigned to you. We will call you '
                'back once we have someone requesting for a new delivery.')

        return call_and_connect_number(
            caller_id=masked_number, to_number=customer_number)

    if from_number not in agent_customer_reverse_mapping:
        # You will usually have some business logic here to retrieve or place
        # an order
        selected_agent = None

        # Let's select an agent now
        for agent in agent_customer_mapping:
            if not agent_customer_mapping[agent]['customer_number']:
                selected_agent = agent
                break

        if not selected_agent:  # We are busy!
            return speak(
                'All agents are currently busy. We will call you back '
                'once we have someone available to deliver your order')

        agent_customer_mapping[selected_agent] = {
            'customer_number': from_number,
            'customer_masked_number': masked_number
        }

        agent_customer_reverse_mapping[from_number] = {
            'agent_number': selected_agent,
            'agent_masked_number': masked_number
        }

        agent_number = selected_agent
    else:
        cust_reverse_mapping = agent_customer_reverse_mapping[from_number]
        agent_number = cust_reverse_mapping['agent_number']
        agent_masked_number = cust_reverse_mapping['agent_masked_number']

        if agent_masked_number != masked_number:
            return speak('You have called an incorrect number. '
                         'Please try again calling the correct number.')

    return call_and_connect_number(
        caller_id=masked_number, to_number=agent_number)


def handle_outbound_calls(from_number, to_number):
    # Not handling outbound calls for now
    return Response('<Response>', mimetype='text/xml')


@app.route('/handle_incoming_calls', methods=['POST'])
def index():
    print_request_details(request)

    # Let's see who is calling
    from_number = request.form.get('From')
    to_number = request.form.get('To')
    direction = request.form.get('Direction')

    if direction == 'inbound':
        return handle_inbound_calls(from_number, to_number)
    elif direction == 'outbound':
        return handle_outbound_calls(from_number, to_number)
    else:
        raise Exception('Unknown direction found: ' + direction)

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

Here is the Github Repo that has the complete source code of this tutorial.