Choose your model
Single-session
Completed in one sitting. Client tracks time and reports completion.
Stateful
Spans multiple sessions. Client tracks time, server records completion.
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 callactivity.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 properties
Access activity state at any time during the session:When the activity was started
Whether the activity is currently paused
Unique identifier correlating heartbeats and completion events for this run
Cumulative active time across all flushed heartbeat windows plus the current in-progress window
Active time for the current heartbeat window only
Configuration
Activity ingestion requires a Caliper sensor URL intimeback.config.json:
timeback.config.json
Sensor precedence
The SDK resolves sensors in this order (highest to lowest):- Per-course env override:
course.overrides[env].sensor - Per-course base override:
course.sensor - Top-level default:
config.sensor