> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Mask two parties' numbers with a proxy session: create, route, terminate, and verify

> Walk a masking session end to end — create it for two E.164 participants, let SMS and voice route through the proxy number, close or let it expire, and confirm each step from webhook events.

# Mask two parties' numbers with a proxy session

A masking session lets two people text and call each other without either one ever seeing the other's real phone number. This guide walks a session through its full life — create, route SMS and voice through the proxy number, terminate, and verify — and ends with a worked rideshare example you can replay against your own order states.

The endpoint surface is field-level at [Number Masking](/numbers/masking) and the [Proxy API reference](/api-reference/proxy); this guide is the sequence.

## 1. What a masking session is

A session binds three numbers:

* **Party A** — one participant's real number, in E.164 (`+14155550100`).
* **Party B** — the other participant's real number.
* **The proxy number** — an E.164 number Orbit draws from the shared number pool and holds for the session's lifetime.

Party A sends to the proxy number; Orbit sees the sender matches the session and forwards to party B with the proxy number as sender. Party B replies the same way. Neither party ever sees `participant_a` or `participant_b` — the only address either side can reach or see is the proxy number.

The lifecycle has three statuses:

| Status    | Meaning                                                                                             |
| --------- | --------------------------------------------------------------------------------------------------- |
| `active`  | The session accepts traffic. Inbound messages from either participant forward to the other.         |
| `closed`  | You ended it explicitly. The proxy number is released immediately.                                  |
| `expired` | Its TTL elapsed and the scheduled sweeper marked it expired, releasing the number in the same pass. |

An expired session is a session you forgot, not a leak: the sweeper runs on schedule and hands the number back. Close explicitly when your app knows the moment of completion — a trip finished, a delivery confirmed — so the number frees up right away and neither party can recontact the other past the point your product intends.

## 2. Create a session

Post the two participants and, optionally, how long the session may stay open:

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/proxy/sessions \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "participant_a": "+14155550100",
    "participant_b": "+14155550399",
    "ttl_minutes": 45
  }'
```

```json theme={null}
{
  "data": {
    "session_id": "proxySession_3f8d2a91c4",
    "proxy_number": "+14155555678",
    "participant_a": "+14155550100",
    "participant_b": "+14155550399",
    "status": "active",
    "ttl_minutes": 45,
    "expires_at": "2026-08-27T12:45:00.000Z",
    "created_at": "2026-08-27T12:00:00.000Z"
  }
}
```

What to know when sizing the request:

* **Both participants are E.164** — leading `+`, country code, digits, nothing else. A malformed number returns `400` naming the field.
* **The two participants must differ.** Identical numbers return `400`.
* **`ttl_minutes`** is the retention window: how long the proxy number stays bound to this pair. It defaults to 60, and accepts 1 up to 1440 (24 hours). Pick the longest window the two parties legitimately need to reach each other — a pickup, a dispatch, an appointment — not longer.
* **The proxy number comes from the shared pool**, not from your organization's one-time free trial number. There is no per-organization concurrency cap, so one session per active order is normal usage.
* The `expires_at` timestamp is server-computed; store it if your product wants to warn participants before a session lapses.

Create and delete require an elevated workspace role (owner, admin, or developer) and are rate-limited to 20 requests per minute; list and get run at 60.

Reuse is a mistake — never point a second order at a number from an earlier session. Create one session per order, ride, or appointment, and let the pool recycle.

## 3. Route SMS and voice through the proxy number

There is nothing to configure. The session routes on two checks, applied to every inbound SMS or call that lands on the proxy number:

1. The proxy number resolves to an `active`, unexpired session.
2. The sender matches `participant_a` or `participant_b`.

On a match, Orbit forwards the SMS body or the call leg to the other participant, sent from the proxy number. Voice calls between the participants bridge the same way — dial the proxy number, reach the counterparty. The forward runs through the same billed message path as any other send, so wallet balance and rate limits apply as they do elsewhere in your account; the proxy session id is attached to the message record so forwarded traffic is attributable.

Three cases deliberately do **not** forward:

* **A third number texts or calls the proxy number.** The sender matches neither participant, so the traffic is dropped and nothing is delivered. Only the two bound numbers can ever use the session.
* **The session is `closed` or `expired`.** After termination the proxy number no longer resolves to anything, and inbound traffic to it goes nowhere.
* **The TTL has passed but the sweep hasn't run yet.** An active-looking row past its `expires_at` still rejects traffic — expiry is enforced on access, and the scheduled sweeper is the cleanup pass, not the gate.

Because only the proxy number is ever visible, also render it that way in your own UI: show both parties the proxy number as the conversation's address, and neither participant collects a personal number from your product surface either.

## 4. Terminate the session and redact the pairing

End a session the moment your product knows the exchange is over:

```bash theme={null}
curl -X DELETE "https://api.orbit.devotel.io/api/v1/proxy/sessions/proxySession_3f8d2a91c4" \
  -H "X-API-Key: dv_live_sk_..."
