Smart Paywall Timing: How to Show Paywalls on Every Nth Session with Amply

Stan
Stan

02 Mar 2025

You shipped a paywall. Conversion is... underwhelming. So you move the trigger earlier. Now users see it on the very first session, get annoyed, and churn. You move it later. Conversions drop to near zero. Sound familiar?

The problem isn't your paywall design or your pricing. It's timing. Specifically, it's the lack of a systematic, data-driven strategy for when and how often to show your paywall across a user's lifetime. In this guide, we'll build a smart session-based paywall strategy using Amply's campaign orchestration SDK — including the powerful "every Nth session" pattern and an advanced "second chance" retargeting flow that works like abandoned cart recovery for mobile.


The Naive Approach and Why It Fails

Most developers start with something like this hardcoded in their app:

// The naive approach - don't do this
val sessionCount = prefs.getInt("session_count", 0) + 1
prefs.edit().putInt("session_count", sessionCount).apply()

if (sessionCount == 3) {
    showPaywall()  // Shows once on session 3, then never again
}

This approach has three fundamental problems:

  1. It's a one-shot trigger. The user sees the paywall exactly once. If they aren't ready to buy on session 3, you've lost them forever.
  2. It's not configurable. Changing the trigger session requires a new app release and store review cycle. Want to A/B test session 3 vs. session 5? That's two separate builds.
  3. It ignores context. A user who already subscribed still gets the paywall. A user who churned and came back gets nothing. There's no awareness of user state.

What you really need is a system that can show your paywall on a recurring schedule, respect user properties, and be reconfigured without shipping code. That's where Amply comes in.


Smart Session-Based Paywall Strategy with Amply

Amply's campaign engine lets you target users based on their session count without hardcoding any logic. Instead, you configure the When step in the Amply dashboard, and the SDK evaluates the rules at runtime. Here are the two core patterns:

Pattern 1: Every Nth Session (EVERY)

Show the paywall on a recurring schedule — every 3rd session, every 5th session, and so on. In the admin panel, set the Repeat Rules mode to every with your chosen cadence and scope it globally so the counter spans the user's lifetime.

Every Nth session: fire on every 3rd session
  • When
    Triggering EventTrigger when event SessionStart occurred.
  • When
    Repeat Rulesevery 3 globally
  • Who
    Custom Propertyis_premium (Boolean) = equal false
Admin panel When step with Repeat Rules set to every 3 globally

This is the bread and butter of session-based paywall timing. The user gets gentle, periodic nudges rather than a single make-or-break moment. You can adjust the interval (every 3rd, every 5th, every 10th) from the dashboard without touching your app code.

Pattern 2: Session Window (INTERVAL)

Show the paywall only during a specific range of sessions — for example, sessions 3 through 7, when users are past onboarding but still in the evaluation phase. In the admin panel, switch Repeat Rules to on mode and enter each session number as a separate chip.

Session window: fire during sessions 3 through 7
  • When
    Triggering EventTrigger when event SessionStart occurred.
  • When
    Repeat Ruleson 3, 4, 5, 6, 7 globally
  • Who
    Custom Propertyis_premium (Boolean) = equal false
Admin panel When step with Repeat Rules set to on 3, 4, 5, 6, 7 globally

You can mix and match these patterns by picking on for discrete windows or every for steady cadence — whichever matches the rhythm you want for that campaign.

The best paywall is one the user sees at the right moment, not the first moment. Amply lets you define that moment with precision -- and change it instantly.

Implementation: Every Nth Session Paywall with Amply

Let's walk through the complete implementation. The SDK side is minimal — most of the logic lives in the Amply dashboard.

Step 1: SDK Initialization and Session Tracking

// Application.kt or your app's entry point
import tools.amply.sdk.Amply
import tools.amply.sdk.config.AmplyConfig

class MyApp : Application() {
    lateinit var amply: Amply
        private set

    override fun onCreate() {
        super.onCreate()

        // Initialize the Amply SDK with config + application
        val config = AmplyConfig(
            appId = "your.app.id",
            apiKeyPublic = "YOUR_PUBLIC_API_KEY",
        )
        amply = Amply(config = config, application = this)

        // Set user properties so campaigns can target non-subscribers
        amply.setCustomProperties(mapOf(
            "is_premium" to false
        ))

        // Track session start — increments the internal session counter
        amply.track("SessionStart")
    }
}

Every time the SessionStart event is tracked, the SDK increments the internal session counter. This is the value the Repeat Rules block reads when it evaluates your campaign.

Step 2: Handle Deeplinks to Show the Paywall

When the campaign targeting rule matches, Amply delivers the campaign action. For paywall campaigns, we use a deeplink action that tells your app to open the paywall screen:

// MainActivity.kt or your main activity
import tools.amply.sdk.actions.DeepLinkListener

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    val amply = (application as MyApp).amply

    // Register deeplink listener for Amply campaigns
    amply.registerDeepLinkListener(object : DeepLinkListener {
        override fun onDeepLink(url: String, info: Map<String, Any>): Boolean {
            return when {
                url.contains("show-paywall") -> {
                    showPaywallScreen(source = "amply_campaign")
                    true
                }
                url.contains("show-discount-paywall") -> {
                    showDiscountPaywall(
                        source = "amply_second_chance",
                        discount = 50
                    )
                    true
                }
                else -> false
            }
        }
    })
}

Step 3: Configure the Campaign in Amply Dashboard

In the Amply dashboard, create a new campaign and fill in the steps as shown below:

