Skip to main content

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, the proxy session sequence, the raw webhook-thread loop, and the Inbox queue. 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. 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 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 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.
Size ttl_minutes from the table in Choosing a privacy primitive: 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.

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: 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:
Two upgrade triggers move a thread from this map onto the Inbox:
  • 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 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:
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 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