> ## 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.

# Two-sided marketplace loop: consent gate → masked proxy → two-way SMS → Inbox → close

> Compose the full marketplace contact loop in one sequence — gate consent first, open a masked proxy session, run SMS and voice through the proxy, escalate to the Inbox when a human must step in, and close the session on completion.

# Two-sided marketplace loop: consent gate → masked proxy → two-way SMS → Inbox → close

A two-sided marketplace contact flow is not one feature — it is five, run in sequence. A rider or buyer consents to be contacted for the transaction (or is on your suppression list and must not be contacted at all). The two parties get a bounded, number-hidden channel to coordinate. Some of those conversations turn into disputes a bot cannot settle, and those need a support agent. And when the trip or delivery finishes, the channel must die so neither party retains a way to reach the other.

Each layer already has its own page — [the decision tree across privacy primitives](/guides/number-masking-guide), [the proxy session sequence](/guides/number-masking-privacy-sessions), [the raw webhook-thread loop](/guides/sms-two-way-conversation), and [the Inbox queue](/guides/inbox-setup). This page is the composition: the order to run them in, what to carry between layers, and where the seams are.

The one ordering rule that makes the rest of this page safe: **consent gating and masking run in sequence, never as alternatives.** A masked session hides who the parties are; it has no opinion on whether you may contact them. Suppression wins every time — a number your own consent controls suppressed stays suppressed even when the send goes out through a proxy.

## 1. Gate consent before any proxy session

Check reachability before a session exists, not after traffic is flowing. The gate has three tenant-owned controls, each defaulting off until you enable it:

* **Run a [DNC pre-flight scrub](/guides/dnc-preflight-scrub) on the counterparty numbers** when you onboard a batch of them — driver pools, seller lists, courier rosters. `POST /compliance/dnc/scrub` returns a per-number verdict (up to 500 per request), and both it and the single-number check require your organization's Do-Not-Call opt-in, enabled in your compliance settings. Pull the flagged numbers from the roster before any of them enter a session.
* **Capture consent with the [public consent form](/guides/public-consent-form)** when contactability is claimed rather than transactional. The hosted page lets recipients opt in or out per channel, and its submissions write to the same consent surfaces the carrier-side `STOP`/`START` keywords write to — so an opt-out from either path suppresses the number at the send path, proxy sends included.
* **Keep these controls tenant-owned.** You decide what to enforce and Orbit executes it; the platform does not impose a global gate on top of your list hygiene. The one exception is the federal TCPA dialing window on US voice calls — a platform-wide guard with no opt-out — which matters here because a marketplace loop that lets carriers call participants is subject to it like any other dial.

A consent failure at this stage is a `400` you return to your own caller, not a session you open and then regret. Gate first, mask second.

## 2. Create the proxy session per transaction

One session per order, ride, or delivery — never reuse a session across transactions, because a session is the pairing's disposal boundary and reuse smears contact across unrelated orders.

```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"
  }
}
```

Size `ttl_minutes` from the table in [Choosing a privacy primitive](/guides/number-masking-guide): 30–60 minutes for a ride-share pickup through drop-off, 60–120 for delivery with a gate/lobby buffer, up to 1440 (the max) for marketplace listings that stay live for days. A TTL longer than the transaction needs is not insurance — it leaves a finished pairing recontactable, which is the leak masking exists to prevent.

Store `session_id`, `proxy_number`, and `expires_at` on the order record. Show both parties the proxy number as the conversation's address in your own UI, so no personal number leaks through your product surface either.

## 3. Run SMS and voice through the same proxy

There is nothing to route-configure. Once the session is active, both channels resolve on the proxy number:

* **SMS.** Either participant texts the proxy number; Orbit matches the sender to the session and forwards to the other participant, sent from the proxy number.
* **Voice.** Either participant dials the proxy number; the call bridges to the counterparty. Ordering coordination is a voice habit — "meet me at the gate," "I'm downstairs" — and it shares the same masked pairing. Neither side ever sees the other's real number on either channel.

A third number that hits the proxy number is dropped — only the two bound participants can use the session. A reply from your own service (a dispatcher answering, an ETA notice) is a plain `POST /api/v1/messages/sms` addressed **to the proxy number**; Orbit resolves the session and delivers to the other participant, exactly like a handset's own reply. The full field surface and the three cases that deliberately do not forward are in the [proxy session guide](/guides/number-masking-privacy-sessions).

