Skip to main content

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 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): 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. 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: 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.
  • thresholdsreview and deny score cutoffs (0–100), merged onto 35/70.
A workload-shaped starting point:
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, 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:
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