Skip to main content
After your iOS app calls grantiva.validateAttestation(), it receives a signed JWT. Your backend should verify this token on every authenticated request to confirm the device passed attestation and to read device intelligence claims.

How it works

  1. Your app sends the JWT as a Bearer token in the Authorization header
  2. Your backend fetches Grantiva’s public key from /.well-known/jwks.json
  3. You verify the JWT signature and expiry locally (no network call per request)
  4. You extract device claims: risk_score, jailbreak status, and any custom claims

The public key endpoint

No authentication. Returns a standard JWKS document describing the RSA public key Grantiva signs attestation JWTs with.
  • Tokens are signed RS256, and the token header carries a kid matching the key in the set — match on kid rather than assuming a single key, so a rotation does not break verification.
  • The response is served with Cache-Control: public, max-age=3600. Every library below caches on that and refreshes on rotation.
  • GET /api/v1/attestation/public-key is an alias that returns the same document, kept for older integrations. Prefer the .well-known path.
  • A 503 means signing keys are not configured for the environment you are calling — retry rather than treating the token as invalid.

JWT Claims Reference

The claims below are the ones most integrations use. JWT Claims is the complete reference, including which claims each plan includes and a decoded example token.
There is no risk_category claim in the JWT — that field exists only in the attestation HTTP response body. Derive the category from risk_score.iss is not the literal string "grantiva". It defaults to app-attest-<your-org-name> and is configurable per organization. Pin your own value.risk_score is captured when the token is minted and tokens live for 1 hour by default — treat it as advisory and re-attest before high-stakes actions. See JWT Claims.

Node.js

Uses jose (ESM/CJS, works in Node.js and edge runtimes).

Python

Uses PyJWT with cryptography for RS256.

Swift (Vapor)

Uses vapor/jwt which has native JWKS support via JWKSet.

Go

Uses golang-jwt/jwt with JWKS fetching via MicahParks/keyfunc.

Reading custom claims

Custom claims you configure in the Grantiva dashboard appear in the custom_claims object:

Reading the subscription claim

If your organization uses subscription entitlement ingestion, the managed subscription object appears under custom_claims:
Gate on the single is_active boolean — it already accounts for billing-retry grace periods. expires_at is epoch seconds. The claim is absent entirely when the device’s sharing unit has no entitlement.

Risk-based access control

Derive the category from risk_score — there is no risk_category claim in the JWT.
Remember the score was captured when the token was minted, up to an hour ago. Require a fresh attestation before payments, transfers, or credential changes. See JWT Claims.

Caching the public key

All three examples cache the JWKS response and refresh it automatically when key rotation occurs. Do not fetch the public key on every request — it adds latency and will trigger rate limiting.