Skip to main content

Amplitude + Tour Kit: measuring onboarding impact on retention

Wire Tour Kit callbacks to Amplitude track() for onboarding funnels, behavioral cohorts, and retention analysis. TypeScript examples included.

DomiDex
DomiDexCreator of Tour Kit
April 7, 20269 min read
Share
Amplitude + Tour Kit: measuring onboarding impact on retention

Amplitude + Tour Kit: measuring onboarding impact on retention

Most teams track whether users finish their onboarding tour. Fewer teams ask the harder question: does finishing the tour actually change retention?

Amplitude's behavioral cohort model answers this directly. You split users into two groups (those who completed the tour within 24 hours of signup, and those who didn't) then compare their Day-7, Day-14, and Day-30 retention curves. The gap between those curves is the tour's real ROI.

Calm ran this exact analysis and found retention was 3x higher among users who completed a single onboarding step compared to those who skipped it.

But Amplitude doesn't ship a product tour component. And most tour libraries don't ship typed analytics callbacks. Tour Kit bridges this: its onTourStart, onStepView, onTourComplete, and onTourSkip callbacks map directly to Amplitude events, which feed funnels and cohorts without manual instrumentation glue.

This tutorial covers the full loop: install, instrument, funnel, cohort, retention analysis.

npm install @tourkit/core @tourkit/react @amplitude/analytics-browser

What you'll build

Tour Kit's TourKitProvider accepts four analytics callbacks that fire on every tour lifecycle event: start, step view, complete, and skip. By the end of this tutorial, those callbacks will send structured events to Amplitude with typed properties, giving you a step-level funnel, behavioral cohorts for completers vs. skippers, and a retention analysis chart that shows whether your onboarding tour actually moves the needle on Day-7 and Day-30 retention. The whole integration is about 70 lines of TypeScript.

One limitation to know upfront: Tour Kit doesn't have a built-in Amplitude adapter, and it requires React 18+ (no older React or React Native support). You'll wire the callbacks manually using @amplitude/analytics-browser.

The @tourkit/analytics package provides a plugin interface if you later need multi-provider support, but for Amplitude alone the direct approach is cleaner.

Prerequisites

  • React 18.2+ or React 19
  • An Amplitude account (the free tier covers 50,000 MTUs/month, as of April 2026)
  • Tour Kit installed (@tourkit/core + @tourkit/react)
  • A working product tour with at least 3 steps

No tour yet? The Next.js App Router tutorial gets you there from scratch.

Step 1: Initialize Amplitude in your React app

Amplitude's browser SDK (@amplitude/analytics-browser) doesn't ship a React-specific package. The deprecated react-amplitude library was never replaced with an official hooks-based successor. You initialize the SDK once at the top of your component tree and call track() from anywhere. No provider wrapper needed.

// src/lib/amplitude.ts
import * as amplitude from '@amplitude/analytics-browser'

let initialized = false

export function initAmplitude() {
  if (initialized) return

  amplitude.init(process.env.NEXT_PUBLIC_AMPLITUDE_API_KEY!, {
    autocapture: { elementInteractions: false },
  })

  initialized = true
}

export { amplitude }

Call initAmplitude() from your root layout:

// src/app/layout.tsx
'use client'

import { useEffect } from 'react'
import { initAmplitude } from '@/lib/amplitude'
import { TourKitProvider } from '@tourkit/react'

export default function RootLayout({ children }: { children: React.ReactNode }) {
  useEffect(() => {
    initAmplitude()
  }, [])

  return (
    <TourKitProvider>
      {children}
    </TourKitProvider>
  )
}

Add your API key to .env.local:

NEXT_PUBLIC_AMPLITUDE_API_KEY=your_amplitude_api_key

The autocapture: { elementInteractions: false } flag disables Amplitude's automatic click tracking. You want explicit events only, which gives you cleaner data and a smaller network footprint. Amplitude batches events and uses lightweight compression (60-80% payload reduction, per their docs), so the SDK's runtime overhead on tour interactions is negligible.

Step 2: Define a typed event schema

Inconsistent event names are the number one pain point developers report with Amplitude. One Reddit thread put it bluntly: "We ended up with inconsistent event names, broken user IDs that don't unify across devices or sessions." A typed schema prevents this at compile time.

// src/lib/tour-events.ts
type TourEventMap = {
  'tour_started': {
    tour_id: string
    total_steps: number
  }
  'tour_step_viewed': {
    tour_id: string
    step_id: string
    step_index: number
    total_steps: number
  }
  'tour_completed': {
    tour_id: string
    total_steps: number
    time_to_complete_ms: number
  }
  'tour_dismissed': {
    tour_id: string
    dismissed_at_step: number
    total_steps: number
    completion_pct: number
  }
}

export type TourEventName = keyof TourEventMap
export type TourEventProperties<T extends TourEventName> = TourEventMap[T]

This gives you autocomplete and type checking on every track() call. If someone misspells tour_started or passes a string where a number belongs, TypeScript catches it before the event ever reaches Amplitude. Clean event schemas also matter for Amplitude's Agentic AI features — as of February 2026, AI agents drive 25% of platform queries, and they work best with consistent, well-typed event data.

