KinesteX

Free Trial & Paid Conversion (AI Trainer)

Turn non-subscribed users into paying customers by letting them experience the product before they ever see your paywall. One launch flag (isSubscribed: false) runs the entire free tier for you:


1. Full onboarding. The trainer interviews the user; every answer streams to your app as events.

2. A real camera fitness assessment. Up to two guided 30-second tests with live rep counting, form scoring, and a fitness level verdict. The user has now used the product, not read about it.

3. A personalized plan preview. The exact structure of their first workout, one tap away, next to a Create my workout button and a locked chat input cycling through prompts a subscriber could ask.

4. The conversion moment. Either tap fires one event, open_subscription_flow; you overlay your paywall. On purchase, the parked workout generates immediately.


A user who has invested five minutes of answers, performed a tracked assessment, and can see their own plan one tap away converts far better than one hitting a paywall cold.


KinesteX never shows a paywall and never processes payments. Your app owns the entire purchase flow and 100% of the pricing.

Requirements: the standard AI Trainer Chat integration. For the full flow on native, use Swift KinesteXAIKit ≥ 1.1.4 (adds the workoutAction binding for posting the purchase result) and Kotlin KinesteX-SDK-Kotlin ≥ 2.0.6 (sendAction). Platforms without runtime messaging can remount with isSubscribed: true instead (see Unlock after purchase).

The funnel at a glance

Onboarding → camera assessment → readiness check-in → plan preview with locked chat (all free) → open_subscription_flow → your paywall on top → you post subscription_result → on "purchased" the parked workout generates and the chat unlocks; on "dismissed" the preview stays and the user can try again.

Onboarding interview

FREE

Goals, injuries, schedule, equipment. Every answer streams to your app.

trainer_profile_onboardingper step, to your app

Camera fitness assessment

FREE

Real motion tracking: reps counted, form scored, fitness level measured.

trainer_assessment_completedto your app

Readiness check-in

FREE

Soreness and energy today, so the plan matches the moment.

Plan preview + locked chat

FREE

The first workout's structure, one tap away, next to Create my workout and the locked input cycling through subscriber prompts.

The conversion moment

CONVERSION

Either tap fires one event. Overlay your paywall on top of the trainer.

open_subscription_flowsource: "generate_workout"open_subscription_flowsource: "chat_input"
subscription_result: "purchased"

The parked workout generates immediately. No relaunch, no second tap.

subscription_result: "dismissed"

The preview stays on screen. The user can tap Create my workout again later.

Workout generates, chat unlocks

SUBSCRIBER

The user trains on the plan they already saw, and the full chat opens up.

Events flow through your existing message handler; you post subscription_result back after your paywall closes.

What free users get (and what stays gated)

You control exactly one switch: isSubscribed. Everything else is automatic. A missing flag or true means fully subscribed; only an explicit false enables the free tier.


ExperienceFree user (isSubscribed: false)Subscriber
Onboarding interview (goals, injuries, schedule)✅ Full✅ Full
Camera fitness assessment with motion tracking✅ Full, including retests✅ Full
Assessment results, fitness level, form score✅ Full✅ Full
Readiness check-in✅ Full✅ Full
Personalized workout structure preview✅ Shown with Create my workoutSkipped (generates right away)
Chat input🔒 Locked, shows rotating example prompts, tap = conversion event✅ Unlocked
Workout generation and training🔒 Gated behind open_subscription_flow✅ Full

Two details worth knowing:


  • The assessment adapts to injuries on its own. Knee problems swap in a chair squat; pressing-chain injuries skip the push-up test; if no test is safe, the step is withheld and the user self-reports. You never manage which exercises run.
  • The locked input is a feature, not a wall. It cycles through real prompts a subscriber could ask, so the free user sees the breadth of the chat before you ask them to pay for it.

The journey, screen by screen

What the free user actually sees, in order:


1. Assessment personalization. The trainer adapts the camera test to the user's profile, visualizing their own answers while it prepares.

2. The tracked assessment. Camera tests end on a statistics screen: workout score, accuracy score, and a per-exercise breakdown with reps and calories.

3. Result confirmation in chat. The result becomes an editable card (reps, form percentage, computed level) with Retest one tap away.

4. The preview and the paywall trigger. The first workout's phase-by-phase structure, then the Create my workout button and the locked chat input. Both routes lead to your subscription flow.

Launch the trainer for free users

Mount the trainer exactly as in the standard integration, passing your live entitlement state as isSubscribed. Pass it on every mount so a purchase made elsewhere (web checkout, promo code, family plan) is reflected on the next launch.


Optional but recommended: brand the trainer as your own coach with aiTrainerName and aiTrainerColor (see Branding & appearance), so the free preview sells your product, not a third-party SDK.

Mount with the free tier enabled
Swift example:
1// Binding used later to post the purchase result back into the trainer.
2@State private var trainerAction: [String: Any]? = nil
3
4kit.createTrainerChatView(
5    user: nil,                        // or your UserDetails
6    style: IStyle(style: "dark"),
7    isLoading: $isLoading,
8    customParams: [
9        "isSubscribed": SubscriptionManager.shared.isActive, // false = free tier
10        // Optional branding, so the preview sells YOUR coach:
11        "aiTrainerName": "ARIA+",
12        "aiTrainerColor": "#7C4DFF",
13    ],
14    workoutAction: $trainerAction,    // KinesteXAIKit >= 1.1.4
15    onMessageReceived: handleMessage
16)

