Choosing a privacy primitive
Four different problems get called “privacy” in a messaging product, and each one maps to a different primitive in Orbit. Picking the wrong one leads to builds that fight the platform: a DNC list handled with masked numbers, a two-sided marketplace forced through OTP sessions. This guide gives you a decision tree across the four primitives, a TTL picker for masking sessions, the webhook security pattern they all share, the masking error taxonomy, and the anti-patterns we see in production.1. When masking is the right primitive
Number masking hides who the parties are. Use it when two people must reach each other but neither should learn the other’s real phone number — ride-share driver and rider, courier and customer, marketplace buyer and seller, clinician and patient. Each side sends to a shared proxy number; Orbit forwards to the counterparty without ever exposing a real number. Masking is not a consent or suppression control. A masked session does not decide whether you are allowed to contact someone — it only hides the identity of the parties while contact happens. Consent gating is a separate class of control, and it is tenant-owned: Orbit provides the controls (DNC pre-flight scrub, consent capture forms, suppression lists, quiet hours) as tenant-configurable gates that default open; your organization decides what to enforce. The one exception is the US federal TCPA dialing window on voice calls, which is a platform-wide guard with no opt-out. The takeaway for design: masking and consent gates run in sequence, not as alternatives. Run your consent gate first, then mask if the two parties need each other’s identity hidden. The proxy session endpoints give masking its API surface; the number masking page covers the single-use-case flow in full. The rest of this guide is the decision layer on top.2. The four privacy proxies — a decision tree
Each primitive answers a different question:
Walk the tree:
- Do you need to prove a user’s identity to yourself? Use Verify. An OTP session authenticates a number holder; it does not create a conversation channel. Stop here if trust is the only requirement.
- Do you need two parties reachable to each other indefinitely? Nothing hides numbers forever by design — a session must end. If both parties should keep a durable channel without masking, send over a normal channel and keep the identities by design; masking buys you nothing.
- Is this a bounded transaction where both sides must reach each other but shouldn’t keep each other’s number? Use proxy masking. The ride, the delivery, the appointment — it has a start and a finish, and the contact should die with the finish.
- Are you choosing a content channel rather than an identity shield? WhatsApp’s 24-hour window and RCS fallback are channel concerns. They decide how the message ships — and can ride on top of a masked session (a forwarded SMS) or an unmasked one. Decide the masking question first, then the channel.
3. TTL picker
The TTL is the masking session’s disposal plan. Set it to the longest window in which the parties legitimately need each other, not longer.
Two rules of thumb:
- Close explicitly when your app owns a completion event. Trip ended, delivery confirmed, appointment concluded — the close releases the pool number immediately and makes a finished transaction un-recontactable, which is the privacy property you actually bought.
- Let the sweeper handle silent endings. No completion event exists for a marketplace thread that goes quiet — size the TTL like the cleanup job it is, and let expiry do the work.
ttl_minutes ranges 1 to 1440 and defaults to 60. A longer TTL than the use case needs is not safety — it is a leak (section 6).
4. Webhook security
Every masking event arrives as a signed webhook, and every consumer should verify the signature before acting on it. The pattern is identical acrossproxy.session.created, proxy.session.closed, and proxy.message.forwarded:
- Compute HMAC-SHA256 over the raw request body with your webhook signing secret.
- Compare the result against the signature header using a constant-time comparison.
- Enforce the timestamp header — reject deliveries older than your replay window.
proxy.message.forwarded is one of the anti-patterns below.
5. Error taxonomy
The 409 deserves two extra notes because it encodes a race, not a bug:
- TTL race at the boundary. If your completion handler closes near the TTL, the sweeper may expire the session first. Either path releases the number — a 409 on close is a successful end state.
- Idempotent close. A 409 tells you the desired state already holds. Log and move on; alerting or retrying on it creates noise about a correct outcome.
6. Anti-patterns
- Long TTLs as insurance. Setting every session to 1440 minutes because “the sweep is free” keeps pool numbers allocated to dead conversations and — worse — leaves a completed ride contactable for a day. The TTL is the privacy boundary; a TTL longer than the use case is a leak.
- Relying on the sweeper instead of closing. The sweeper is the backstop for sessions with no completion event, not the primary disposal path. When your app knows the moment of completion, close — the number returns to the pool immediately and the parties lose reachability on your schedule, not the TTL’s.
- Not subscribing to
proxy.message.forwarded. Without it you cannot render a two-sided thread or reconcile message flow — the event tells you which direction each forwarded message moved.proxy.session.createdandproxy.session.closedalone give you lifecycle bookkeeping and leave the conversation itself invisible. - Masking where consent was the question. A DNC complaint answered with masking is the wrong primitive at the wrong layer — suppress first, mask second.
- Reusing one session across orders. A session is one bounded transaction between one pair. Re-opening yesterday’s session for a new ride shares the window across unrelated transactions and defeats the TTL model — create a session per transaction.
7. Related pages
- Number masking (single-use-case flow) — create, list, close, webhook payloads, pool fairness.
- Number lifecycle — the six-state vocabulary every number, pooled or owned, moves through.
- Webhook consumer — registration, retries, delivery semantics for the events above.
- Webhook security — the HMAC-SHA256 verification pattern referenced in section 4.
- Delivery log — search and reconcile the messages a masked session forwarded.
- Verify profiles and fallback chains — the OTP primitive from section 2.
- WhatsApp 24h window and cross-channel fallback — the channel primitives from section 2.