```

Closing does three things in one step: the session flips to `closed`, the proxy number is released back to the pool for reassignment, and inbound traffic to that number stops resolving. The **redaction is complete at close** — neither party can reach the other afterward, and the number that linked them no longer names either of them. Keep the session's proxy number in your conversation view after close, since it remains the only address either participant ever saw; pair it with [message history](/guides/search-message-history) redaction if your data policy also covers stored bodies.

If you close nothing, the TTL is your retention bound: at `expires_at` the session stops accepting traffic, and the sweeper marks it `expired` and releases the number in the same pass.

Edge rules worth encoding in your completion handler:

* Only an `active` session closes. A `DELETE` on an already `closed` or `expired` session returns `409` — treat it as "already ended," not as a failure to retry.
* If your completion event fires near the TTL boundary, the sweeper may win the race and the close comes back `409`. The outcome is identical — the number is released either way — so treat a `409` on close as a successful end state.
* Reconcile with `GET /api/v1/proxy/sessions?status=active`: if your app believes an order is finished but a session still reads `active`, close it. That list call is the drift check between your order states and the proxy lifecycle.

## 5. Verify the flow from webhook events

Subscribe a webhook endpoint to the three proxy events and you can verify every step above without polling — see the [webhook consumer guide](/guides/webhook-consumer) for endpoint registration and signature verification. The events arrive in this order during a healthy session:

**1. `proxy.session.created`** — the session exists and its number is bound.

```json theme={null}
{
  "session_id": "proxySession_3f8d2a91c4",
  "proxy_number": "+14155555678",
  "participant_a": "+14155550100",
  "participant_b": "+14155550399",
  "expires_at": "2026-08-27T12:45:00.000Z"
}
```

**2. `proxy.message.forwarded`** — one per forwarded message, telling you the direction:

```json theme={null}
{
  "session_id": "proxySession_3f8d2a91c4",
  "proxy_number": "+14155555678",
  "from_participant": "+14155550100",
  "to_participant": "+14155550399",
  "channel": "sms"
}
```

**3. `proxy.session.closed`** — the pairing is released:

```json theme={null}
{
  "session_id": "proxySession_3f8d2a91c4",
  "proxy_number": "+14155555678",
  "closed_at": "2026-08-27T12:38:11.000Z"
}
```

The verification sequence for a new integration:

1. Create a session between two test numbers you control. Expect `proxy.session.created` and assert the `proxy_number` is not either participant.
2. Text the proxy number from the participant A handset. Expect `proxy.message.forwarded` with `from_participant` equal to A and `to_participant` equal to B, and the message arriving on B from the proxy number.
3. Reply from B. Expect the webhook with the direction reversed — confirmation the session is symmetric.
4. Text the proxy number from a third number. Expect silence: no forward, no webhook.
5. Delete the session. Expect `proxy.session.closed`, then text the proxy number once more from A — nothing should forward.

Pass all five and your masking flow is airtight in both directions.

## Worked example: a delivery from dispatch to handoff

A courier app masks the courier–customer channel for one delivery.

**Dispatch assigns a courier.** The app creates a session with a 90-minute TTL — dispatch through handoff, plus a buffer for gate and lobby calls:

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/proxy/sessions \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{ "participant_a": "+14155550100", "participant_b": "+14155550399", "ttl_minutes": 90 }'
```

`proxy.session.created` arrives; the app shows both sides the proxy number as the contact for this delivery.

**The customer texts the proxy number** asking the courier to leave the package at the side door. The inbound resolves to the session, the sender matches party B, and the text reaches the courier from the proxy number — `proxy.message.forwarded` records the direction for the conversation view. The courier calls back the same number and the voice leg bridges to the customer.

**Handoff confirms.** The app deletes the session. `proxy.session.closed` arrives, the number goes back to the pool, and neither side can recontact the other. If the confirmation event is ever lost, the 90-minute TTL closes the session on schedule through the sweeper instead — slower, same end state.

## See Also

* [Number Masking](/numbers/masking) — field-level reference for the session endpoints, validation rules, and TTL-by-use-case table.
* [Proxy API reference](/api-reference/proxy) — the endpoint surface.
* [Webhook consumer guide](/guides/webhook-consumer) — endpoint registration, signature verification, and delivery semantics.
* [Webhook events reference](/reference/webhook-events) — the full event catalog.
* [Buy and provision numbers](/guides/buy-numbers) — when you need dedicated DIDs rather than shared-pool proxy numbers.
