Designing a Scalable Phone Number Masking System for Hyperlocal Apps

Arpit Bhayani

Arpit Bhayani

Oct 31, 2022 • 7 min read

Play

Designing a Scalable Phone Number Masking System for Hyperlocal Apps

In hyperlocal delivery and ride-hailing platforms such as Gojek, Uber, and Swiggy, customers and delivery partners must frequently coordinate in real time. However, a user’s phone number is Personally Identifiable Information (PII). Exposing real phone numbers creates substantial risks: spam, harassment, targeted social engineering, and unauthorized account takeovers.

To mitigate these risks without hindering seamless communication, platforms employ Phone Number Masking using temporary, virtual phone numbers provided via CPaaS (Communications Platform as a Service) providers and telecom operators. This guide explores the architectural blueprints, data flows, and design trade-offs behind implementing phone number masking at scale.


1. The Core Problem Statement

Consider an active delivery order with two entities:

  • Customer AA with real phone number 123.
  • Delivery Partner DD with real phone number 456.
[Customer A: 123]  <--- Must communicate bidirectionally --->  [Driver D: 456]
                    WITHOUT revealing 123 or 456

The functional requirements are straightforward:

  1. Customer AA must be able to call Driver DD without knowing DD‘s real number.
  2. Driver DD must be able to call Customer AA without knowing AA‘s real number.
  3. Neither party should be able to contact the other once the transaction completes.

2. Why Static Virtual Numbers Do Not Work

A naive approach is assigning a dedicated, permanent virtual number to every registered user.

Challenges with Static Mappings:

  • Cost and Number Exhaustion: Phone numbers are limited and cost recurring rental fees. A platform with 50 million registered accounts would require 50 million rented numbers, even if only 100,000 users are active concurrently.
  • Loss of Anonymity Over Time: If Customer AA always appears to drivers as virtual number AAA, bad actors can correlate trips, map behavioral patterns, and execute targeted harassment or social engineering.

System Constraints:

  • On-Demand Allocation: Virtual numbers must be leased dynamically when a transaction begins.
  • Ephemeral Scope: The number mapping must be bound strictly to the lifetime of an active transaction/order.
  • Resource Pooling: After an order is marked delivered or cancelled, numbers are deallocated and returned to an available pool.

3. High-Level Architecture

The architecture divides responsibilities among the Order Service, Event Bus (Kafka), an internal Virtual Number Service (VNS), and Telecom Partners (such as Twilio, Exotel, Airtel, or Jio).

sequenceDiagram
    autonumber
    actor Customer
    participant OrderService as Order Service
    participant Kafka as Event Bus (Kafka)
    participant VNS as Virtual Number Service (VNS)
    participant VN_DB as VNS Inventory DB
    participant Telecom as Telecom Operator / CPaaS
    actor Driver

    OrderService->>Kafka: Emit OrderStateChanged(DRIVER_ASSIGNED)
    Kafka->>VNS: Consume DriverAssigned Event
    VNS->>VN_DB: Lease 2 Virtual Numbers (AAA, DDD)
    VNS->>OrderService: Store Virtual Mappings for Order
    OrderService-->>Customer: Display Driver Virtual Number (DDD)
    OrderService-->>Driver: Display Customer Virtual Number (AAA)

    Customer->>Telecom: Calls DDD from 123
    Telecom->>VNS: Webhook: Validate Call (From: 123, To: DDD)
    VNS-->>Telecom: Forward to 456, Mask Source as AAA
    Telecom->>Driver: Connects Call (Displays AAA, Rings 456)

Component Breakdown:

  1. Virtual Number Pool (VNS Inventory):

    • Rather than purchasing numbers synchronously on every order (which introduces latency and vendor rate-limiting risks), the VNS pre-provisions and manages a pool of rented numbers from CPaaS providers.
  2. Virtual Number Service (VNS):

    • Manages stateful leases for active transactions.
    • Validates call bridging requests sent by telecom webhooks.
  3. Order Service & Event Pipeline:

    • Tracks order lifecycles.
    • Emits events (DRIVER_ASSIGNED, ORDER_COMPLETED, ORDER_CANCELLED) over Apache Kafka.

4. End-to-End Execution Flow

Step 1: Ephemeral Number Assignment

