Changing Your Paywall Without a Release
Updated 24 Oct 2026
The promise is easy to like: stop shipping a release every time the paywall needs a different headline, and let the dashboard decide what a given user sees. It is real, and plenty of teams run this way. But it is a promise with a precondition, and the precondition lives in the one place a growth team does not control — the compiled app.
This is the engineering half of the conversation, written so a product lead can follow it. If you read nothing else: remote selection chooses between screens that already shipped. It cannot invent a screen, and it cannot invent a price.
What Remote Switching Can and Cannot Do
The mechanism is narrow on purpose. At the start of a session, or when your app asks, Amply works out which audience the person belongs to and fires a deeplink. Your app receives that deeplink and presents the matching screen. Nothing is downloaded, nothing is rendered remotely, and no layout crosses the network.
So the honest version of the pitch is: you can change which paywall appears, for whom, and when, on the same day, without a store submission. You cannot change what any individual paywall looks like without one.
Remote configuration moves the decision out of the release train. It does not move the user interface out of the binary.
That boundary is not a limitation to work around; it is what makes the mechanism safe. A screen that was reviewed, translated and tested on real phones stays reviewed, translated and tested, whatever the dashboard does with it afterwards.
The Build Is the Menu, Not the Kitchen
Everything the dashboard will ever be allowed to choose has to be in a version that users have already installed. In practice that means four things ship together, before anyone touches a campaign:
- Every screen you might want to show. A layout that is not compiled into a released version cannot be chosen later. The dashboard picks between screens; it does not draw them.
- A route for each one. One handler that maps an incoming identifier to a screen. New identifiers that the handler does not recognise must fail into the default rather than into nothing.
- Every price you might want to charge. Products are approved in App Store Connect and Google Play, not in a dashboard. Approve the ones a future variant needs while you are shipping the screens.
- The tags that make results comparable. A variant you cannot tell apart in your analytics is a variant you cannot learn from. This is the part teams discover two weeks too late.
The routing piece is small, and its shape matters more than its size: one listener, one lookup, and a default branch that catches identifiers the installed version has never heard of. That default is what keeps an old build from showing a blank screen when a newer variant is promoted.
// Android (Kotlin). 'amply' is the Amply instance created in your Application class.
import tools.amply.sdk.actions.DeepLinkListener
amply.registerDeepLinkListener(object : DeepLinkListener {
override fun onDeepLink(url: String, info: Map<String, Any>): Boolean {
val screen = paywallScreens[url.substringAfterLast('/')]
?: return false // unknown to this build: fall through to the default paywall
present(screen)
return true
}
})Note what this buys you later. Adding a variant to a future release is a change to one map. Promoting it is a change in the dashboard. The two are no longer the same event, and that separation is the entire point.
Giving Each Variant a Name That Survives
Naming looks like bikeshedding until the first time someone asks which paywall was live in February. Three rules save that conversation:
- Say what the screen is, not when you made it: a compact three-plan screen, not "v3".
- Keep the identifier stable once it is live. Renaming it mid-flight splits one result into two.
- Never reuse an identifier for a different layout — last quarter's numbers quietly become this quarter's.
The identifier in the deeplink, the name of the thing in the dashboard, and the tag you attach to events should all be the same string. Where they differ, someone eventually joins two of them by hand and gets it wrong.
Tagging the Outcome So Variants Compare
Amply decides which variant a given user sees. What that variant then earned is measured somewhere else: Amply does not know what a subscription is worth, it keeps no history of your funnel, and it holds no payment data at all. Whatever you want to compare — conversion, revenue per paying user, refunds a month later — is computed where your revenue data already lives, from the events your app sends to the analytics tool you already use.
Which means the variant tag has to travel with the events your app already sends, starting with the moment the screen appears:
// Same tag on presentation and on purchase, so the two can be joined later.
amply.track("PaywallPresented", mapOf("paywall" to screenId))
amply.track("SubscriptionStarted", mapOf(
"paywall" to screenId,
"product" to productId
))One more thing to decide before you start, not after: how variants are split. Dividing by audience — market, plan status, how long someone has had the app — gives you a result you can act on but not a clean experiment, because the groups differ in more than the paywall. Dividing at random gives you the experiment and forgoes the targeting. Both are legitimate; mistaking the first for the second is how a “winning” variant turns out to be a better market.
Rolling One Back on a Tuesday Afternoon
Rollback is the part worth rehearsing, because it is the reason this setup exists. A variant that is losing money gets stopped in the dashboard, and sessions go back to the screen that was winning. There is no submission, no review and no waiting for adoption of a new build.
Two details decide whether that actually works on the day:
- There is always a broad fallback running — the paywall everyone sees when no narrower variant applies. If the fallback is itself an experiment, a rollback has nowhere to land.
- The change takes effect on each user's next session, not mid-screen. Someone looking at the old variant right now finishes with it, which is correct behaviour and still surprises people.
Write down who is allowed to press stop and what evidence they need. Rollback that requires a meeting is rollback you do not have.
Who Owns Which Half of This
The division is clean once stated. Engineering owns the screens, the routing and the tags, and owns them ahead of time — a variant nobody has shipped is not a variant. Product owns which audience sees what, when it starts, and when it stops. The stores own prices and approvals, as they always did; Amply does not price anything and never sees a transaction.
It also pairs well with a timing decision rather than a layout one — the question of which session a paywall shows up on is independent of which paywall it is, and the two are usually worth separating.
When the strategy is settled, how paywall versioning is set up in the dashboard has the campaign states, the audience conditions and the hand-off list in implementation detail.