Skip to main content
Activity tracking lets you record learning sessions, measure time spent, and report completion metrics to the Timeback platform. The SDK supports two activity models. Both use the same continuous time tracking under the hood; the difference is in how completion is handled.

Choose your model

Not sure which to choose? See Activity Models for a conceptual comparison.

How time tracking works

Both models use continuous heartbeat-based time tracking. Rather than accumulating time and reporting it once at the end, the SDK sends periodic updates throughout the session. Each update reconciles a time window, reporting how many milliseconds were active and how many were inactive. Heartbeats begin automatically when you call activity.start() and repeat at a regular interval (default 15s), and are sent as TimeSpentEvents.

Why this matters

  • Maximum 15s of data loss if a student closes the tab unexpectedly (vs. losing the entire session)
  • Accurate per-window timestamps: each event represents the actual engagement window
  • Visibility-aware: time is only counted when the tab is in the foreground

Abandoned tab detection

If a tab stays hidden for an extended period (default 10 minutes), the SDK stops heartbeats entirely. When the student returns, tracking resets from a clean slate. This prevents counting hours of abandoned-tab time.

Event correlation with runId

Every activity is identified by a runId, a unique identifier generated at activity.start(). All heartbeats, as well as the final completion event, share the same runId, which allows downstream systems to correlate time-spent data with completion results. For stateful activities, the runId is persisted and reused when the student resumes, so heartbeats from different sessions are still correlated with the same completion event.

SDK methods

The SDK separates time tracking from completion into two distinct operations.

Client-side (TypeScript)

activity.start()
Begins periodic heartbeats, emitting TimeSpentEvents throughout the session. Lightweight — analytics only.
activity.end()
Flushes remaining time data. No completion event is recorded.
activity.end(metrics)
Flushes time data and sends an ActivityCompletedEvent. Runs the full pipeline including gradebook and XP.

Server-side (TypeScript or Python)

timeback.activity.record()
Sends an ActivityCompletedEvent from the backend. Runs the full pipeline including gradebook and XP. See the server adapter docs for setup.

Pause and resume

Both models support pause and resume:
activity.pause() // Flushes accumulated time, stops heartbeats
activity.resume() // Resets tracking window, restarts heartbeats
Paused time is not counted as active. The SDK distinguishes active and inactive milliseconds in every heartbeat. See single-session for framework-specific lifecycle examples.

Activity properties

Access activity state at any time during the session:
startedAt
Date
When the activity was started
isPaused
boolean
Whether the activity is currently paused
runId
string
Unique identifier correlating heartbeats and completion events for this run
totalActiveMs
number
Cumulative active time across all flushed heartbeat windows plus the current in-progress window
elapsedMs
number
Active time for the current heartbeat window only

Configuration

Activity ingestion requires a Caliper sensor URL in timeback.config.json:
timeback.config.json
{
	"$schema": "https://timeback.dev/schema.json",
	"name": "My App",
	"sensor": "https://my-app.example.com/sensors/default",
	"courses": [
		{
			"subject": "Math",
			"grade": 3,
			"courseCode": "MATH-3",
			"sensor": "https://my-app.example.com/sensors/math",
			"overrides": {
				"staging": {
					"sensor": "https://staging.my-app.example.com/sensors/math"
				}
			}
		}
	]
}

Sensor precedence

The SDK resolves sensors in this order (highest to lowest):
  1. Per-course env override: course.overrides[env].sensor
  2. Per-course base override: course.sensor
  3. Top-level default: config.sensor

Next Steps