This tutorial connects an Apple StoreKit subscription to a Roadie entitlement. When a user buys Pro, your backend verifies the purchase and grants the end user a 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_plan policies for free and pro (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 as end_user_id in 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 2 Transaction.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.
A 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.
This endpoint is authenticated with a secret key only. A client token or publishable key is rejected 403 permission_error (client_token_cannot_mint) — the anti-self-upgrade gate, so a device can never raise its own tier.

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’s plan 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) — grant pro again with the new expires_at.
  • Expiry / cancellation / refund (EXPIRED, DID_CHANGE_RENEWAL_STATUS, refund notice) — grant { "tier": "free" } (or a past expires_at). An authoritative free is a downgrade that overrides any asserted pro; 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 a pro 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.