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 Custom Activities 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)
See the reference for full parameter documentation including time options, callbacks, and runId.

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 completion metrics:
  1. Flushes a final heartbeat (TimeSpentEvent)
  2. Submits the completion (ActivityCompletedEvent)
await activity.end({
	xpEarned: number,
	totalQuestions: number?,
	correctQuestions: number?,
	masteredUnits: number?,
})
See the reference for full completion data documentation.

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>
}

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

Stateful activities

Multi-session activities with server-side completion

Reference

Parameters, properties, methods, and callbacks