Skip to main content

RCS UP3.0 rich deep-link guardrails

A GSMA Universal Profile 3.0 rich deep-link is an RCS suggestion chip (rich_link_action) whose deep_link opens in-app on a UP3.0 handset and whose fallback_url carries every other recipient to a browser. Before Orbit dispatches a send that carries one of those URLs — or requests any of the other UP3.0 surfaces (MLS end-to-end encryption, the UP3.0 profile itself) — the composed request passes one guardrail evaluation that returns a single verdict: allow, review, or block.
Every control on this page is tenant-owned and defaults off. Orbit is the conduit, not the compliance owner: the per-tenant rollout flags ship OFF (a capability you never enabled is blocked for your sends, but you can enable it), quiet-hours only ever holds a send and only when you configured a window — absent config means the send is never held — and the deep-link allowlist is a list you curate. The two platform-side deny lines are anti-abuse, not compliance rules: honouring an explicit recipient STOP, and refusing a URL the reputation scanner flags malicious (smishing protection on the shared sender reputation). Enabling a flag is your decision to make on your own regulatory counsel.

Why the default posture is safe-by-default

UP3.0’s richer surfaces widen both the attack surface and the recipient experience: a rich deep-link hands the recipient a clickable in-app action, and MLS end-to-end encryption puts message content beyond platform inspection. Orbit’s stance is that a tenant adopts those surfaces deliberately, one capability at a time:
  • The UP3.0 profile, MLS E2EE, and E2EE acknowledgment flags all default off. A send requesting a capability the tenant never enabled is denied before dispatch — you cannot ship UP3.0 traffic by accident.
  • Outbound URL reputation gates delivery: rich deep-link and fallback URLs are scored before send, and a malicious verdict blocks unless you explicitly allowlist the host. That is anti-smishing protection of the shared sender reputation — the one platform-side gate on this page besides STOP carry-over.
  • Everything tenant-configurable stays default-open in the fail-open sense: quiet-hours with no configured window never holds a send, a malformed window decodes to “open,” and a capability the recipient does not support degrades to the chip’s fallback_url rather than failing.

The guardrail verdict

The send path composes every gate into one verdict object: The reason codes, quoted from the evaluator: Consent parity. STOP carries over cross-channel but never invents a new gating regime: an explicit recipient opt-out recorded on RCS or any parity channel suppresses the UP3.0 send exactly as it suppresses a plain RCS send. The verdict does not widen the opt-out — it refuses to let a richer channel bypass an existing one. Quiet-hours decode. The window is <startHour, endHour> in whole hours [0, 24), recipient-local, and may wrap past midnight (21 → 8 holds 21:00 through 07:59). An empty window (start === end) or an out-of-range bound decodes to open — a malformed config fails open rather than silently gating every send. A valid hour that lands inside the window produces a warn hold only; it never blocks.

Tenant operator guide

Enable a capability. The three rollout flags — UP3.0, MLS E2EE, and the E2EE content-scan acknowledgment — are per-tenant and default off. Enable UP3.0 first; an MLS send without the UP3.0 flag fails on both gates. Do not request mls_e2ee on a send until the blind-spot acknowledgment is recorded: true end-to-end encryption removes platform-side CSAM and media-threat scanning on that channel, and the platform holds every encrypted send until you own that. That acknowledgment is the conditioning that makes E2EE shippable — the platform will not encrypt-blind a channel you have not signed off. Check the recipient first. UP3.0 support is negotiated per recipient and per bot — a bot that renders rich deep-links to one MSISDN may be talking UP 2.x to the next. Run the capability check (endpoint surface below) before a campaign and read universalProfileVersion and featureFlags:
  • featureFlags.richLinkSupported — the recipient renders rich_link_action as a real deep-link. When false, the recipient opens fallback_url instead, which is why both URLs are required on the chip.
  • universalProfileVersion: "3.0" — the floor for rich deep-links and MLS. A recipient negotiating 2.x cannot render the chip; plan around it.
  • Positive verdicts cache for 24 hours, so a pre-send sweep across a segment is cheap.
Fallback when UP3.0 is unsupported. If the capability check or the on-send inline check fails, the RCS send short-circuits with RCS_NOT_SUPPORTED and your org-level cross-channel fallback chain advances (typically RCS → SMS). A recipient that supports RCS but only renders the fallback_url is not an error path — that is the chip’s designed degradation on non-UP3.0 handsets. Curate the deep-link allowlist. Hosts you own can be exempted from reputation gating: an entry matches the exact host or any subdomain (e.g. acme.example admits acme.example and t.acme.example). Use it for your own domains only — allowlisting a third-party shortener removes the smishing screen for every recipient.

Endpoint surface

All sends and capability checks are rooted at https://api.orbit.devotel.io/api/v1. The deep-link itself is a suggestion-chip action on the send payload:
deep_link (UP3.0 handset target) and fallback_url (every other recipient) are both required HTTPS URLs; app_link and tracking_params are optional metadata carried through to the rendered action. Both URLs — plus the fallback — are what the reputation scan scores before send. The full chip contract (mutual exclusion with open_url, chip limits, carousel placement) is on the RCS channel page.

Worked example: blocked, re-issued, proven

1. The dirty send gets blocked. You send a campaign chip whose fallback_url points at a third-party link shortener the reputation feed already lists. The guardrail returns:
The send never dispatches — the recipient never sees the link. 2. Re-issue with a clean URL. You repoint the chip at your own domain and send again. Now the verdict is { "decision": "allow", "reasons": [] } and the message dispatches. If the host had merely scanned suspicious, the send would hold for review (decision: "review") rather than dispatch or block — a quarantine you can release once you have verified the destination. 3. Red-team it: the host you allowlisted. Your allowlist contains acme.example. A red-team test sends a chip whose deep_link is https://acme.example.evil.example/phish — a host that ends with your domain string but is not a sub-domain of it. The match is exact-host or host.endsWith("." + entry), so acme.example.evil.example is not allowlisted, its malicious score stands, and the send is blocked. That is the safe default catching the look-alike.

Audit trail

Every verdict leaves a record you can read back:
  • The reasons[] codes (deep_link_malicious, rollout_up3_disabled, recipient_opted_out, e2ee_scan_ack_required, quiet_hours, content_scan_blind) are machine-readable and stamped on the message audit trail, so a blocked or held send explains itself post-hoc.
  • The flags.contentScanBlind marker travels with any allowed E2EE send — the audit record preserves that the platform was blind to the payload, so a later review cannot mistake “no scan finding” for “scanned clean.”
  • Blocked sends surface on the message status and in the delivery log; a capability-check short-circuit reads back as RCS_NOT_SUPPORTED, and a reputation block reads back with the verdict reasons.
  • The URL reputation findings behind a deep_link_* verdict are the same signals carried on SMS/compose scanning — one scanner, one audit shape, across channels.