pro entitlement with your
secret key. On the device’s next token mint, the pro limit preset applies — and the paywall you
wired in the image-enhance tutorial
stops firing.
The key rule: only your backend can grant an entitlement, so a device can never upgrade itself.
The two money layers
Keep two payment flows separate — Roadie touches only the second:
Roadie never takes your end user’s money and never issues refunds. Its job on this side is to
meter, entitle, and limit — you tell it the purchase outcome by
granting an entitlement. See
Billing & plans.
Prerequisites
- The image-enhance and (ideally) App Attest tutorials working, so the device authenticates and hits per-device free limits.
- Free and Pro limit presets configured on the project so the two tiers actually differ — set
end_user_planpolicies forfreeandpro(see Per-user limits). - A configured StoreKit subscription product and a secret key (
rd_sk_…) available to your backend (never the app).
1. Know which end user to entitle
An entitlement is keyed by the Roadie end-user external id — the same id the gateway attributes requests to.- If your app has its own accounts (or uses federated identity), that id is your user id — you already know it server-side.
- For a device-only app (App Attest), the id is
device:<hash>, returned asend_user_idin the mint response. Capture it when you mint and forward it to your backend with the purchase.
2. Sell the subscription (StoreKit 2)
In the app, present your product and purchase it. On a verified transaction, send the signed transaction (and your end-user id) to your backend — do not grant Pro from the app.Paywall.swift
3. Verify on your backend, then grant the entitlement
Your backend is the source of truth. Verify the transaction with Apple (StoreKit 2Transaction.currentEntitlements, or the App Store Server API for a server-authoritative check),
then call Roadie’s entitlement endpoint on the data plane (/v1) with your
secret key. Set expires_at to the subscription’s paid-through date so the grant lapses
automatically if you never hear about a renewal.
200 OK returns the resolved entitlement:
entitled_since is stamped on the first non-free grant and preserved across later downgrades —
your “customer since” marker. The call is idempotent (last-write-wins per user), and the end user is
created on first use if it does not exist yet.
4. The device picks up Pro on its next mint
The effective entitlement always wins over any plan a client asserts, and it is stamped into the client token’splan claim at the next mint. Because client tokens live at most an hour,
propagation is bounded by the token TTL — the device is on pro within the hour (immediately if it
re-mints). No app change and no gateway hot-path change: the existing per-plan policy engine now
enforces the pro preset.
Concretely, in your app: an .insufficientQuota error was your paywall trigger while the device was
on free. After the grant, the next mint carries plan: "pro", the pro limits apply, and the
image edits succeed again.
5. Handle renewals and churn
Subscriptions change state over time. Point Apple’s App Store Server Notifications at your backend and mirror each change into the entitlement:- Renewal (
DID_RENEW) — grantproagain with the newexpires_at. - Expiry / cancellation / refund (
EXPIRED,DID_CHANGE_RENEWAL_STATUS, refund notice) — grant{ "tier": "free" }(or a pastexpires_at). An authoritativefreeis a downgrade that overrides any assertedpro; on the next mint the device falls back to free limits.
Node (App Store Server Notification handler)
Setting
expires_at on every pro grant is a safety net: even if a churn notification is missed,
the entitlement lapses on its own and the device falls back to free. Entitlement is read-only in
the dashboard — every change is an authenticated PUT from your backend, which keeps your
server the sole authority over who is a paying user.What you built
A complete free-to-pro flow: the user buys with StoreKit, your backend verifies the purchase and grants apro entitlement with your secret key, and the device flips to Pro limits on its next
token mint. Renewals and churn keep the entitlement in sync via App Store Server Notifications, and
expires_at guarantees a clean fallback to free if you ever miss an event. The device could never
have upgraded itself — the grant is server-authoritative by design.
Next steps
Per-user limits
Define the free and pro presets this entitlement switches between.
End users & entitlements
The asserted-plan vs authoritative-entitlement model in full.
Observability
See per-user spend and who has crossed the free line.
Billing & plans
How the two money layers stay separate.