Skip to content
Tolinku
Tolinku
Sign In Start Free
Use Cases · · 5 min read

Onboarding for Subscription Apps

By Tolinku Staff
|
Tolinku e commerce deep linking dashboard screenshot for use cases blog posts

Subscription apps have a unique onboarding constraint: the user needs to experience enough value during the trial to justify a recurring payment. Unlike one-time purchases, subscriptions require ongoing perceived value. Onboarding has to demonstrate that value quickly, and deep links play a role in getting users to the right features at the right time.

For freemium approaches, see Onboarding for Freemium Apps: Free to Paid Journey. For personalization, see Personalized Onboarding Flows with Deep Link Data. For general onboarding principles, see Onboarding Best Practices for Mobile Apps in 2026.

Trial Onboarding Strategy

Trial Types and Onboarding Goals

Trial Type Duration Onboarding Goal
Full-access trial 7-14 days Expose all premium features
Limited trial 3-7 days Drive urgency, focus on core value
Reverse trial No trial (premium first, then downgrade) Create loss aversion
Metered trial X uses of premium features Demonstrate specific feature value

Day-by-Day Trial Onboarding

Structure the trial period to maximize feature exposure:

const trialPlan = {
  day0: {
    goal: 'Core activation',
    push: null, // No push on day 0
    features: ['core_workflow'],
    email: 'welcome',
  },
  day1: {
    goal: 'Second value moment',
    push: { title: 'Try [Feature A]', deepLink: '/feature/a' },
    features: ['feature_a'],
    email: 'getting_started',
  },
  day3: {
    goal: 'Premium feature exposure',
    push: { title: 'Your premium feature: [Feature B]', deepLink: '/feature/b' },
    features: ['feature_b'],
    email: 'premium_highlight',
  },
  day5: {
    goal: 'Habit formation',
    push: { title: 'Your weekly summary is ready', deepLink: '/summary' },
    features: ['analytics', 'reports'],
    email: null,
  },
  day7: {
    goal: 'Social features',
    push: { title: 'Invite your team for free', deepLink: '/team/invite' },
    features: ['collaboration', 'sharing'],
    email: 'team_invite',
  },
  day10: {
    goal: 'Advanced features',
    push: { title: 'Power tip: automate your workflow', deepLink: '/automations' },
    features: ['automations', 'integrations'],
    email: 'advanced_tips',
  },
  day12: {
    goal: 'Conversion prep',
    push: { title: 'Your trial ends in 2 days', deepLink: '/upgrade' },
    features: [],
    email: 'trial_ending_soon',
  },
  day14: {
    goal: 'Conversion',
    push: { title: 'Your trial has ended', deepLink: '/upgrade' },
    features: [],
    email: 'trial_ended',
  },
};

Implementation

async function executeTrialDay(userId, day) {
  const plan = trialPlan[`day${day}`];
  if (plan === undefined) return;

  const user = await getUser(userId);

  // Send push if scheduled and user hasn't already used the feature
  if (plan.push && user.hasUsedFeature(plan.features[0]) === false) {
    await sendPush(userId, {
      title: plan.push.title,
      body: plan.push.body || 'Tap to try it.',
      data: { deepLink: plan.push.deepLink },
    });
  }

  // Send email if scheduled
  if (plan.email) {
    const emailShouldSend = await shouldSendEmail(userId, plan.email);
    if (emailShouldSend) {
      await sendTrialEmail(userId, plan.email, plan);
    }
  }
}

Paywall Placement

When to Show the Paywall