Step 3: Wire Tour Kit callbacks to Amplitude

Tour Kit's TourKitProvider accepts onTourStart, onStepView, onTourComplete, and onTourSkip as props. Each callback receives the tour ID, step ID, and step index, which covers everything Amplitude needs as event properties.

// src/providers/tracked-tour-provider.tsx
'use client'

import { TourKitProvider } from '@tourkit/react'
import { amplitude } from '@/lib/amplitude'
import { useRef } from 'react'

export function TrackedTourProvider({ children }: { children: React.ReactNode }) {
  const tourStartTimes = useRef<Map<string, number>>(new Map())

  return (
    <TourKitProvider
      onTourStart={(tourId) => {
        tourStartTimes.current.set(tourId, Date.now())

        amplitude.track('tour_started', {
          tour_id: tourId,
          total_steps: 0, // updated on first step view
        })
      }}
      onStepView={(tourId, stepId, stepIndex) => {
        amplitude.track('tour_step_viewed', {
          tour_id: tourId,
          step_id: stepId,
          step_index: stepIndex,
          total_steps: 0, // set from tour config
        })
      }}
      onTourComplete={(tourId) => {
        const startTime = tourStartTimes.current.get(tourId) ?? Date.now()
        const elapsed = Date.now() - startTime

        amplitude.track('tour_completed', {
          tour_id: tourId,
          total_steps: 0,
          time_to_complete_ms: elapsed,
        })

        // Set a user property for cohort building
        const identify = new amplitude.Identify()
        identify.set(`tour_completed_${tourId}`, true)
        identify.set(`tour_completed_${tourId}_at`, new Date().toISOString())
        amplitude.identify(identify)

        tourStartTimes.current.delete(tourId)
      }}
      onTourSkip={(tourId, stepIndex) => {
        amplitude.track('tour_dismissed', {
          tour_id: tourId,
          dismissed_at_step: stepIndex,
          total_steps: 0,
          completion_pct: 0,
        })

        tourStartTimes.current.delete(tourId)
      }}
    >
      {children}
    </TourKitProvider>
  )
}

Replace TourKitProvider in your layout with TrackedTourProvider and every tour in your app sends events to Amplitude automatically. No per-tour wiring.

The Identify call on completion sets a user property (tour_completed_onboarding-v2: true) that persists across sessions. This is what powers cohort creation in step 5.

Step 4: Build a step-level funnel in Amplitude

Amplitude's funnel analysis takes those four events and shows exactly where users drop off during your tour. According to Amplitude's onboarding measurement guide, identifying the largest drop-off and fixing it first is the single most impactful thing you can do. Teams that redesign flows based on drop-off data see trial-to-paid conversion improvements of 20-25%.

In your Amplitude dashboard:

  1. Go to Analytics > Create > Funnel Analysis
  2. Add these events in order:
    • tour_started (filter: tour_id = onboarding-v2)
    • tour_step_viewed (filter: step_index = 0)
    • tour_step_viewed (filter: step_index = 1)
    • tour_step_viewed (filter: step_index = 2)
    • tour_completed (filter: tour_id = onboarding-v2)
  3. Set the conversion window to 1 hour

The resulting chart shows conversion between each step as a percentage. If step 2 drops from 80% to 45%, that step's content or placement needs work.

Funnel stepTypical conversionRed flag threshold
Start → Step 185-95%Below 75%
Step N → Step N+170-85%Below 60%
Last step → Complete60-80%Below 45%
Overall (5-step tour)30-40%Below 20%

Use Amplitude's "Group by" feature to segment funnels by user properties: signup source, plan type, device. A tour that works on desktop but collapses on mobile tells you something different than one that fails everywhere.

Step 5: Create behavioral cohorts

The funnel tells you where users drop off. Cohorts tell you whether dropping off matters. This is the part most analytics tutorials skip, and it's the part that actually justifies the instrumentation work.

Amplitude's behavioral cohort model lets you define groups by actions taken (or not taken), then compare their downstream behavior. With Tour Kit events flowing in, you can answer: "Do users who complete the onboarding tour retain better than users who skip it?"

In Amplitude:

  1. Go to Cohorts > Create Cohort
  2. Define "Tour completers": users who have user property tour_completed_onboarding-v2 equals true
  3. Add "Tour skippers": users who performed tour_dismissed where tour_id = onboarding-v2
  4. Add "Tour unseen": users who signed up but never triggered tour_started with tour_id = onboarding-v2

Three cohorts, not two. The "unseen" group is your control: users who never encountered the tour. Comparing completers against skippers alone introduces selection bias. Users who finish tours may already be more motivated. The unseen group gives you a baseline.

Step 6: Run a retention analysis

This is where the investment pays off. Amplitude's Retention Analysis chart compares how each cohort retains over time. If tour completers retain at 2x the rate of the unseen group, the tour is driving real value. If there's no gap, the tour might be teaching the wrong things.