The conversion moments and the event you receive

A free user hits the gate from two places, and the source field tells you which:


json
1{ "type": "open_subscription_flow", "source": "generate_workout", "date": "01 09 2026 14:52:10" }
2{ "type": "open_subscription_flow", "source": "chat_input",        "date": "01 09 2026 14:52:10" }

  • "generate_workout": the user tapped Create my workout (or asked a returning-user flow to rebuild their plan). Their readiness answers are parked so the workout can generate the instant they purchase.
  • "chat_input": the user tapped the locked chat input. The pending generation is parked the same way.

Treat the event as an instruction, not a status: present your subscription flow now, overlaid on top of the trainer view. Do not dismiss the trainer; the parked generation is waiting underneath.

Both values arrive through the same handler and lead to the same paywall. source is also a strong analytics signal: chat_input conversions were sold by the chat itself.

Handle the event
Swift example:
1private func handleMessage(_ message: KinestexMessage) {
2    switch message {
3    case .exit_kinestex:
4        showKinesteX = false
5    case .custom_type(let data):
6        guard let type = data["type"] as? String else { return }
7        if type == "open_subscription_flow" {
8            let source = data["source"] as? String // "generate_workout" | "chat_input"
9            analytics.track("paywall_shown", ["source": source ?? "unknown"])
10            presentSubscriptionFlow()
11        }
12    default:
13        break
14    }
15}

Unlock after purchase

When your subscription flow closes, tell the trainer what happened by posting exactly one subscription_result, from every exit path (purchase success, close button, swipe-down, back gesture, purchase failure):


  • "purchased": the user is unlocked for the session, the parked workout generates immediately, and the chat input opens up. No relaunch, no second tap.
  • "dismissed": the pending request is dropped. The preview stays on screen and the user can tap Create my workout again later.

The message is idempotent: the first result wins and repeats are ignored, so a double-fired callback does no harm.

No runtime channel on your platform? Two alternatives:


  • React Native / Flutter / React TS: remount the trainer with isSubscribed: true after a purchase. The confirmed profile is already persisted; the user lands back in the chat and taps Create my workout once.
  • Standalone / link integrations with no host app: pass subscriptionReturnUrl at launch. The trainer redirects the browser there instead of posting the event, and your page relaunches KinesteX with the same userId and isSubscribed: true.

To enforce the status server-side so clients cannot bypass the gate, register the subscription with the Trainer API; the backend then becomes the source of truth and overrides the launch flag.

Report the outcome
Swift example:
1func presentSubscriptionFlow() {
2    let paywall = PaywallViewController()
3    paywall.onPurchaseCompleted = { [weak self] in
4        self?.trainerAction = ["subscription_result": "purchased"] // parked workout generates
5        paywall.dismiss(animated: true)
6    }
7    paywall.onDismissed = { [weak self] in
8        self?.trainerAction = ["subscription_result": "dismissed"] // trainer resumes, preview stays
9    }
10    present(paywall, animated: true)
11}

What you learn about users who don't convert

The free tier is also a data funnel. Even a user who dismisses the paywall has handed you a complete fitness profile and a measured assessment, all delivered as events while they onboarded:


  • trainer_profile_onboarding fires at every applied wizard step with that stage's answer (goals, injuries, schedule, equipment), using the same field names as the profile object. Mirror them into your CRM as they arrive.
  • trainer_profile_updated fires once the questionnaire is confirmed, with the full profile.
  • trainer_assessment_completed delivers the measured result:

json
1{
2  "type": "trainer_assessment_completed",
3  "date": "01 09 2026 14:52:10",
4  "results": {
5    "squats":  { "reps": 21, "level": "intermediate" },
6    "pushups": { "reps": null, "level": null }
7  },
8  "fitnessLevel": "intermediate"
9}

A slot the injury-adapted plan didn't measure keeps its object shape with null fields, so results.squats.reps never throws. Full payloads: Assessment & profile sync.


That data powers win-back campaigns ("You measured 21 squats last week. Your plan is one tap away."), and pre-filling it on the next launch means a returning free user resumes instead of starting over.

Launch checklist

  • isSubscribed passed on every mount from your live entitlement state (missing or true means subscribed; only an explicit false enables the free tier)
  • open_subscription_flow handled for both source values (generate_workout and chat_input) and routed to your paywall, overlaid on top of the trainer view
  • Every exit path of your paywall posts subscription_result = "purchased" or "dismissed" (Swift/Kotlin/HTML), or remounts with isSubscribed: true (React Native / Flutter / React TS)
  • Verified end to end: free onboarding → camera assessment runs and reports results → preview + locked input shown → tap fires the event → sandbox purchase. On Swift / Kotlin / HTML, "purchased" makes the parked workout generate without a relaunch; on React Native / Flutter / React TS, the remount comes up unlocked and one tap of Create my workout generates
  • Verified the dismissal path: closed paywall + "dismissed" (or a remount still on isSubscribed: false) → trainer resumes with the preview still on screen
  • Onboarding and assessment events (trainer_profile_onboarding, trainer_profile_updated, trainer_assessment_completed) forwarded to your backend for analytics and win-back campaigns
  • Purchases made outside the app (web checkout, promo codes) reflected via isSubscribed: true on the next mount, or enforced server-side with managed subscriptions

For the mechanics of the gate itself (edge cases, duplicate messages, app killed mid-purchase), see the Subscription gating guide.