Approach Conversion Rate User Sentiment
Before any use (hard paywall) 2-5% Negative (haven't seen value)
After first value moment 8-15% Positive (experienced value)
At trial end 15-25% Neutral to positive
When user hits a limit 10-20% Motivated (wants more)
Never (always soft) 3-8% Positive but low urgency

The highest conversion rates come from showing the paywall after the user has experienced value and when there's a natural trigger (trial ending, limit reached). For techniques on tracking and improving these metrics, see Improving Onboarding Completion Rates.

Paywall Design for Onboarding

function OnboardingPaywall({ user, trigger }) {
  return (
    <Screen>
      <TrialSummary
        daysUsed={user.trialDaysUsed}
        featuresUsed={user.premiumFeaturesUsed}
        itemsCreated={user.itemCount}
      />

      <Heading>Keep your premium features</Heading>

      <PlanComparison
        plans={[
          { name: 'Monthly', price: '$9.99/mo', savings: null },
          { name: 'Annual', price: '$79.99/yr', savings: 'Save 33%', recommended: true },
        ]}
      />

      <Button primary onPress={() => subscribe('annual')}>
        Start Annual Plan
      </Button>

      <Button secondary onPress={() => subscribe('monthly')}>
        Start Monthly Plan
      </Button>

      <LinkButton onPress={dismiss}>
        Continue with Free
      </LinkButton>
    </Screen>
  );
}

Personalized Paywall Based on Usage

Show the paywall differently based on which features the user actually used:

function getPaywallCopy(user) {
  const topFeature = user.mostUsedPremiumFeature;

  const copy = {
    analytics: {
      headline: 'Keep tracking your performance',
      body: `You've checked your analytics ${user.analyticsViews} times. Subscribe to keep access.`,
    },
    collaboration: {
      headline: 'Keep collaborating with your team',
      body: `You and ${user.teamSize} teammates are working together. Subscribe to continue.`,
    },
    export: {
      headline: 'Keep exporting your data',
      body: `You've exported ${user.exportCount} reports. Subscribe for unlimited exports.`,
    },
    default: {
      headline: 'Unlock all premium features',
      body: 'Subscribe to access advanced analytics, team collaboration, exports, and more.',
    },
  };

  return copy[topFeature] || copy.default;
}
async function sendTrialReminderPush(userId, daysRemaining) {
  const deepLink = await Tolinku.createLink({
    path: '/upgrade',
    params: {
      source: 'push',
      trigger: `trial_${daysRemaining}d_remaining`,
      offer: daysRemaining <= 1 ? 'last_chance' : null,
    },
  });

  const messages = {
    3: { title: '3 days left in your trial', body: 'Subscribe to keep all premium features.' },
    1: { title: 'Last day of your trial', body: 'Subscribe now to avoid losing access.' },
    0: { title: 'Your trial has ended', body: 'Subscribe to restore premium features.' },
  };

  const msg = messages[daysRemaining];

  await sendPush(userId, {
    title: msg.title,
    body: msg.body,
    data: { deepLink: deepLink.url },
  });
}

Win-Back Deep Links (Post-Trial)

For users who didn't convert when the trial ended:

const winBackSequence = [
  {
    daysAfterTrialEnd: 3,
    channel: 'push',
    title: 'Missing [Feature]?',
    body: 'Come back and get 20% off your first month.',
    deepLink: '/upgrade?promo=COMEBACK20',
  },
  {
    daysAfterTrialEnd: 7,
    channel: 'email',
    subject: 'We saved your data',
    body: 'Your projects and data are still here. Subscribe anytime to pick up where you left off.',
    deepLink: '/home',
  },
  {
    daysAfterTrialEnd: 14,
    channel: 'push',
    title: 'Special offer: 50% off for 3 months',
    body: 'Limited time only.',
    deepLink: '/upgrade?promo=WINBACK50',
  },
  {
    daysAfterTrialEnd: 30,
    channel: 'email',
    subject: 'Start a new trial?',
    body: 'A lot has changed since you last tried [App]. Start a fresh 7-day trial.',
    deepLink: '/trial/restart',
  },
];

Feature Adoption Tracking

Which Features Drive Conversion

async function featureConversionCorrelation() {
  const converters = await getUsers({ convertedDuringTrial: true });
  const nonConverters = await getUsers({ convertedDuringTrial: false, trialEnded: true });

  const features = getAllPremiumFeatures();

  for (const feature of features) {
    const usedByConverters = converters.filter(u => u.usedFeature(feature)).length;
    const usedByNonConverters = nonConverters.filter(u => u.usedFeature(feature)).length;

    console.log(feature, {
      converterUsage: (usedByConverters / converters.length * 100).toFixed(1) + '%',
      nonConverterUsage: (usedByNonConverters / nonConverters.length * 100).toFixed(1) + '%',
      lift: ((usedByConverters / converters.length) / (usedByNonConverters / nonConverters.length)).toFixed(2) + 'x',
    });
  }
}

Features with a high lift value (converters use it much more than non-converters) are your conversion drivers. Push users toward these features during the trial.

Trial Health Score

function calculateTrialHealthScore(user) {
  const factors = {
    daysActive: Math.min(user.activeDays / user.trialLength, 1) * 25,
    premiumFeaturesUsed: Math.min(user.premiumFeaturesUsed / 5, 1) * 25,
    contentCreated: Math.min(user.itemCount / 3, 1) * 25,
    returnVisits: Math.min(user.sessionCount / 5, 1) * 25,
  };

  return Object.values(factors).reduce((sum, val) => sum + val, 0);
}

// Health score 0-100
// 0-25: Low engagement, unlikely to convert
// 25-50: Moderate, needs nudging
// 50-75: Good engagement, time for upgrade prompt
// 75-100: High engagement, likely to convert

Measuring Subscription Onboarding

Key Metrics

Metric Formula Benchmark
Trial start rate Trials started / Installs 30-50%
Trial activation rate Activated / Trials started 50-70%
Trial-to-paid rate Subscribed / Trials started 15-25%
Time to first premium feature Median time from trial start < 24 hours
Premium features tried Avg features used during trial 3-5
Day 30 retention (subscribers) Active on D30 / Subscribed 70-85%

Revenue Impact

Monthly revenue per install = Trial start rate × Trial-to-paid rate × Monthly price
Example: 40% × 20% × $9.99 = $0.80 per install

Improving trial-to-paid by 5% (20% → 25%):
New: 40% × 25% × $9.99 = $1.00 per install (+25% revenue lift)

Small improvements in trial conversion compound significantly at scale.

For deep linking features, see Tolinku deep linking. For onboarding use cases, see the onboarding documentation.

Get deep linking tips in your inbox

One email per week. No spam.

Ready to add deep linking to your app?

Set up Universal Links, App Links, deferred deep linking, and analytics in minutes. Free to start.