validateAttestation(), you need to know whether it is actually working. This guide walks through what to look for on both the iOS Simulator and a physical device, what a successful response looks like, and how to diagnose common errors.
Physical device required for full attestation. The iOS Simulator cannot run App Attest because it has no Secure Enclave. If you are developing in the Simulator, see Simulator Setup to configure API key fallback first.
Quick verification checklist
Run through these five checks after your firstvalidateAttestation() call:
result.token is a non-empty string
A JWT was issued. It should begin with
eyJ (base64-encoded JSON header).riskScore is present (if on Pro or above)
On a physical device with a Pro+ plan, A score of
riskScore is a non-nil integer between 0 and 100. On the Free tier or in the Simulator, it will be nil.nil on a physical device with a Pro plan indicates an issue — see riskScore is nil on a real device below.The JWT attest claim matches your environment
Decode the JWT payload (Base64URL-decode the middle segment) and confirm the
If you see
attest claim:| Environment | Expected value |
|---|---|
| Physical device | hardware |
| Simulator (API key fallback) | api_key |
api_key on a real device, the SDK fell back to API key auth — check that you are not passing an apiKey parameter in your production initialization.Your app appears in the Grantiva dashboard
Open Dashboard → Analytics → Devices. You should see a new attestation event. If nothing appears after 30 seconds, the request may not be reaching the server — check network errors below.
What the response looks like
Physical device (full attestation)
Simulator (API key fallback)
Sample JWT payload
After decoding the token (Base64URL-decode the middle segment), you will see something like this:| Claim | Type | Description |
|---|---|---|
sub | string | Unique device ID |
iss | string | Issuer — api.grantiva.io |
aud | string | Your Bundle ID |
iat / exp | number | Issued at / expiry (Unix timestamps) |
attest | string | hardware or api_key |
bundle_id | string | Your app’s Bundle ID |
team_id | string | Your Apple Team ID |
jailbreak_detected | boolean | Always present |
| Claim | Type | Description |
|---|---|---|
risk_score | number | 0–100 integer |
risk_category | string | low, medium, high, or critical |
device_integrity | string | uncompromised, simulated, or compromised |
attestation_count | number | Lifetime attestation count for this device |
Risk score reference
| Score | Category | Suggested action |
|---|---|---|
| 0–20 | low | Trusted — proceed normally |
| 21–50 | medium | Monitor — consider step-up verification |
| 51–75 | high | Challenge — require additional confirmation |
| 76–100 | critical | Block — device likely compromised |
Common errors and fixes
deviceNotSupported
Cause: App Attest is unavailable. Most commonly this is the iOS Simulator, or a very old device without a Secure Enclave.
Fix: Add API key fallback for Simulator builds. See Simulator Setup.
simulatorNotConfigured
Cause: Running in the Simulator without providing an API key. The SDK detected the Simulator environment but has no fallback credentials.
Fix: Pass an apiKey parameter during initialization for Simulator builds:
configurationError
Cause: The Team ID you passed to Grantiva(teamId:) does not match the Bundle ID of the running app, or the app is not registered in the Grantiva dashboard.
Fix:
- Verify your Team ID in the Apple Developer portal under Membership.
- Confirm the Bundle ID in Xcode matches what you registered in the dashboard under Apps.
- Check that the
teamIdstring has no extra whitespace.
networkError
Cause: The SDK could not reach api.grantiva.io. Common causes: no internet connection, App Transport Security (ATS) misconfiguration, VPN interference, or the challenge request timing out.
Fix:
- Confirm the device has internet access.
- Check that your
Info.plistdoes not block outbound HTTPS toapi.grantiva.io. - In development, check the device is not behind a proxy that intercepts TLS.
- The SDK retries automatically — if
networkErroris consistently thrown, check the status page.
validationFailed
Cause: The Grantiva server rejected the attestation. This usually means the attestation object was malformed, the challenge expired before the SDK submitted it, or the device’s App Attest key is corrupted.
Fix:
- Call
grantiva.clearStoredData()to wipe the cached key and token, then retry. - If the problem persists across retries on the same device, the device’s App Attest key may need to be regenerated — this happens automatically after
clearStoredData().
challengeExpired
Cause: The server-issued challenge has a short TTL (typically 60 seconds). If the device’s clock is significantly skewed, or the attestation process takes too long (e.g. slow network), the challenge may expire before the SDK submits the response.
Fix: The SDK retries automatically on challengeExpired. If you see this error bubbling up to your code, it means retries were exhausted. Check for:
- Device clock sync issues (
Settings → General → Date & Time → Set Automatically) - Network latency causing attestation submission to exceed 60 seconds
rateLimited
Cause: Your tenant has exceeded the request rate limit, or this device has submitted too many attestation requests in a short window.
Fix: The SDK includes backoff-aware retry logic. If rateLimited surfaces to your code:
- Ensure you are not calling
validateAttestation()in a tight loop — let the SDK handle caching. - If you see this in production at scale, contact support to review your plan limits.
tokenExpired returned by your backend
Cause: The JWT was issued with a limited TTL (default 1 hour). Your backend received a token that has already expired.
Fix: Call refreshToken() before sending the token to your backend, or catch the expired state in your backend and prompt the client to re-attest:
riskScore is nil on a real device
Cause: One of:
- Your account is on the Free tier — risk scoring requires Pro or above.
- The attestation used API key fallback instead of App Attest (check the
attestJWT claim).
- Check your plan in Dashboard → Settings → Billing.
- Confirm
result.deviceIntelligence.riskScoreisnilspecifically (not0) — a score of0is a valid low-risk score, not an error. - Confirm the JWT
attestclaim ishardware, notapi_key.
Next steps
Backend JWT verification
Verify the token server-side and read device claims in Node.js, Python, or Go.
Error handling reference
Full list of GrantivaError cases with suggested handling.
Risk scoring concepts
How risk scores are computed and how to set thresholds.
Simulator setup
Configure API key fallback for development builds.