# Paywall conversion climbs when every test changes one thing at a time

> Your paywall works but nobody buys: remotely editable offers, one-test-at-a-time discipline, and the three conversion leaks to fix first.
> By Dave · 2026-09-10
> Source: https://otf-kit.dev/blog/paywall-conversion-expo-apps-production

Your paywall is live and nobody is buying. The purchase flow works in sandbox, the restore button works on your device, the products are approved in both stores — and conversion sits flat. This is the normal shape of a first paywall: setup done, selling not started. Setup asks whether the purchase works. Conversion asks whether the offer earns the tap, and those are two different jobs done with different tools.

The setup post in this series got the purchase working end to end with one entitlement model across iOS and Android. This post starts where that one ends: the paywall is functional, and now every change you make should move one number — the share of paywall viewers who start a trial or purchase — without a binary resubmission and without guessing.

## Conversion is a separate job from setup

Setup bugs are loud: purchases fail, entitlements never enable, restores lose state. Conversion problems are quiet: the paywall renders, the buttons work, and most viewers dismiss it. Treating a quiet problem like a loud one leads to rewrites of working purchase code when the actual fault is the offer — wrong trial length, price shown without context, a single plan where two would segment, or a paywall that appears before the user feels any value.

So freeze the purchase path first. If entitlements gate correctly, receipts validate server-side, and restore works on a fresh install, stop touching that code. Conversion work happens in configuration and presentation: which offering is attached, what the paywall shows, when it appears, and who sees which variant. The teams that improve conversion fastest are the ones that can change all four without shipping a build, because each change is a test, and tests need volume to mean anything.

A useful boundary: purchase code changes ship with a release; offer changes ship with a dashboard edit. If a conversion idea requires an app release, scope it down until it does not, or batch it with the next scheduled release and test configuration-only ideas first. Speed of iteration beats cleverness of any single variant.

## One entitlement, one offer, one test at a time

The fastest way to waste a month is testing three things at once: a new headline, a new trial length, and a new price, all in one variant. Whatever moves, you will not know why, and whatever you learn will not transfer to the next screen.

Keep the entitlement model fixed — one entitlement per access tier, as the setup guide recommends — and vary the offer built on top of it. An offering is the collection of products shown as a single choice; the paywall is the view that presents it. Because one entitlement can back many offerings, you can test weekly versus annual framing, trial versus no trial, or two price points while the gating logic never changes. The app keeps asking one question — does this user hold this entitlement — and the answer stays correct no matter which variant they saw.

Run one test at a time per placement, and give each test a single decision rule written before it starts: this variant wins if trial-start rate rises by a stated margin over a stated number of viewers. Small apps need patience here; with a few hundred viewers a week, a test needs weeks, not days. That is fine. One clean read beats four overlapping guesses, and the discipline compounds — after a quarter you hold a short list of claims you actually verified instead of a folder of inconclusive screenshots.

