Guide · Chapter 1 of 4

Why paywalls should be server-driven

A paywall is copy, packages, and layout choices that change far more often than your billing code does. Keeping them in server-side config means a change is a publish, not an app release.

The problem with a paywall compiled into the app

When the headline, feature list, package order, and badge text are constants in your app binary, every edit rides the release train: a build, a store review, and then however long it takes users to update. Meanwhile the parts of a paywall you most want to iterate on — wording, which package is highlighted, which languages you ship — are exactly the parts that are cheapest to change and slowest to ship.

The fix is to treat the paywall as data. The app ships a renderer; the server holds the configuration it renders. That split is what Tierux calls a server-driven paywall, and it is the premise for the rest of this guide.

What lives in the paywall config

A paywall is one structured object. Per the paywall spec (spec/PAYWALL_SPEC.md), its fields include a theme (dark, light, or auto), an accent color, a badge, a headline and subtitle, a feature list, the packages, which package is recommended, the call-to-action labels for subscriptions and one-time purchases, and optional terms and privacy URLs. Translations live beside it in localizations, one override object per locale.

// Shape of a paywall config (abridged from spec/PAYWALL_SPEC.md)
{
  id: "pro",              // equals the entitlement it grants
  theme: "auto",
  headline: "Unlock everything",
  features: ["...", "..."],
  packages: [{ id, productId, label, price, sub, type, on, badge? }],
  recommended: "yearly",
  localizations: { "de": { headline: "..." } }
}

Note the first field. The paywall's id is the entitlement it grants, so the paywall your app opens and the access your backend checks are the same identifier. That coupling is deliberate: it is what lets one call render the screen and land on a verified entitlement. The entitlement side is covered in chapter 5 of the server-side verification guide.

What the client does at runtime

The client fetches the paywall by app id and paywall id. The serving route returns only the published snapshot; a paywall that has been saved as a draft and never published answers 404 rather than leaking work in progress (src/app.ts, the /api/client/apps/:appId/paywalls/:paywallId route). The Android SDK then renders the config and, when the user taps the call to action, launches Google Play billing and sends the resulting purchase for server-side verification.

Tierux.showPaywall(activity, paywallId = "pro") { result ->
  when (result) {
    is PaywallResult.Purchased -> if (result.active) unlockPro()
    PaywallResult.Cancelled -> Unit
    is PaywallResult.Error -> showError(result.error)
  }
}

What stays native, and what stays with the store

Server-driven describes the presentation layer only. Billing does not move. The purchase itself is always a native store flow, and the paywall carries a "Secured by Google Play" badge below the call to action to say so. Prices are a further boundary: once a package is synced from Play, its price is owned by Play and is read-only in the paywall — the subject of the next chapter.

It is also worth being precise about what config cannot do. It changes what the installed SDK renders. It does not add a capability that the SDK version in a user's hands lacks, so a new kind of screen or flow still needs an app update.

Platform status

Google Play has the deepest coverage: remote paywall config with no app release is implemented, with paywall rendering in the Android Kotlin/Java SDK. Apple App Store support is implemented with limitations: the Swift SDK ships and remote config fetch is wired, but paywall rendering is less mature than on Android and the path is less battle-tested. Cross-platform wrappers for React Native and Flutter bridge both stores. The platform support matrix is the canonical per-feature reference.

Drafts and publishing

Server-driven config only helps if a half-finished edit cannot reach users. Tierux separates the working copy from the served version: creating a paywall through the MCP tool saves and publishes it, while later edits made with update_paywall are saved as a draft and change nothing your users see until publish_paywall is called. Every write tool shows a preview and a confirm token first. Chapter 4 walks through that loop end to end.

Related reading

Glossary: Paywall

What a paywall is and where it sits between a user and an entitlement.

Glossary: Entitlement

The server-side record of access that a paywall's id maps to.

Guide: Why client-side verification fails

Why the purchase result on the device is advisory, and where the trust boundary belongs.

Docs: SDKs

Installing the Android SDK and the framework wrappers that render these paywalls.

Platform support matrix

Per-feature, per-platform status for Google Play and Apple App Store.

← Back to the paywalls and win-back guide

Build paywalls that ship without a release

Free tier — unlimited apps, 1 paywall. No credit card, no revenue share.

Start free