In Amplitude:

  1. Go to Analytics > Create > Retention Analysis
  2. Set the start event to Signup (or whatever your registration event is)
  3. Set the return event to any meaningful engagement action (feature used, project created, API call made)
  4. Under Performed by, select each of your three cohorts

Amplitude shows Day-1 through Day-30 retention curves for each group, overlaid on the same chart. The gap between the curves is your tour's contribution to retention.

Calm's team discovered through exactly this analysis that users who set a daily reminder during onboarding retained at 3x the rate of those who didn't. They made the reminder step mandatory. Retention went up measurably across the entire user base. That's the kind of decision this data enables.

Amplitude calls this the 7% retention rule: "The most successful products don't leave early activation to chance. They design specific interventions that guide new users to value quickly." Tour Kit is how you build those interventions. Amplitude is how you measure them.

Common issues and troubleshooting

We tested this integration in a Next.js 15 app with a 5-step onboarding tour and measured three problems that come up regularly. Each one was subtle enough to miss during development but obvious once events started flowing into Amplitude.

"Events appear in Amplitude but user properties are missing"

The Identify call needs to happen after amplitude.init() resolves. If your tour auto-starts before init completes, the identify gets dropped silently. Guard the callback:

onTourComplete={(tourId) => {
  // amplitude.init() is async, so check it resolved
  if (!amplitude.getSessionId()) {
    console.warn('Amplitude not initialized, skipping identify')
    return
  }

  const identify = new amplitude.Identify()
  identify.set(`tour_completed_${tourId}`, true)
  amplitude.identify(identify)
}}

"Amplitude adds too much to my bundle"

As of April 2026, @amplitude/analytics-browser ships at roughly 36KB gzipped. That's lighter than PostHog's 52KB but still noticeable. If bundle size matters (and for Tour Kit users it usually does), dynamic import the SDK:

// src/lib/amplitude.ts
let amplitudeInstance: typeof import('@amplitude/analytics-browser') | null = null

export async function getAmplitude() {
  if (!amplitudeInstance) {
    amplitudeInstance = await import('@amplitude/analytics-browser')
    amplitudeInstance.init(process.env.NEXT_PUBLIC_AMPLITUDE_API_KEY!)
  }
  return amplitudeInstance
}

This keeps Amplitude out of your critical path. Tour Kit's core is under 8KB gzipped, so the analytics SDK is the heavier dependency by a factor of 4.5x.

"Tour events fire twice in development"

React 18+ StrictMode double-invokes effects, which can trigger duplicate onTourStart calls. This doesn't happen in production. If it creates noisy data during development, deduplicate with a ref:

const firedEvents = useRef(new Set<string>())

onTourStart={(tourId) => {
  const key = `start-${tourId}-${Date.now()}`
  if (firedEvents.current.has(key)) return
  firedEvents.current.add(key)
  amplitude.track('tour_started', { tour_id: tourId, total_steps: 0 })
}}

Next steps

You've got tour-level funnels, behavioral cohorts, and retention curves. A few directions to take this:

  • Use Amplitude's Experiment feature to A/B test different tour step content against the same retention metric
  • Wire @tourkit/hints callbacks to track hotspot engagement alongside tour events
  • Build a custom Amplitude dashboard that combines tour metrics with your product's activation funnel
  • Feed cohort data back into Tour Kit by conditionally showing tours based on Amplitude user properties

The Tour Kit docs cover the full callback API. Amplitude's onboarding measurement guide goes deeper on the four-step framework referenced throughout this tutorial.

FAQ

Does Amplitude have a built-in product tour feature?

Amplitude doesn't include product tours. It's a pure analytics platform. You need a separate tour library like Tour Kit, then wire its events to Amplitude via the track() API. The integration adds about 70 lines of TypeScript.

How does Amplitude compare to Mixpanel and PostHog for onboarding analytics?

Amplitude excels at behavioral cohorts and retention analysis, making it strongest for measuring whether onboarding drives retention. Mixpanel offers 20M free events/month vs. Amplitude's 50,000 MTUs. PostHog adds session replay and feature flags. All three work with Tour Kit's callback API.

What retention rate should I expect from a well-instrumented onboarding tour?

Amplitude's research finds that products hitting a 7% Day-30 retention threshold are on a viable growth trajectory. Calm saw 3x higher retention from one key onboarding step. A 2-3x gap between tour completers and non-completers is a strong signal that your tour is worth keeping.

Does Tour Kit work with Amplitude's new Agentic AI features?

Tour Kit's typed event callbacks produce consistent, well-structured event data that Amplitude's AI agents can process automatically. As of April 2026, AI agents drive 25% of Amplitude's platform queries. Clean tour event schemas let these agents detect patterns like "users who skip step 3 churn at 2x the rate" without manual analysis.

Does adding Amplitude tracking affect Tour Kit's accessibility?

Amplitude's track() and identify() calls are asynchronous JavaScript that don't modify the DOM. Tour Kit maintains full WCAG 2.1 AA compliance regardless of analytics callbacks. Focus management, ARIA attributes, and keyboard navigation are unaffected by the analytics layer.


Ready to try userTourKit?

$ pnpm add @tour-kit/react