Skip to main content
Every attestation returns a risk category — but the full numeric score depends on your plan.
On the Free plan the JWT carries no risk_score claim at all — the key is omitted, not set to null. The category is still returned in the attestation response body as deviceIntelligence.riskCategory, but that value is not signed. There is no risk_category claim in the JWT on any plan. See JWT Claims for the full per-plan claim table.

Score ranges

On Pro, Business, and Enterprise plans, the numeric score maps to these levels:

Risk factors

The score considers multiple signals:
  • Jailbreak detection — Actual jailbreak/root signals. The strongest single factor.
  • Development builds — Debug, Xcode, and TestFlight installs are scored as a separate, lower-weight signal than jailbreak. A dev build alone doesn’t mark a device as compromised — it’s expected during integration.
  • Device integrity — Does the attestation pass Apple’s checks?
  • Suspicious activity history — Past suspicious events raise the score, with recency decay: each clean week forgives one past event, so a device that stays clean earns its trust back over time.
  • Device age — New devices (fewer than 3 attestations) carry a temporary penalty that clears as attestation history builds.
  • Anomaly detection — Rapid country changes, app version downgrades, VPN/proxy networks, device model changes, and unusual timing patterns.
  • Trust streak — A device with 30 consecutive clean attestations gets a credit against its score, and a larger one at 90.
Scores are dynamic in both directions: they rise on suspicious signals and fall again as a device builds a clean streak. Don’t persist a device’s score long-term — read it fresh from each attestation.
The Devices page in the dashboard shows the reasoning behind an individual device’s score. See Devices.

Using risk data

By category (all plans)

riskCategory is returned on every plan in the attestation response. It is an enum on the SDK side:

By numeric score (Pro / Business / Enterprise)

riskScore is Int? — it is nil on the Free plan, so unwrap it:

On your backend

Your backend reads the signed JWT, not the response body. The claims are flat and snake_case, and there is no risk_category claim — derive it from risk_score:
risk_score is a snapshot from when the token was issued and tokens live for an hour by default. See JWT Claims for the full claim reference, the staleness caveat, and Node/Python verification code.

Webhooks

On Pro, Business, and Enterprise plans, webhooks fire for risk-related events:
  • device.high_risk — A device’s score exceeds your threshold
  • device.new — First attestation from a new device
  • device.attestation_failed — A device fails attestation
  • attestation.anomaly — One or more anomalies are detected on an attestation
The high-risk threshold is your organization’s compliance maxRiskScore, defaulting to 50 when unset. The payload carries both the score and the threshold that fired it. See Webhooks for setup and payload shapes.

Risk alerts

On Business and Enterprise plans you can define risk alert rules: a threshold, a comparison (gte or gt), and a webhook URL that Grantiva calls when a device’s score matches. Rules are independent of webhook endpoints and keep their own delivery log. Manage them from the risk alerts API. Reading the rules and delivery log also requires Business or above.

Dashboard

The Attestation page shows the risk distribution across your fleet with the recommended action per band, and Devices lets you filter your fleet by band. See Attestation analytics and Devices.