[Feature flags for AI-built apps](https://otf-kit.dev/blog/feature-flags-production-ai-app) describe the same deploy-versus-release split applied to behavior: the code ships once, the variant flips remotely. Paywall offerings are the monetization instance of that pattern — the binary stays still while the offer moves.

## Remote paywalls beat binary resubmits

Every paywall change that needs a resubmission costs you the test and the week. Store review adds days, staged rollouts add more, and by the time the variant is live the idea behind it is stale. Remote paywall configuration exists to break that coupling: the paywall view is defined on a dashboard, delivered natively at display time, and edited without touching app code.

The verified behavior, per the primary source below, is that paywalls can be built from templates or from scratch, presented as a single screen or a multipage flow, and attached per offering so each variant under test gets its own configuration. That combination — unlimited offerings, each with its own paywall — is what makes one-test-at-a-time practical: you create the variant as a new offering with its own paywall, route a share of viewers to it, and retire the loser without ever editing the client.

```typescript
// Gating stays fixed while offers vary: the client checks the
// entitlement, never the variant. Which offering the viewer saw
// is a dashboard decision, not a code branch.
import { useCustomerInfo } from './billing';

export function usePremiumAccess(): boolean {
  const { customerInfo } = useCustomerInfo();
  return customerInfo.entitlements.active['premium'] !== undefined;
}
```

Keep this check as the only purchase branch in the client. If you find variant names leaking into app code — conditionals on experiment labels, hardcoded product identifiers per test — move that logic into offering configuration. Code branches multiply with every test and fossilize into permanent complexity; configuration retires cleanly when the test ends.

Teams already gating screens on identity will recognize the shape from [Expo Router auth guards in production](https://otf-kit.dev/blog/expo-router-auth-guards-production): a single guard question, asked consistently, with the policy living outside the component. Entitlement checks deserve the same discipline.

## Trials, pricing display, and restore: the three leaks

When conversion is flat, the fault is usually one of three leaks, in this order: the trial ask is wrong, the price display confuses, or restore anxiety kills the tap.

The trial ask means when and how the trial appears. A paywall shown before the user experiences value converts on promise alone, which is the hardest sale. Showing it after a meaningful action — first export, first project saved, first week of use — converts on experienced value, which is easier. Test placement before testing copy: the same paywall after value often beats a rewritten paywall before value. Trial length follows the same logic: long enough to form the habit the app sells, short enough that the purchase decision stays connected to the experience. There is no universal length; there is only your app's habit loop, measured.

Price display is about context, not font size. Viewers compare what you show them: annual framed per-month against monthly, trial-included totals against bare prices, and plan names that describe outcomes against names that describe tiers. Keep every price the store will actually charge visible before the tap — stores reject and users churn on mismatched expectations alike — and test one framing change at a time as the previous section demands. If annual dominates but monthly exists for the cautious, say who each plan is for in plain words; undecided viewers pick nothing.

Restore anxiety is the quietest leak. A viewer who paid before, switched devices, or simply fears double-charging will not tap purchase no matter how good the offer looks. A visible restore path, an entitlement check on launch that silently heals access, and a paywall that recognizes existing subscribers instead of selling to them all remove the fear. Test this like any variant: restore success rate and support-message volume are numbers too.

Price the whole exercise honestly while you are at it. [The ownership math that keeps you shipping](https://otf-kit.dev/blog/app-ownership-cost-math) puts store commissions and backend meters on the same sheet as builder seats — run those six lines before raising prices to cover costs you never modeled, because a paywall that funds a stack you do not understand is a plan, not a business.

## Measure with store-agnostic events

Store dashboards report revenue; they do not explain it. Attribute every step — paywall viewed, trial started, trial converted, purchase completed, restore used, cancellation reasoned — through one customer record that is identical on iOS and Android, so a test read never depends on which platform the viewer used.

The setup guide's single-customer-record pattern pays off here: because every store event normalizes into one entitlement state, your funnel counts viewers and converters instead of receipts and renewals. Define the funnel once, in your own analytics, keyed on the same events on both platforms: viewer, starter, converter, retained-after-first-renewal. Platform dashboards then reconcile money while your funnel explains behavior, and the two should agree within the normal refund and grace-period gaps.

Two measurement rules prevent self-deception. First, never read a test before its predeclared viewer count arrives — early reads always favor the variant that is ahead by noise. Second, track the second renewal, not just the trial start; a variant that buys trials with a confusing trial-to-paid transition will show up as churn a month later, and a win that evaporates at first renewal was never a win. The metric that matters is retained revenue per viewer, and it takes two billing cycles to see it.

## Ship checklist before your next variant

Start from a frozen, working purchase path: entitlements gate one tier, server-side validation, restore verified on a fresh install. Then work this order, one item per test, each with a predeclared decision rule and viewer count.

First, confirm the paywall is remotely editable and each variant is its own offering with its own view — no client edits per test. Second, move the paywall to after first value if it currently shows on launch, and measure trial starts per viewer. Third, test trial presence and length against your habit loop. Fourth, test price framing with all chargeable amounts visible. Fifth, add and measure the restore path: silent entitlement healing on launch plus a visible restore action. Sixth, read retained revenue per viewer at two renewals before declaring any winner, and retire losers by detaching their offering, not by editing shared code.

The full purchase-path setup this assumes is walked end to end in [RevenueCat paywalls in Expo apps](https://otf-kit.dev/blog/revenuecat-paywalls-expo-apps-production): dev builds, one entitlement per tier, screen gating on customer info, and server-side sync. If any item on that list is not true for your app, fix setup before testing conversion — no variant rescues a purchase that does not complete.

Conversion work never ends, but it does get boring, which is the goal. A paywall program that ships one clean test per cycle, retires losers without code changes, and reads retained revenue instead of day-one spikes will beat a clever redesign every quarter. Boring is the shape of compounding.

## Sources

- [RevenueCat Paywalls documentation](https://www.revenuecat.com/docs/tools/paywalls) — remotely configured paywall views, templates and custom builds, single-screen and multipage flows, one paywall per offering, unlimited offerings per test variant. Verified live today.
- Internal companions: [RevenueCat paywalls in Expo apps](https://otf-kit.dev/blog/revenuecat-paywalls-expo-apps-production) · [The ownership math that keeps you shipping](https://otf-kit.dev/blog/app-ownership-cost-math) · [Feature flags for AI-built apps](https://otf-kit.dev/blog/feature-flags-production-ai-app) · [Expo Router auth guards in production](https://otf-kit.dev/blog/expo-router-auth-guards-production)