Skip to main content
Single-session activities are the simplest model: a student starts and completes an activity in one browser session. The client SDK tracks time automatically and reports completion when the student finishes.
For info on shared concepts, see the Activity Tracking overview.

How it works

Once started, the SDK sends periodic TimeSpentEvents throughout the session. When the student finishes, the SDK sends one final TimeSpentEvent followed by an ActivityCompletedEvent. All events share the same runId for correlation.

Starting an activity

Every single-session activity starts the same way:
const activity = timeback.activity.start({
	id: 'intro-to-fractions',
	name: 'Introduction to Fractions',
	course: { subject: 'Math', grade: 3 }, // from timeback.config.json
})

// Student engages... (heartbeats emit automatically)

Ending an activity

How you end an activity depends on whether the student completed the activity. In both cases, accumulated time data is flushed. The difference is whether a completion result is also recorded.

With completion

When a student finishes, call activity.end() with metrics:
  1. Flushes a final heartbeat (TimeSpentEvent)
  2. Submits the completion (ActivityCompletedEvent)
await activity.end({
	xpEarned: number,
	totalQuestions: number?,
	correctQuestions: number?,
	masteredUnits: number?,
})

Without completion

Call activity.end() without arguments to flush time data only; in this case, no completion event is recorded.
  1. Flushes a final heartbeat (TimeSpentEvent)
  2. Stops the heartbeat timer
await activity.end()
There are two reasons to end without completion:
  1. Cleanup: the component unmounts before the student finishes (e.g. navigating away mid-activity). You want to flush accumulated time without recording a result.
  2. Stateful activities: the activity spans multiple sessions, so the client only tracks time per visit. Completion is recorded by the server when the student eventually finishes.

Framework integration

import { useTimeback } from '@timeback/sdk/react'
import { useEffect, useRef } from 'react'

function LessonPage({ lessonId, lessonName }) {
	const timeback = useTimeback()
	const activityRef = useRef(null)

	useEffect(() => {
		if (!timeback) return

		activityRef.current = timeback.activity.start({
			id: lessonId,
			name: lessonName,
			course: { subject: 'Math', grade: 3 },
		})

		return () => {
			// Cleanup flush: time only, do not mark completion on unmount.
			void activityRef.current?.end()
		}
	}, [timeback, lessonId, lessonName])

	return <div>Lesson content...</div>
}

Completion metrics

When ending an activity with completion, include metrics:
await activity.end({
	totalQuestions: 10,
	correctQuestions: 8,
	xpEarned: 80,
	masteredUnits: 1,
	pctComplete: 67,
})
xpEarned
number
required
XP earned for this activity. Must follow the 1 XP = 1 focused minute rule.
totalQuestions
number
Total questions in the activity. Optional, but required if correctQuestions is provided.
correctQuestions
number
Questions answered correctly. Optional, but required if totalQuestions is provided.
masteredUnits
number
Units mastered. Optional.
pctComplete
number
Completion percentage, 0–100. Optional.
totalQuestions and correctQuestions must be provided together.If you provide one, you must provide the other.
xpEarned is required for completion.Use activity.end() with no args for time-only flushes.

Time override

By default, the SDK tracks time automatically. For advanced scenarios where you need to override the timing (e.g., offline sync or batch imports), use the time parameter:
await activity.end({
	totalQuestions: 10,
	correctQuestions: 8,
	xpEarned: 80,
	time: {
		active: 1800000, // 30 minutes active
		inactive: 300000, // 5 minutes paused
	},
})

Best practices

Always start activities in lifecycle hooks to ensure proper cleanup.
Use activity.end() (no args) in cleanup functions. Only call activity.end(metrics) when the student has actually finished the activity.
Check if the timeback client exists before creating activities (it may be null if not authenticated).
Use stable, unique IDs that identify the specific lesson or content piece.

Next steps