## 4. Handle replies in your thread map — and escalate to the Inbox when a human enters

Most marketplace threads are coordination traffic your own service handles without a queue. The wiring is the webhook loop from [Two-way SMS conversations](/guides/sms-two-way-conversation): subscribe to `message.received`, receive the reply, key a thread map on the session's address pair, and answer on the same addressing.

In a masked flow, the thread key is the session itself, not the participants — index the thread record by `session_id` from `proxy.message.forwarded` events, and your UI never needs to look up a participant to render or reply:

```ts theme={null}
// POST /webhooks/orbit — signature verified, event id deduplicated.
if (event.type === "proxy.message.forwarded") {
  const { session_id, proxy_number, from_participant, to_participant, channel } = event.data;
  await appendToThread(session_id, {
    direction: from_participant === order.riderNumber ? "rider→driver" : "driver→rider",
    proxyNumber: proxy_number,
    channel,
  });
}
```

Two upgrade triggers move a thread from this map onto the [Inbox](/guides/inbox-setup):

* **A human must answer inside a live window** — a dispute, a safety flag, a failed handoff. Create a ticket into the queue with `POST /api/v1/inbox/tickets/internal`, carrying the order it belongs to and the session id as the thread reference, so the agent opens a thread the participants already live on. If the agent needs live conversation handling rather than ticket follow-up, the Inbox's native inbound path opens the conversation on the queue instead — inbound keeps working on the same numbers, no re-wiring.
* **The conversation outgrows SMS or your team needs supervision** — an agent sees the surfaced thread context, assignment, internal notes, and SLA clocks apply. Set up channels, routing rules, and SLA policies from the [Inbox setup guide](/guides/inbox-setup) before the first escalation lands.

Whatever entered the queue, the session gates the same way for agents as for your service: replies from your workspace go **to the proxy number** only. Never hand an agent a participant's real number — the Inbox thread should hold the proxy address and nothing more.

## 5. Close the session at completion

End the session when your app owns the completion event — trip finished, delivery confirmed, dispute resolved:

```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 flips the session to `closed`, releases the proxy number to the pool, and stops all traffic resolution in one step. Handle these exactly as the taxonomy in [Choosing a privacy primitive](/guides/number-masking-guide) prescribes:

* **`409 CONFLICT` on close means already ended** — closed by an earlier handler or expired by the sweeper. Treat it as success and never retry; the desired state already holds.
* **The TTL is the fallback, not the plan.** If no completion handler runs, the session stops accepting traffic at `expires_at` and the sweeper releases the number on its next pass. An expired session is a forgotten cleanup, not a leak.
* **Reconcile drift with the list call.** `GET /api/v1/proxy/sessions?status=active` tells you which sessions your order states think are finished but the proxy lifecycle thinks are open — close anything on that list.

The verification sequence for the whole loop:

1. Gate consent; expect flagged numbers to fail at the gate before a session exists.
2. Create the session; expect `proxy.session.created`.
3. Text the proxy number from participant A; expect `proxy.message.forwarded` and delivery to B. Repeat from B and from a voice dial.
4. Escalate one thread to the Inbox and answer from the queue, addressed to the proxy number.
5. Delete the session; expect `proxy.session.closed`, then confirm nothing forwards and neither party can be recontacted.

## See also

* [Choosing a privacy primitive](/guides/number-masking-guide) — the decision layer: masking vs OTP vs channel primitives, plus the TTL table and the full error taxonomy this page defers to.
* [Number masking: proxy privacy sessions](/guides/number-masking-privacy-sessions) — the proxy sequence in full: create, route, terminate, webhook verification.
* [Two-way SMS conversations](/guides/sms-two-way-conversation) — the webhook thread map this page upgrades from.
* [Inbox setup](/guides/inbox-setup) — channels, routing rules, macros, SLA, AI drafts for the queue escalations land in.
* [DNC pre-flight scrub](/guides/dnc-preflight-scrub) and [public consent form](/guides/public-consent-form) — the tenant-owned consent gate from section 1.
* [Proxy API reference](/api-reference/proxy) — the field-level session endpoint surface.
