subscription object inside custom_claims in every attestation JWT. Your backend gates features by verifying the signed JWT it already receives; there is no entitlement API to call and no API key involved.
Subscription entitlement ingestion is an Enterprise feature.
Sharing units (families)
Entitlements attach to a Subject — an app-owned sharing unit such as a family or household, not an individual device. One paying member entitles every device in the unit, because every member’s device declares the same sharing-unit id at attestation time. The join key is one opaque id used in three places — all three must carry the identical value:
The Subject id is opaque to Grantiva — mint a stable UUID per household in your own system and reuse it everywhere. It is distinct from
identify(_:), which scopes the individual user for feedback and flag targeting.
The claim
The claim appears undercustom_claims.subscription in the decoded JWT:
Rules worth knowing:
- Absent means free. If the device’s sharing unit has no entitlement, the
subscriptionkey is simply not present. - Gate on
is_active, not onstatus— it already accounts for grace periods. - When a unit holds multiple entitlements (two payers, or Apple + Stripe), the JWT surfaces the best one: active-like first, then higher tier, then later expiry.
subscriptionis a reserved claim key, managed by Grantiva. Attempts to create a custom claim namedsubscriptionare rejected, and the managed claim is injected even if your custom-claims toggle is off.- The JWT has a TTL of at most 1 hour and refreshes on every attest/refresh, so the claim self-heals — treat the JWT as the source of truth.
How state flows in
Apple and Stripe push subscription lifecycle events directly to Grantiva:appAccountToken / client_reference_id, maps the product or price to a tier using your entitlement configuration, updates the entitlement, and from then on projects the claim into every JWT minted for any device in that unit. Ingestion trusts source events; there is no periodic re-sync against the App Store Server API or Stripe API in v1.
Instant invalidation webhooks
JWTs live up to an hour, so Grantiva also fires outbound webhooks the moment an entitlement changes:subscription.changed (always), subscription.expired, and subscription.refunded. Use them as a cache-bust nudge — evict whatever you cached for that subject_id and re-read the next JWT. See Webhooks for payloads and signature verification.
Setup checklist
- Configure your product/price → tier mapping (Enterprise, dashboard admin).
- Point Apple App Store Server Notifications v2 and/or your Stripe webhook at Grantiva.
- In your app: mint a household UUID, call
setSubjectId, and pass the same UUID at purchase time. - In your backend: verify the JWT against Grantiva’s JWKS and gate on
custom_claims.subscription.is_active.