When Customer AA places an order and Driver DD is assigned:

  1. The Order Service changes state and publishes a DriverAssignedEvent to Kafka.
  2. A VNS consumer picks up the event.
  3. VNS queries its inventory database for two available virtual numbers:
    • AAA (assigned to represent Customer AA).
    • DDD (assigned to represent Driver DD).
  4. VNS creates an active lease entry in its database:
    • Order_ID: ORD-9871
    • Party_A: Real = 123, Virtual = AAA
    • Party_D: Real = 456, Virtual = DDD
    • Status: ACTIVE
  5. The masked numbers are written back to the order record so client applications can render them:
    • Customer AA‘s UI displays the “Call Driver” button dialing DDD.
    • Driver DD‘s UI displays the “Call Customer” button dialing AAA.

Step 2: Inbound Call Interception and Bridging

When Customer AA taps “Call Driver” in their application:

  1. Customer AA‘s phone dials DDD using real caller ID 123.
  2. The carrier routes this call to the CPaaS/Telecom provider that owns the virtual number DDD.
  3. The CPaaS provider receives the incoming call on DDD from 123. However, the telecom platform has no internal context on which driver should receive this call.
  4. The Webhook Query: The CPaaS provider makes a synchronous HTTP/gRPC request to Gojek’s VNS:
    POST /api/v1/telecom/incoming-call
    {
      "caller_number": "+123",
      "dialed_virtual_number": "+DDD"
    }

Step 3: Authorization and Dynamic Bridging

VNS evaluates the request through a strict validation layer:

  • Is there an active transaction where dialed_virtual_number == DDD and caller 123 is the registered customer?
  • If the transaction is expired, cancelled, or the caller does not belong to that order, the request is rejected (returning a busy tone or disconnect).
  • If valid, VNS resolves Driver DD‘s real number (456) and responds with bridging instructions:
    {
      "action": "bridge",
      "forward_to": "+456",
      "display_caller_id": "+AAA"
    }

Step 4: Connecting the Call

  • The telecom provider bridges the audio stream to 456.
  • The telecom provider overrides the caller ID display using display_caller_id = AAA.
  • Driver DD receives an incoming call from AAA.
  • Result: Both parties communicate in real time, but neither party’s true PII is exposed.

5. Critical Edge Cases and Design Nuances

1. Multi-Order Batching (Concurrent Deliveries)

A single delivery partner may handle multiple active deliveries simultaneously (e.g., delivering food for Customer AA and groceries for Customer BB).

[Customer A] ---> Calls DDD_1 ---
                                 \---> Both ring [Driver D (456)]
[Customer B] ---> Calls DDD_2 ---/
  • If Driver DD were assigned the same virtual number across both orders, incoming calls from drivers back to customers could lead to collision ambiguity.
  • Solution: Driver DD is assigned a distinct virtual number per active transaction. When DD calls Customer AA, they dial Customer AA‘s unique virtual number AAA, allowing VNS to route accurately.

2. Guarding the Validation Step

The telecom webhook verification is a critical security barrier:

  • Without strict caller validation (caller == 123 AND dialed == DDD), any arbitrary caller who dials DDD could connect to the delivery partner or customer.
  • Strict lookup ensures that ephemeral pairing is restricted entirely to the authorized parties for the duration of the ride/delivery.

3. Grace Periods and Teardown

  • When the order transitions to DELIVERED, deallocating numbers immediately can be disruptive (e.g., the customer may need to call the driver back because an item was left behind).
  • Grace Period (Cool-off window): Systems typically keep the mapping active for a small time buffer (e.g., 5 to 10 minutes post-delivery) before running garbage collection and returning numbers back to the free pool.

6. Summary of Architectural Trade-Offs

StrategyProsCons
Static 1:1 Number AllocationSimple to implement; no dynamic routing engine needed.Prohibitively expensive; leads to number pool exhaustion; identity tracking risks.
Ephemeral Transaction-Scoped PoolingHighly cost-effective; maximizes pool reuse; strong PII isolation.Requires stateful lease management, CPaaS webhook latency, and concurrency handling.
On-Demand Vendor API PurchasingZero idle inventory cost.High latency on order placement; vulnerable to external vendor downtime and rate limits.
Pre-Provisioned Local Pool (Chosen Pattern)Sub-millisecond local allocation; decoupled from vendor checkout latency.Requires holding an idle baseline inventory of virtual numbers.

By combining pre-provisioned virtual number pools with event-driven lease lifecycles and real-time telecom routing hooks, hyperlocal platforms achieve strict customer privacy at multi-million transaction scale.

Arpit Bhayani

Principal Engineer II at Razorpay - building Agent Studio, Ex-staff engg at GCP Memorystore & Dataproc, Creator of DiceDB, ex-Amazon Fast Data, ex-Director of Engg. SRE and Data Engineering at Unacademy. I spark engineering curiosity through my no-fluff engineering videos on YouTube and my courses