Skip to main content

Continuous monitoring: SIM-swap and roaming subscriptions

The one-shot checks in Network signals before you send answer “is this number suspicious right now?” at the moment you ask. A monitoring subscription answers the follow-up: “tell me the moment it changes.” You enroll a number once, and the operator pushes a CloudEvent to your webhook when the SIM swaps, or when the device starts or stops roaming. No polling loop, no gap between checks. Orbit exposes two subscription families under /api/v1/numbers/network-apis, both aligned with the CAMARA event-subscription contract: Each family also has GET and DELETE routes on /subscriptions/{subscriptionId} to read state and tear down. Every request carries your X-API-Key.

Check vs subscription: when to enroll

Polling the one-shot checks is the right shape for a batch screen: you already hold a recipient list, one request per recipient, and you cache the verdict for the send window. A subscription is the right shape when a number matters continuously, not only at the moment you send or log someone in:
  • High-value accounts. Accounts whose takeover would be catastrophic (large wallets, payout rails, admin roles) deserve monitoring that reacts in seconds, not at the next poll.
  • Step-up windows. After a password reset, a new-device login, or a suspicious risk:score verdict, enroll the number for the risk window instead of polling sim-swap:check every few minutes until the window closes.
  • Home-network fleets. IoT devices and domestic-only customer bases where a device answering abroad is itself the alert. That is the roaming subscription’s native use case.
The cost comparison is direct. Polling N numbers every M minutes is N × 1440/M requests per day, most of which return “nothing changed.” A subscription is one enrollment request, then one event per actual change, and it sees the change when it happens rather than at the next poll boundary. A subscription ends in one of three ways, and you choose the combination at enrollment:
  1. subscriptionExpireTime — the subscription auto-expires at the RFC-3339 timestamp you set. Fit it to your risk window.
  2. subscriptionMaxEvents — the operator stops after delivering that many events (1 to 1000). Useful as a safety cap.
  3. DELETE — you tear the subscription down explicitly. Omit both limits and this is the only way it ends.

Enroll a number

The enrollment body is the same shape for both families: the number, your webhook, and optional limits.
A successful enrollment returns 201 with the operator-issued subscription id, its lifecycle status, and its expiry. Store the id; the read and teardown routes take it as a path parameter.
Notes on the enrollment path:
  • Send an Idempotency-Key. If your enroll call retries on a network error, the key prevents a second live subscription for the same number.
  • notificationUrl must be HTTPS. Plain-HTTP and internal addresses are rejected with a 422.
  • The roaming variant is the same call with a different path. POST /device-roaming-status/subscriptions takes the identical body; the operator then delivers roaming-on / roaming-off events instead of swapped.

Read state and tear down

Read a subscription’s current state by id:
The response carries the same id / status / startsAt / expiresAt shape, so a reconciliation job can compare what the operator reports against what you enrolled. Tear down with DELETE; a 204 means the operator accepted the teardown and stops delivering notifications.
Plan for a short tail of in-flight events: notifications already inside the operator’s delivery pipeline when you tear down can still arrive moments after the 204. Your consumer must ack and discard events for subscriptions it no longer tracks, not error on them. The same tolerance covers subscriptions that hit subscriptionExpireTime or subscriptionMaxEvents a beat before your bookkeeping does.

Consume the CloudEvents

The operator POSTs each event to your notificationUrl as a CloudEvent. Three rules make the receiver production-grade; they are the same rules as the production webhook consumer checklist, adapted to this sink:
  1. Authenticate the callback. On this sink the operator presents your notificationAuthToken as an Authorization: Bearer header on every delivery. Reject anything else with a 401. (Orbit-dispatched webhooks elsewhere in the platform authenticate differently, with the X-Orbit-Signature HMAC; the pattern below swaps only the authenticity check.)
  2. Dedupe on the event id. Delivery is at-least-once; the same event can arrive more than once. Persist seen ids in your database, not in memory, so dedupe survives restarts.
  3. Ack fast. Verify, dedupe, enqueue, return 200. The decision logic runs in a queue worker, never inline in the handler.
The worker is where a swap becomes a decision. Route on the CloudEvent type, resolve the number from the event’s data block (its exact shape follows your operator’s CAMARA implementation; the device.phoneNumber layout shown here matches what Orbit enrolls), and apply your own policy:
Revoking active sessions and forcing step-up on the next login is the canonical mapping: the swapped verdict never blocks anything by itself, it feeds your session policy. Keep the one-shot sim-swap:check in your login flow as well; the subscription catches swaps between logins, the check catches them at the door.

Roaming on and off: pair before you act

The roaming family delivers two event types, roaming-on when the device starts roaming and roaming-off when it stops. Treat them as two edges of one state, not as two independent alarms. Devices near a border, or under roaming agreements that re-anchor periodically, can flap: roaming-on, roaming-off, roaming-on inside an hour. Acting on a single roaming-on (freezing an account, holding a payout) turns border towns into false positives. Debounce instead:
  • Persist the latest event type and timestamp per number when the worker processes an event; do not act inline.
  • A scheduled pass evaluates only numbers whose state has been unchanged for your quiet window (30 to 60 minutes is a workable default for a domestic-only customer base).
  • A roaming-off that follows a roaming-on inside the quiet window resolves it: the device came home, and the pair is a log line, not an alert.
  • A roaming-on that survives the quiet window is your corroborating signal for the fraud review queue, alongside SIM-swap and device-swap verdicts.

Coverage and fail-soft

Subscriptions exist only when a GSMA/CAMARA operator is configured for the deployment. Probe GET /api/v1/numbers/network-apis/status before you build on this surface:
On { "enabled": false } every enrollment returns 503 SERVICE_UNAVAILABLE, and your integration must route around the whole family: keep the polling path from Network signals before you send (sim-swap:check, device-roaming-status:retrieve) as your standing screen. It is also the right fallback inside an enabled deployment for the highest-value numbers, because absence of events is not evidence of absence of change: a delivery gap means nothing the operator could see happened, nothing more. Periodic one-shot checks complement subscriptions; they do not compete with them.

Tenant controls

Continuous monitoring consults the operator’s record about a subscriber over time. That is a processing act your privacy notice and consent capture must cover, and the decision to act on a verdict is yours. Orbit is the conduit that delivers the operator’s event to your decision point; it does not own your regulatory posture.
Concretely: pair enrollment with your existing consent flows (see Consent management), and keep the act decisions, revoke, step-up, hold, refuse, in your own policy code with your own thresholds. Enrollment never sends, routes, or blocks a message; outbound messaging stays on your normal send path. See Country compliance requirements for the tenant-owned controls these signals feed into.

In the console

The same subscriptions are operable from the console under Developer → Network APIs. The SIM Swap monitoring card enrolls a number, shows the subscriptions created from the console with their operator status (Active, Inactive, or Pending), re-reads their state, and stops them behind a confirmation dialog; the matching card below it does the same for roaming monitoring. Use the console for manual operations and debugging; your production automation enrolls and tears down through the API above.

What’s next