Campaign: Show paywall every 3rd session
  • When
    Triggering EventTrigger when event SessionStart occurred.
  • Who
    Custom Propertyis_premium (Boolean) = equal false
  • When
    Repeat Rulesevery 3 globally
  • What
    Deeplinkhappens://show-paywall
  • When
    Frequency Limits1 time per session (prevents double-showing on rapid restarts).
  • When
    Frequency LimitsMinimum interval between impressions at least 24 hours.
Admin panel campaign configured with SessionStart trigger, every 3 globally, and a deeplink action

That's it. No conditional logic in your codebase. When you want to change from every 3rd session to every 5th, you update the rule in the dashboard and the new configuration reaches devices on the SDK's next config fetch — typically within minutes, no app update required.


Advanced: The Second Chance Paywall (Exit-Intent Retargeting)

Here's where it gets really interesting. You know the concept of abandoned cart emails in e-commerce? The same psychology applies to mobile paywalls. A user saw your paywall, considered subscribing, but closed it. They're not a "no" — they're a "not yet."

With Amply, you can build a second chance campaign that delivers a deeplink on a later session so your app can show a custom discount screen to users who previously dismissed your paywall. Think of it as abandoned cart retargeting for mobile subscriptions — Amply orchestrates the when and your app renders the UI.

The Strategy

  1. User sees the standard paywall on their 1st session (or via the Nth session pattern).
  2. User closes the paywall without subscribing.
  3. On session 2 or 3, Amply fires a deeplink and your app renders a discount screen: "Still thinking about it? Here's 50% off your first month — but only for the next 24 hours."

Campaign Configuration

Create a second campaign in the Amply dashboard:

Second Chance: show discount to users who dismissed the paywall
  • When
    Triggering EventTrigger when event SessionStart occurred.
  • Who
    Custom Propertysaw_paywall (Boolean) = equal true
  • Who
    Custom Propertyis_premium (Boolean) = equal false
  • When
    Repeat Ruleson 2, 3 globally
  • What
    Deeplinkhappens://show-discount-paywall
  • When
    Frequency LimitsShow campaign 1 time (total — single offer per device).
  • When
    Frequency LimitsMinimum interval between impressions at least 12 hours.
Admin panel campaign targeting users who saw the paywall and haven't subscribed, firing on sessions 2 and 3

Handling the Discount Paywall in Code

// Track when user dismisses the standard paywall
fun onPaywallDismissed() {
    val amply = (application as MyApp).amply
    amply.track("PaywallDismissed")
    amply.setCustomProperties(mapOf(
        "saw_paywall" to true,
        "is_premium" to false
    ))
}

// Track successful subscription
fun onSubscriptionCompleted(plan: String) {
    val amply = (application as MyApp).amply
    amply.track("SubscriptionStarted")
    amply.setCustomProperties(mapOf(
        "is_premium" to true,
        "subscription_plan" to plan
    ))
}
// Show the discount paywall with urgency (host app code — not an SDK call)
fun showDiscountPaywall(source: String, discount: Int) {
    val intent = Intent(this, DiscountPaywallActivity::class.java).apply {
        putExtra("source", source)
        putExtra("discount_percent", discount)
        putExtra("expires_in_hours", 24)
    }
    startActivity(intent)
}
Users who dismissed a paywall aren't lost causes -- they're warm leads. A well-timed second chance offer with urgency and a discount is often materially higher-converting than ignoring them, and Amply lets you measure exactly how much it moves the needle in your app.

Measuring Results

A paywall strategy without measurement is just guessing. Track these events through Amply to understand how your campaigns perform:

// Track paywall impressions with source attribution
fun showPaywallScreen(source: String) {
    val amply = (application as MyApp).amply
    amply.track("PaywallViewed")
    amply.setCustomProperties(mapOf(
        "last_paywall_source" to source
    ))
    // ... show your paywall UI
}

// Track paywall dismissals
fun onPaywallClosed() {
    val amply = (application as MyApp).amply
    amply.track("PaywallDismissed")
}

// Track conversion
fun onPurchaseCompleted(plan: String, price: Double) {
    val amply = (application as MyApp).amply
    amply.track("SubscriptionStarted")
    amply.setCustomProperties(mapOf(
        "is_premium" to true,
        "subscription_plan" to plan
    ))
}

With these events flowing into Amply, you can measure:

  • Paywall view rate: How many sessions result in a paywall impression? Your Nth session rule directly controls this.
  • Conversion rate by session number: Do users convert more on session 3 or session 6? Adjust your every N cadence accordingly.
  • Second chance recovery rate: What percentage of users who dismissed the first paywall converted on the discount offer?
  • Fatigue signal: If dismissal rates climb as you increase frequency, you're showing too often. Use the minimum interval setting to add breathing room.

The key advantage is iteration speed. Spot a drop in conversion at session 9? Change the WhenRepeat Rules value from every 3 to every 5 in the dashboard. The change ships on the next config fetch — live in minutes, with no app update required.


Conclusion

Paywall timing isn't a "set it and forget it" decision. It's an ongoing optimization loop: show, measure, adjust, repeat. A meaningful share of the gap between a paywall that works and one that doesn't comes down to when you ask, not what you offer.

With Amply, you move paywall orchestration out of your codebase and into a configurable campaign system. The every N repeat pattern gives you recurring, non-intrusive nudges. The second chance campaign turns dismissed paywalls into recovered revenue. And impression limits with time-based intervals ensure you never cross the line from persistent to annoying.

Here's the full picture of what we built:

  1. Session tracking with a single SDK call
  2. Every Nth session paywall via the every N repeat pattern
  3. Second chance discount offer for warm leads
  4. Deeplink-driven paywall rendering
  5. Event tracking for continuous measurement and optimization

Stop guessing when to show your paywall. Start orchestrating it. Get started with Amply and turn session count into your most powerful conversion lever.