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

# RCS Universal Profile 3.0 rich deep-link guardrails

> How Orbit gates GSMA Universal Profile 3.0 rich deep-links on RCS — the safe-by-default posture, the guardrail verdict, per-recipient capability handling, and the audit trail.

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

<Warning>
  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.
</Warning>

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

| Field       | Shape                                                           | Meaning                                                                                                                                         |
| ----------- | --------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `decision`  | `allow` \| `review` \| `block`                                  | Aggregate outcome. Any `block`-severity reason forces `block`; otherwise any `warn` reason yields `review` (hold); otherwise `allow`.           |
| `blocked`   | boolean                                                         | Convenience flag: `true` iff `decision === "block"`.                                                                                            |
| `reasons[]` | `{ code, severity, detail }`                                    | Every signal that fired, in evaluation order. `severity` is `block` (deny), `warn` (hold), or `info` (FYI).                                     |
| `flags`     | `{ requiresUniversalProfile3, requiresE2ee, contentScanBlind }` | Derived facts about the send, carried to the audit trail. `contentScanBlind` marks an E2EE payload the platform content scanner cannot inspect. |

The reason codes, quoted from the evaluator:

| Code                     | Severity | When it fires                                                                                                                                                                      |
| ------------------------ | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `rollout_up3_disabled`   | block    | The send requests `universal_profile_3` but the tenant's UP3.0 rollout flag is off.                                                                                                |
| `rollout_mls_disabled`   | block    | The send requests `mls_e2ee` but the tenant's MLS/E2EE rollout flag is off.                                                                                                        |
| `recipient_opted_out`    | block    | The recipient has an active STOP on RCS or a parity channel (see below).                                                                                                           |
| `e2ee_scan_ack_required` | block    | The send requests MLS E2EE but the tenant has not acknowledged the content-scan blind spot and recorded a lawful basis.                                                            |
| `deep_link_malicious`    | block    | A rich deep-link or fallback URL scanned `malicious` and its host is not on your allowlist.                                                                                        |
| `deep_link_suspicious`   | warn     | A non-allowlisted URL scanned `suspicious` — held for review, not denied.                                                                                                          |
| `quiet_hours`            | warn     | The recipient's local hour is inside your quiet-hours window — held until the window closes, never hard-blocked.                                                                   |
| `content_scan_blind`     | info     | The send is E2EE; platform content moderation cannot inspect the payload. Surfaces on the verdict even when the send is allowed, so the blind spot is stamped on the audit record. |

**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`.

| Endpoint                              | Purpose                                                                                                                                |
| ------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| `POST /messages/rcs`                  | Send the RCS message. The guardrail evaluation runs inline on every send before dispatch.                                              |
| `GET /rcs/capability/{botId}/{phone}` | Probe a recipient's capability through a specific bot; returns `isCapable`, `features`, `universalProfileVersion`, and `featureFlags`. |

The deep-link itself is a suggestion-chip action on the send payload:

```json theme={null}
{ "action": {
    "text": "Track Order",
    "postback_data": "track_12345",
    "rich_link_action": {
      "deep_link": "https://app.acme.example/track/12345",
      "fallback_url": "https://www.acme.example/track/12345",
      "app_link": "acme://track/12345",
      "tracking_params": { "campaign": "order-updates" }
    } } }
```

`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](/channels/rcs).

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

```json theme={null}
{
  "decision": "block",
  "blocked": true,
  "reasons": [
    { "code": "deep_link_malicious", "severity": "block",
      "detail": "A rich deep-link or fallback URL was flagged malicious by URL reputation. Remove or allowlist the destination before sending." }
  ],
  "flags": { "requiresUniversalProfile3": true, "requiresE2ee": false,
             "contentScanBlind": false }
}
```

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.

## Related references

* [RCS channel](/channels/rcs) — chip contract, capability check, and the
  fallback chain.
* [RCS onboarding guide](/guides/rcs-onboarding) — brand → agent → verify →
  launch.
* [Send Gates](/compliance/send-gates) — quiet hours, DNC, RND, and the
  other send-time gates.
* [Consent Management](/compliance/consent-management) — the consent records
  STOP parity reads.
* [Opt-Out & Suppression Lists](/compliance/opt-out-suppression) — the
  suppression layer the recipient-opt-out gate honours.
