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. With a single launch flag (isSubscribed: false), the AI Trainer runs a complete free tier for you:


1. Full onboarding. The trainer interviews the user: goals, body parts, injuries, schedule, equipment. Every answer streams to your app as events, so you learn who this user is even if they never pay.

2. A real camera fitness assessment. Up to two guided exercises (a 30-second squat test and a 30-second push-up test) with live motion tracking: rep counting, form scoring, and a fitness level verdict. This is the "wow" moment. The user has now used the product, not read about it.

3. A personalized plan preview. The trainer shows the exact structure of the first workout it would build (phases, minutes, "every exercise set to your level") next to a Create my workout button, and a locked chat input that cycles through prompts a subscriber could ask ("Adjust the workout structure", "Analyze my last workout", "Make it easier on my knees").

4. The conversion moment. Tapping Create my workout or the locked input fires one event, open_subscription_flow. You overlay your paywall. On purchase, the parked workout generates immediately: no relaunch, no second tap.


Free users who have invested five minutes of answers, performed a tracked assessment, and seen their own plan one tap away convert at a far higher rate than users hitting a paywall cold. And because KinesteX never shows a paywall and never processes payments, your app owns the entire purchase flow and 100% of the pricing.


The funnel at a glance:


typescript
1Onboarding wizard (free)                 → trainer_profile_onboarding per step
234Camera fitness assessment (free)         → trainer_assessment_completed
5        │   real motion tracking: reps, form score, fitness level
67Readiness check-in (free)
8910Workout structure preview + locked chat  ← the free tier's edge
11        │   user taps "Create my workout" or the locked input
1213open_subscription_flow                   → your paywall, overlaid on top
14        │   you post { "subscription_result": "purchased" }
1516Parked workout generates immediately, chat unlocks

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).

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. A user who reports knee problems gets a chair squat variant; pressing-chain injuries skip the push-up test. You never have to manage which exercises run. If no test is safe to run, the assessment step is withheld and the user self-reports instead.
  • 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 what the chat does before you ask them to pay for it.

The journey, screen by screen

What the free user actually sees, in order:


1. Assessment personalization. After onboarding, the trainer adapts the camera test to the user's profile. The interlude visualizes their own answers (duration, frequency, goal, equipment, focus areas) orbiting while it prepares.

2. The tracked assessment. The user performs the tests in front of the camera and lands on the statistics screen: workout score, accuracy score, activity level, and a per-exercise breakdown with reps counted and calories. This is real KinesteX motion tracking, free.

3. Result confirmation in chat. Back in the chat, the result becomes an editable card: reps, form percentage, and the computed level (Beginner / Intermediate / Advanced), with Retest one tap away.

4. The preview and the paywall trigger. The trainer lays out the first workout's structure: phases with per-phase minutes, built from the user's goal and equipment, with the note "Every exercise set to your level." Below it: 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 on the preview (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, so a purchase from here also produces the workout immediately.

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. The source value is also a strong analytics signal: users arriving via chat_input were sold by the chat itself, which tells you the locked-input preview is doing its job.


Both values arrive through the same handler you already have; route them to the same paywall.

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 rest of 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, the assessment stays reachable, 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.


Platforms without runtime messaging (React Native, Flutter, React TS): remount the trainer with isSubscribed: true after a purchase. The confirmed onboarding profile is already persisted, so the user lands back in the chat and taps Create my workout once; only the parked one-tap resume is lost. Standalone / link integrations with no host app can pass subscriptionReturnUrl at launch instead: the trainer redirects the browser there rather than 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. See Assessment & profile sync for the full payloads.


That data powers your win-back campaigns: "You measured 21 squats last week. Your Beginner-to-Intermediate plan is one tap away." And because you pass the profile back on the next launch (pre-filling), a returning free user resumes exactly where they left off 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.