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

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 session count using the built-in @session.counter variable. Instead of hardcoding logic, you define targeting rules in the Amply dashboard, and the SDK evaluates them 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, etc. The targeting rule uses the modulo operator:

// Amply targeting rule: show paywall every 3rd session
@session.counter % 3 == 0

// This matches sessions: 3, 6, 9, 12, 15, ...
// Combined with is_premium = false to skip subscribers

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:

// Amply targeting rule: show paywall during sessions 3-7
@session.counter >= 3 AND @session.counter <= 7

// Perfect for the "evaluation window" when users
// have enough context to make a purchase decision

You can combine both patterns too. For instance, show the paywall on every 3rd session, but only after the user has completed at least 3 sessions:

@session.counter >= 3 AND @session.counter % 3 == 0
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
class MyApp : Application() {
    override fun onCreate() {
        super.onCreate()

        // Initialize the Amply SDK
        AmplySDK.initialize(this, "YOUR_API_KEY")

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

        // Track session start - this increments @session.counter
        AmplySDK.track("SessionStart")
    }
}

Every time AmplySDK.track("SessionStart") fires, the SDK increments the internal session counter. This is the value that @session.counter references in your targeting rules.

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
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    // Register deeplink handler for Amply campaigns
    AmplySDK.setDeeplinkHandler { url ->
        when {
            url.contains("show-paywall") -> {
                showPaywallScreen(source = "amply_campaign")
            }
            url.contains("show-discount-paywall") -> {
                showDiscountPaywall(
                    source = "amply_second_chance",
                    discount = 50
                )
            }
        }
    }
}

Step 3: Configure the Campaign in Amply Dashboard

In the Amply dashboard, create a new campaign with these settings:

  • Trigger event: SessionStart
  • Targeting rule: @session.counter % 3 == 0 AND is_premium = false
  • Action: Deeplink to happens://show-paywall
  • Impression limit: 1 per session (prevents double-showing on rapid restarts)
  • Minimum interval: 24 hours between impressions (respects user attention)

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 it takes effect immediately for all users.


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 shows a custom limited-time discount offer to users who previously dismissed your paywall. Think of it as abandoned cart retargeting for mobile subscriptions.

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, show a custom popup: "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:

  • Trigger event: SessionStart
  • Targeting rule: @session.counter >= 2 AND @session.counter <= 3 AND is_premium = false
  • Action: Deeplink to happens://show-discount-paywall
  • Impression limit: 1 per device lifetime (show this offer exactly once)
  • Minimum interval: 12 hours after last paywall impression

Handling the Discount Paywall in Code

// Track when user dismisses the standard paywall
fun onPaywallDismissed() {
    AmplySDK.track("PaywallDismissed")
    AmplySDK.setCustomProperties(mapOf(
        "saw_paywall" to true,
        "is_premium" to false
    ))
}

// Track successful subscription
fun onSubscriptionCompleted(plan: String) {
    AmplySDK.track("SubscriptionStarted")
    AmplySDK.setCustomProperties(mapOf(
        "is_premium" to true,
        "subscription_plan" to plan
    ))
}

// Show the discount paywall with urgency
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 can recover 10-15% of these users.

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) {
    AmplySDK.track("PaywallViewed")
    AmplySDK.setCustomProperties(mapOf(
        "last_paywall_source" to source
    ))
    // ... show your paywall UI
}

// Track paywall dismissals
fun onPaywallClosed() {
    AmplySDK.track("PaywallDismissed")
}

// Track conversion
fun onPurchaseCompleted(plan: String, price: Double) {
    AmplySDK.track("SubscriptionStarted")
    AmplySDK.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 modulo value 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 campaign targeting from @session.counter % 3 == 0 to @session.counter % 5 == 0 in the dashboard. The change is live immediately — 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. The difference between a 2% and a 5% conversion rate often 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 @session.counter % N == 0 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 modulo targeting
  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.