Session Management
Understand how the platform tracks learning sessions and choose the right integration pattern for your app.
Note: This guide covers common integration patterns. See the API Reference for complete endpoint documentation, all parameters, and example requests.
What Sessions Provide
A session represents a student's continuous learning activity within a single app. Sessions are the foundational unit that ties together Caliper events, coaching insights, proctoring, and analytics. By managing sessions correctly, you enable:
- Accurate time-on-task tracking per student per app
- Session-level coaching insights (idle time, distractions, off-task behavior)
- Proctoring and recording attribution
- Cross-session trend analysis and progress dashboards
Without proper session management, the platform can still process individual events, but cannot group them into meaningful activity windows or generate session-level insights.
Prerequisites
Before implementing session management:
- Implement Level 3 (Caliper Events)—sessions are created and extended through Caliper events
- Request OAuth credentials with the appropriate scopes (see Authentication Guide):
events.write— send SessionEvents, heartbeat, session metadataevents.readonly— read session lists, session insights, session events
Session Lifecycle
Every session follows the same lifecycle:
| State | Description |
|---|---|
| Created | Session established with a startedAtTime |
| Active | endedAtTime extends as new events arrive; loggedOut is false |
| Completed | Session closed—loggedOut is true after an explicit LoggedOut or TimedOut event |
Once a session is completed, its endedAtTime is finalized and new events will not extend it.
Session Attribution
When sending Caliper events, you must decide how to attribute them to sessions. The platform supports two patterns:
Explicit Sessions
Use Caliper SessionEvent to create and close sessions when your app can detect session start and end (login/logout, app open/close, etc.).
Step 1: Create a session with LoggedIn
Send a SessionEvent with LoggedIn action when the student starts a session. Include a session object with a unique id and startedAtTime:
const sessionId = `urn:uuid:${crypto.randomUUID()}`;
const startTime = new Date().toISOString();
await fetch('https://platform.timeback.com/events/1.0/', {
method: 'POST',
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
'@context': 'http://purl.imsglobal.org/ctx/caliper/v1p2',
id: `urn:uuid:${crypto.randomUUID()}`,
type: 'SessionEvent',
profile: 'SessionProfile',
actor: {
id: `urn:uuid:${user.platformId}`,
type: 'Person',
},
action: 'LoggedIn',
object: {
id: `urn:uuid:${process.env.TIMEBACK_ED_APP_ID}`,
type: 'SoftwareApplication',
},
eventTime: startTime,
edApp: `urn:uuid:${process.env.TIMEBACK_ED_APP_ID}`,
session: {
id: sessionId,
type: 'Session',
startedAtTime: startTime,
extensions: {
requiresHeartbeat: true,
processInsights: true,
},
},
}),
});
Include only the extensions your integration requires (see Session Extensions).
Step 2: Reference the session in subsequent events
Use the same sessionId in all events during this session:
await fetch('https://platform.timeback.com/events/1.0/', {
method: 'POST',
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
'@context': 'http://purl.imsglobal.org/ctx/caliper/v1p2',
id: `urn:uuid:${crypto.randomUUID()}`,
type: 'AssessmentItemEvent',
actor: `urn:uuid:${user.platformId}`,
action: 'Started',
object: {
id: `urn:uuid:${question.id}`,
type: 'AssessmentItem',
name: question.text,
},
eventTime: new Date().toISOString(),
edApp: `urn:uuid:${process.env.TIMEBACK_ED_APP_ID}`,
session: sessionId,
}),
});
Step 3: Close the session with LoggedOut
When the student logs out or leaves your app:
await fetch('https://platform.timeback.com/events/1.0/', {
method: 'POST',
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
'@context': 'http://purl.imsglobal.org/ctx/caliper/v1p2',
id: `urn:uuid:${crypto.randomUUID()}`,
type: 'SessionEvent',
profile: 'SessionProfile',
actor: {
id: `urn:uuid:${user.platformId}`,
type: 'Person',
},
action: 'LoggedOut',
object: `urn:uuid:${process.env.TIMEBACK_ED_APP_ID}`,
eventTime: new Date().toISOString(),
edApp: `urn:uuid:${process.env.TIMEBACK_ED_APP_ID}`,
session: {
id: sessionId,
type: 'Session',
endedAtTime: new Date().toISOString(),
},
}),
});
You can also send a TimedOut action instead of LoggedOut to indicate an inactivity-based session close.
Auto-Attach (Fallback)
If your app cannot detect when sessions start and end (e.g., no login/logout flow, no app lifecycle hooks), you can use session: "urn:tag:auto-attach" and the platform will attribute events to sessions automatically.
How it works:
- When an event arrives with
session: "urn:tag:auto-attach", the platform looks for an active session for the same user and app - If the user has an active session whose
endedAtTimeis within 1 hour of the event'seventTime, the event attaches to that session and extendsendedAtTime - If no matching session is found within the 1-hour window, the event is processed without a session
This is useful when another system (e.g., a desktop container or monitoring agent) manages session boundaries separately, and your app only needs to send learning events without worrying about session lifecycle.
await fetch('https://platform.timeback.com/events/1.0/', {
method: 'POST',
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
'@context': 'http://purl.imsglobal.org/ctx/caliper/v1p2',
id: `urn:uuid:${crypto.randomUUID()}`,
type: 'AssessmentItemEvent',
actor: `urn:uuid:${user.platformId}`,
action: 'Started',
object: {
id: `urn:uuid:${question.id}`,
type: 'AssessmentItem',
name: question.text,
},
eventTime: new Date().toISOString(),
edApp: `urn:uuid:${process.env.TIMEBACK_ED_APP_ID}`,
session: 'urn:tag:auto-attach',
}),
});
Session Heartbeat
If your app cannot guarantee a graceful session close (e.g., the user closes the browser tab, the app crashes, or there is no reliable logout hook), use the heartbeat endpoint to periodically extend the session. This way, the platform knows the session is still active, and the endedAtTime stays accurate to the last known activity — even without an explicit LoggedOut event.
Heartbeat is also useful for long-running sessions where Caliper events are infrequent (e.g., video watching, reading) and you want to prevent the session from appearing idle.
POST /events/1.0/sessions/{sessionId}/heartbeat
Prerequisites:
- The session must be created with
requiresHeartbeat: truein its extensions (see Session Extensions) - The session must be active (
loggedOutisfalse)
Request:
await fetch(`https://platform.timeback.com/events/1.0/sessions/${sessionId}/heartbeat`, {
method: 'POST',
headers: {
Authorization: `Bearer ${accessToken}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
eventTime: new Date().toISOString(),
}),
});
Behavior: If the eventTime is after the session's current endedAtTime, the session's endedAtTime is extended to the new time. Otherwise, the heartbeat is silently ignored.
Recommended interval: Send heartbeats every 30–60 seconds during passive activities.
Integration Decision Guide
| Scenario | Session Strategy | Heartbeat? |
|---|---|---|
| App with login/logout or open/close detection | Explicit SessionEvent (LoggedIn/LoggedOut) | Optional |
| App can start sessions but cannot guarantee graceful close | Explicit SessionEvent (LoggedIn) + Heartbeat | Yes — endedAtTime stays accurate to last heartbeat |
| App with long passive activities (video, reading) | Explicit SessionEvent + Heartbeat | Yes — prevents the session from appearing idle |
| Proctoring or monitoring system | Explicit SessionEvent + Metadata upsert | Depends on use case |
| App cannot detect session start/end | Auto-attach (urn:tag:auto-attach) |
No |
| Another system manages sessions, app only sends events | Auto-attach | No — the external system creates sessions, your events attach to them |
Common Pitfalls
Mixing auto-attach and explicit sessions
// ❌ Bad: Creating a session with SessionEvent but then using auto-attach
session: 'urn:tag:auto-attach'; // This won't attach to your explicit session
// ✅ Good: Use the session ID from your LoggedIn event
session: sessionId; // Reference the explicit session
Sending heartbeat without requiresHeartbeat
// ❌ Bad: Session created without requiresHeartbeat extension
// Heartbeat call will return 400 "Session does not require heartbeat"
// ✅ Good: Set requiresHeartbeat when creating the session
session: {
id: sessionId,
type: 'Session',
startedAtTime: startTime,
extensions: {
requiresHeartbeat: true,
},
}
Sending events after session is closed
// ❌ Bad: Sending events referencing a logged-out session
// The session's endedAtTime will NOT be extended
// ✅ Good: Stop sending events with that session ID after LoggedOut
// Or use auto-attach to let the platform handle session boundaries
Related Docs
Level 3: Caliper Events
Learn how to send Caliper events, which are attributed to sessions.
Level 4: Insights API
Access coaching insights generated from session activity, including session-level analytics.
Caliper Events Reference
Complete event schemas including SessionEvent payloads for LoggedIn, LoggedOut, and TimedOut.
Authentication
Obtain OAuth client credentials and access tokens required for session APIs.
