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

# Identity risk gate: fused signals — port-event recency, SIM swap, and Silent Auth

> The signal model behind POST /numbers/network-apis/identity:gate — which operator-asserted and caller-held signals the fusion kernel scores, how the additive weights land a verdict in allow / review / deny, and the per-request tuning knobs your own funnel posture is built from.

# Identity risk gate: fused signals — port-event recency, SIM swap, and Silent Auth

`POST /api/v1/numbers/network-apis/identity:gate` answers one question for your registration or login funnel: **should this phone-number–anchored transaction proceed, step up, or stop?** It fuses operator-asserted network signals and signals you already hold into a single composite risk score, then resolves one of three decisions — `allow`, `review`, or `deny` — against your thresholds. The [Verify fraud gate](/concepts/verify-fraud-gate-timeline-model) applies the same kind of fusion inside an OTP send, bound to a Verify profile; the identity gate is the same idea exposed as a general endpoint you can call at any funnel moment — registration, login, checkout, payout — before you commit to anything more expensive.

Everything the gate does is a **tenant-owned control** (see [the compliance posture](#4-configuring-your-posture)): the fuse-and-decide kernel runs only when you call it, your per-request thresholds decide what the score means, and your funnel decides what a `review` becomes. The gate itself sends nothing — every signal is a silent, read-only assertion, so calling it never initiates an outbound SMS or call.

## 1. What the gate scores, and when it fires

The gate scores one phone number in one transaction context. You call it with the number plus whichever caller-held signals you already have, and it runs two parallel collections:

1. **Operator dips (automatic).** When a CAMARA Open Gateway operator is configured for your account, the gate concurrently queries SIM-swap recency, number-recycling tenure, device reachability, device-location verification (when you pass a claimed area), Silent-Auth possession (when you pass a device-bound access token), and the operator's own call-forwarding assertion.
2. **Caller-held signals.** Signals the operator dip does not own and you already hold — most importantly **cross-carrier port-event recency** (`portedRecently`), plus roaming state, a number-reputation bucket, and any unconditional call-forwarding state you read from a Lookup data package.

All of it folds into one additive score from 0 to 100, which the kernel then buckets with your thresholds: score at or above the `deny` threshold returns `deny`; at or above the `review` threshold returns `review`; below both returns `allow`. The response also carries `review_or_block` — a single boolean (`decision !== "allow"`) your funnel can test directly to route a user into its step-up path.

Every dip is **fail-soft**: an operator that is unreachable, unconfigured, or slow lowers the number of signals the verdict rests on but never fabricates risk, and never absolves it either. A signal that could not be determined adds zero points and is named in `signals_unavailable` so your logs show exactly what the verdict was based on.

### Where it fires in a funnel

* **Registration:** call before account creation completes — a recent port plus a recent SIM swap on a brand-new account is the classic account-takeover replay.
* **Login on a new device:** call with the Silent-Auth access token bound to the device; a possession mismatch plus any corroborating signal should step up, not proceed.
* **Checkout / payout:** call before authorizing the transaction; use `review_or_block` as the trigger for your existing step-up (an OTP on a stronger factor, a voice-print challenge, manual review).

## 2. The fused signals

The kernel consumes a fixed set of tri-state signals. Only an **asserted negative** adds risk — a clean assertion and an unknown one both add nothing.

| Signal (request field)              | Source                                                        | What an asserted-negative means                                                                                                                                                                  |
| ----------------------------------- | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `accessToken` → possession          | Operator (Silent Auth / Number Verification)                  | The network confirms the device's asserted number is **not** the claimed one — the strongest takeover/spoofing signal.                                                                           |
| SIM-swap recency                    | Operator dip with `simSwapMaxAge` hours (default 240)         | The SIM was swapped inside the window — the canonical account-takeover precursor.                                                                                                                |
| `portedRecently`                    | **Caller-held** — cross-carrier port history you already hold | The number moved to a new carrier recently. Distinct from a same-carrier SIM swap, and a stronger takeover wedge than same-carrier recycling — this is the exact vector a port-out PIN protects. |
| Number-recycling tenure             | Operator dip with `numberRecyclingMaxAge` days (default 30)   | The MSISDN was reassigned to a new subscriber recently — the new holder inherits the previous holder's fraud history.                                                                            |
| `claimedLocation` → device location | Operator dip with your geofence                               | The operator verifiably asserts the device is outside the claimed area (`FALSE`) or partly inside it (`PARTIAL`).                                                                                |
| Device reachability                 | Operator dip                                                  | The operator reports the device not connected — weak corroboration only.                                                                                                                         |
| `roaming`                           | Caller-held                                                   | The SIM is serving in a different country than it is provisioned for.                                                                                                                            |
| `reputationRiskLevel`               | Caller-held bucket (`low` / `medium` / `high` / `unknown`)    | A line-type or reputation classification you already hold, e.g. a disposable-VoIP classification.                                                                                                |
| `callForwardingUnconditional`       | Caller-held                                                   | Unconditional ("always") call forwarding is active — every inbound call, including voice OTPs, silently diverts to another line.                                                                 |
| Operator-asserted call forwarding   | Automatic operator dip alongside the others                   | The **carrier itself** confirms forwarding is active — materially stronger evidence than the caller-held version, so it carries its own weight.                                                  |

Two properties of this set matter for how you reason about a verdict:

* **Signals are independently fused, not shadowed.** A caller-held `callForwardingUnconditional` and the operator-asserted forwarding dip can both fire on the same transaction and add up as corroborating evidence rather than double-counted noise.
* **A lone signal resolves; it never hard-fails.** No single signal — not even a possession mismatch — mechanically blocks a transaction. A lone moderate signal lands at `review` under the default weights, which is the knob you use to require corroboration before anything is refused. Blocking is your threshold's decision, applied to the composite.

## 3. Kernel weighting: the additive model

The score is the sum of observed negative assertions, capped at 100, mapped by your thresholds. Default weights and thresholds:

| Reason code                                                 | Points  | Reading                                                                                        |
| ----------------------------------------------------------- | ------- | ---------------------------------------------------------------------------------------------- |
| `number_verification_failed`                                | 45      | Silent-Auth possession mismatch.                                                               |
| `sim_swap_recent`                                           | 45      | SIM swapped inside the look-back window.                                                       |
| `device_location_mismatch`                                  | 40 / 15 | Device-location `FALSE` / `PARTIAL` against your claimed area.                                 |
| `device_unreachable`                                        | 15      | Device reported not connected.                                                                 |
| `roaming`                                                   | 20      | SIM serving abroad.                                                                            |
| `reputation_high` / `reputation_medium`                     | 40 / 20 | Caller-held reputation bucket.                                                                 |
| `carrier_reputation_flagged` / `carrier_reputation_at_risk` | 40 / 20 | Carrier-analytics spam/scam label on the number.                                               |
| `call_forwarding_unconditional`                             | 35      | Caller-held: all inbound calls divert.                                                         |
| `call_forwarding_operator_asserted`                         | 45      | Carrier-confirmed forwarding — operator-grade evidence, weighted like SIM swap and possession. |
| `number_recycled`                                           | 30      | Recently reassigned number.                                                                    |
| `ported_recently`                                           | 35      | Recent cross-carrier port.                                                                     |

Default thresholds: **review at 35, deny at 70.**

Why the weights sit where they do:

* **Port-event recency defaults to 35 — above number recycling (30), below SIM swap (45).** A port moves the number onto a new network entirely, which is a stronger takeover vector than a same-carrier reassignment but corroborates rather than confirms. At exactly the review threshold, a lone `ported_recently` assertion lands the transaction in `review` — never auto-`deny`. It denies only when fused: port (35) + SIM swap (45) = 80, port + possession mismatch = 80, port + operator-asserted forwarding = 80.
* **Operator-asserted evidence outweighs caller-held claims of the same fact** (45 vs 35 for forwarding) — the carrier's own assertion is harder to spoof than one your side holds.
* **The 70 deny threshold is reachable by any pair of strong signals** (45 + 45, 45 + 40, 45 + 35 = 80) but unreachable by weak corroboration alone — three weak signals (15 + 20 + 20 = 55) still only review. Deliberate: weak signals step a transaction up; they do not kill it.
* **Why nothing hard-fails.** The kernel is additive and fail-open on missing data, so there is no "one signal equals block" carve-out. A hard block is always your `deny` threshold doing its job on a composite — which keeps a single noisy dip from refusing legitimate users, and puts the refusal criterion on the record in `reasons` every time.

Because the weights sit in the request, you tune the fusion to your transaction's risk appetite without a deploy — lower `portedRecently` below 35 when your funnel already screens ports upstream, or raise the review threshold when a step-up is cheap for your users.

## 4. Configuring your posture

Per Orbit's compliance model, the posture is **yours**: the gate ships no platform-side hard gate, and every knob is per-request. Two objects tune it:

* `weights` — per-signal point overrides (0–100 each), merged onto the defaults; omitted keys keep their defaults.
* `thresholds` — `review` and `deny` score cutoffs (0–100), merged onto 35/70.

A workload-shaped starting point:

```json theme={null}
{
  "phoneNumber": "+14155550100",
  "simSwapMaxAge": 168,
  "portedRecently": true,
  "thresholds": { "review": 35, "deny": 80 },
  "weights": { "portedRecently": 40 }
}
```

Pair the knobs with the funnel side: `review_or_block: true` is the branch point — route it into your own step-up (a Verify OTP with channel escalation, a [voice-biometric challenge](/concepts/voice-biometrics), or a manual-review queue) rather than treating `review` as a block. In the Verify stack, the profile's `riskStepUpThreshold` / `riskBlockThreshold` play the same role as this endpoint's `thresholds`; keep one posture across both by setting them from the same risk appetite rather than tuning each surface in isolation.

The look-back windows are yours too: `simSwapMaxAge` (1–2400 hours, default 240) and `numberRecyclingMaxAge` (1–2555 days, default 30) decide what "recent" means for the operator dips. When you know the exact port-in completion timestamp for a number you just provisioned, tighten the recycling window to everything since the port and assert `numberRecycled: false` — the verdict then records that recycling was evaluated-and-clean instead of unavailable.

## 5. A worked call, and troubleshooting false positives

A registration-time gate call with a caller-held port signal and a SIM-swap dip returning:

```json theme={null}
{
  "data": {
    "decision": "deny",
    "review_or_block": true,
    "risk_score": 80,
    "risk_level": "critical",
    "reasons": ["sim_swap_recent", "ported_recently"],
    "signals_evaluated": ["simSwapped", "portedRecently"],
    "signals_unavailable": ["deviceLocation", "roaming"]
  },
  "meta": {
    "request_id": "req_01HZQX4E7JQ4M2E2H2DM9XE5FJ",
    "timestamp": "2026-08-28T12:00:00.000Z"
  }
}
```

Reading it: two asserted negatives fused (SIM swap 45 + port 35 = 80), clearing the 70 deny line; two signals were never evaluated so the verdict rests on what was actually observed; `risk_level` bands the score (`unknown` means *no* signal was evaluable — treat that as "no data", never as "trusted").

Common false-positive shapes and their resolution:

* **Legitimate user ports in, then registers.** Bring your own port-in timestamp and pass `portedRecently: false` when your inventory says the port is older than your recency window — the flag is caller-held precisely so your ledger resolves it. Where the port is real and recent and the user is legitimate, the intended outcome is `review`, not `deny` — a 35-point lone signal lands there exactly so your step-up (not a refusal) handles it.
* **Forwarding set for a legitimate reason.** `call_forwarding_unconditional` at 35 also lands a lone line in `review` rather than deny, for the same reason — forwarding has legitimate uses, so corroboration decides.
* **A `deny` without clear cause.** Read `reasons` — every point is explained there — and `signals_unavailable` for what never evaluated. Then move the specific weight or the deny threshold in your next request rather than blanket-disabling the gate.
* **Operator unreachable at decision time.** The missing dips appear in `signals_unavailable` and the verdict degrades to the caller-held half of the fusion — which is why `review_or_block` never treats absent data as risk. If your funnel requires operator evidence to proceed, gate on `risk_level === "unknown"` explicitly.

## See also

* [The Verify fraud gate: score, verdict, and the fallback-constrained chain](/concepts/verify-fraud-gate-timeline-model) — the OTP-send-bound sibling of this endpoint and the per-profile thresholds that mirror `thresholds`.
* [Composite pre-send risk verdict](/concepts/risk-verdict-model) — how `verify_signal` and `voice_biometrics_signal` fold across detectors on `POST /risk/score`.
* [Number portability model](/concepts/number-portability-model) — the port lifecycle the caller-held port-event signal reads from.
* [Reassigned numbers database](/concepts/reassigned-numbers-database) — the roster screen behind number-recycling tenure checks on your own sends.
* [Network APIs reference](/api-reference/endpoints/network-apis) — the endpoint contract, including `identity:gate` and `risk:score`.
* [Verify fraud gate](/guides/verify-console) — the console surface for the Verify-side gate configuration.
