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:

  1. Implement Level 3 (Caliper Events)—sessions are created and extended through Caliper events
  2. Request OAuth credentials with the appropriate scopes (see Authentication Guide):
    • events.write — send SessionEvents, heartbeat, session metadata
    • events.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:

  1. When an event arrives with session: "urn:tag:auto-attach", the platform looks for an active session for the same user and app
  2. If the user has an active session whose endedAtTime is within 1 hour of the event's eventTime, the event attaches to that session and extends endedAtTime
  3. 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: true in its extensions (see Session Extensions)
  • The session must be active (loggedOut is false)

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

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.