Client-side gating
One boolean for the UI: Growth.isSubscribed(). Honest about what it is — a rendering hint that fails closed — and what it is not.
import { Growth } from "@fictura/sdk";
if (Growth.isSubscribed()) {
// render the premium experience
}The verdict rides in the SDK config payload, derived server-side from Stripe's and the stores' own webhooks. It refreshes with the config — launch, foreground, five-minute TTL — is cached offline with the config, and fails closed: before the first successful fetch on a fresh install the answer is false, never a guess.
Division of authority
- Web-checkout purchases —
isSubscribed()is the authoritative client answer; the store SDK has never heard of them. - Store purchases — your store SDK (for most apps, RevenueCat) remains the source of truth it already is. Fictura's copy of the verdict is a shadow that is continuously scored against the store SDK's answer.
- Anything that costs you money — gate it server-side. A modified client can lie to itself; it can't lie to your backend.
For more than the boolean, getShadowEntitlement() returns the full verdict — status, product_id, will_renew, expires_at — marked shadow: true for exactly this reason: read it, display it, but don't build access control on the client from it.
Onboarding flows
Server-driven onboarding: steps, questions and permission prompts configured on the dashboard, rendered by one component, measured end to end.
Server-side gating
Ask Fictura whether a user is entitled instead of maintaining your own billing table — one GET, derived from the same webhooks that feed everything else.