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

# Choosing a privacy primitive: OTP, WhatsApp windows, RCS fallback, or number masking

> Decide which of Orbit's four privacy mechanisms fits your use case — Verify OTP, the WhatsApp 24-hour window, RCS fallback, or a masked proxy session — then size the TTL, secure the webhooks, and handle the error taxonomy.

# 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](/api-reference/proxy) give masking its API surface; the [number masking page](/numbers/masking) 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:

| Primitive                                                  | Question it answers                                         | Hides                                                | Lifetime                     |
| ---------------------------------------------------------- | ----------------------------------------------------------- | ---------------------------------------------------- | ---------------------------- |
| [Verify (OTP)](/guides/verify-fallback-chains)             | Is this user really holding this number right now?          | Nothing — verification, not identity masking         | One code, minutes            |
| [WhatsApp 24h window](/guides/whatsapp/24h-window)         | Is this customer still in a free-form conversation?         | Nothing — a Meta session rule governing content type | 24 hours per inbound message |
| [RCS upgrade / fallback](/concepts/cross-channel-fallback) | Can this message reach the recipient on a richer channel?   | Nothing — a delivery path, not an identity shield    | Per send                     |
| **Proxy masking**                                          | Can these two parties talk without exchanging real numbers? | Both participants' real numbers                      | TTL-bounded session          |

Walk the tree:

1. **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.
2. **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.
3. **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.
4. **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.

Two rules that fall out of the tree: masking is the only primitive that hides *both* parties; and every other primitive composes **with** masking rather than competing with it. A rider can receive an OTP on their real number while the trip itself runs over a masked proxy session.

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

| Use case                     | Legitimate window                                        | Suggested `ttl_minutes`                                           | Close explicitly?                                  |
| ---------------------------- | -------------------------------------------------------- | ----------------------------------------------------------------- | -------------------------------------------------- |
| Ride-share                   | Pickup through drop-off                                  | 30–60                                                             | Yes — close on trip completion                     |
| Delivery                     | Dispatch through handoff plus a gate/lobby buffer        | 60–120                                                            | Yes — close on confirmed delivery                  |
| Healthcare (scheduled visit) | The appointment day plus reminder traffic                | Same-day visit: a few hours. Multi-day reminder cycle: up to 1440 | Yes when the cycle has a completion event          |
| Marketplace                  | A listing that stays live for days and re-opens on reply | 1440 (the max); create a fresh session when the buyer re-engages  | Optional — the sweeper handles quiet conversations |

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 across `proxy.session.created`, `proxy.session.closed`, and `proxy.message.forwarded`:

1. Compute HMAC-SHA256 over the raw request body with your webhook signing secret.
2. Compare the result against the signature header using a constant-time comparison.
3. Enforce the timestamp header — reject deliveries older than your replay window.

The full pattern, header names, and language examples live on the [webhook security page](/webhooks/security); endpoint registration and delivery semantics are in the [webhook consumer guide](/guides/webhook-consumer). Subscribe to all three proxy events — skipping `proxy.message.forwarded` is one of the anti-patterns below.

## 5. Error taxonomy

| Status                 | Cause                                                                                                  | Correct handling                                                                                          |
| ---------------------- | ------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------- |
| `400 VALIDATION_ERROR` | `participant_a` equals `participant_b`, a participant is not E.164, or `ttl_minutes` is outside 1–1440 | Fix the request — two distinct E.164 numbers, TTL in bounds. Never retried successfully without a change. |
| `404 NOT_FOUND`        | The session id does not exist in your organization                                                     | Fetch the id from the create response; list sessions to reconcile.                                        |
| `409 CONFLICT`         | Closing a session that is already `closed` or `expired`                                                | Treat as *already ended*, not as a failure. Do not retry the close.                                       |

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.created` and `proxy.session.closed` alone 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)](/numbers/masking) — create, list, close, webhook payloads, pool fairness.
* [Number lifecycle](/concepts/number-lifecycle) — the six-state vocabulary every number, pooled or owned, moves through.
* [Webhook consumer](/guides/webhook-consumer) — registration, retries, delivery semantics for the events above.
* [Webhook security](/webhooks/security) — the HMAC-SHA256 verification pattern referenced in section 4.
* [Delivery log](/guides/delivery-log) — search and reconcile the messages a masked session forwarded.
* [Verify profiles and fallback chains](/guides/verify-fallback-chains) — the OTP primitive from section 2.
* [WhatsApp 24h window](/guides/whatsapp/24h-window) and [cross-channel fallback](/concepts/cross-channel-fallback) — the channel primitives from section 2.
