> ## Documentation Index
> Fetch the complete documentation index at: https://docs.timeback.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Events

> Caliper event types emitted by the Timeback SDK

The Timeback SDK emits Caliper-based events to report learning activity. This page describes the SDK's TimebackProfile output before any destination-specific transformation. Choose your assigned endpoint and wire format in [Event Contracts](/beta/build-on-timeback/reference/event-contracts). The Partner Events API and Platform native schemas have separate event references.

<Tip>
  See [Activity Models](/beta/about-timeback/concepts/activity-models) for when each event is
  emitted.
</Tip>

## ActivityCompletedEvent

Emitted once per activity run when the student completes the activity. Carries completion metrics like questions answered, XP earned, and mastery.

```json theme={null}
{
	"@context": "http://purl.imsglobal.org/ctx/caliper/v1p2",
	"id": "urn:uuid:c51570e4-f8ed-4c18-bb3a-dfe51b2cc594",
	"type": "ActivityEvent",
	"action": "Completed",
	"profile": "TimebackProfile",
	"eventTime": "2026-01-27T10:03:00.000Z",
	"actor": {
		"id": "https://api.example.com/ims/oneroster/rostering/v1p2/users/student-123",
		"type": "TimebackUser",
		"email": "student@example.com"
	},
	"object": {
		"id": "https://myapp.example.com/activities/Math/g3/lesson-1",
		"type": "TimebackActivityContext",
		"subject": "Math",
		"app": { "name": "My App" },
		"course": { "id": "https://api.example.com/ims/oneroster/rostering/v1p2/courses/course-example", "name": "Math Grade 3" },
		"activity": { "name": "Fractions lesson" },
		"process": true
	},
	"generated": {
		"id": "https://api.example.com/ims/metrics/collections/activity/abc-123",
		"type": "TimebackActivityMetricsCollection",
		"items": [
			{ "type": "xpEarned", "value": 80 },
			{ "type": "totalQuestions", "value": 10 },
			{ "type": "correctQuestions", "value": 8 },
			{ "type": "masteredUnits", "value": 1 }
		],
		"extensions": {
			"pctCompleteApp": 67
		}
	},
	"extensions": {
		"runId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
		"courseId": "course-example"
	}
}
```

### Activity metrics

The `generated.items` array contains completion metrics. Each item has a `type` (string) and a `value` (number).

<ResponseField name="xpEarned" type="number" required>
  XP supplied by your app for this activity. Follow the XP policy agreed for your integration.
  The SDK does not infer XP from elapsed time or question counts.
</ResponseField>

<ResponseField name="totalQuestions" type="number">
  Total questions in the activity. Optional, but required if `correctQuestions` is provided.
</ResponseField>

<ResponseField name="correctQuestions" type="number">
  Questions answered correctly. Optional, but required if `totalQuestions` is provided.
</ResponseField>

<ResponseField name="masteredUnits" type="number">
  Number of **new** units (lessons) the student mastered during this activity. This is an
  incremental count, not a cumulative total. When this value is positive, the SDK server can
  combine it with historical enrollment facts and a configured positive `totalLessons` to
  compute `pctCompleteApp`, unless you supplied `pctComplete` explicitly. If enrollment or
  historical facts cannot be resolved, computation can be skipped. Optional. See [Course
  progress](/beta/build-on-timeback/sdk/activity-tracking/course-progress) for full details.
</ResponseField>

<ResponseField name="pctCompleteApp" type="number">
  Course completion percentage (0--100). Either passed directly via `pctComplete` in the activity
  payload, or auto-computed by the server from `masteredUnits` and
  [`totalLessons`](/beta/build-on-timeback/reference/configuration#course-progress-config). Sent
  via `generated.extensions.pctCompleteApp`. See [Course
  progress](/beta/build-on-timeback/sdk/activity-tracking/course-progress).
</ResponseField>

## TimeSpentEvent

The SDK sends periodic heartbeats every 15 seconds by default and flushes measured time when an activity ends. The server uses that measured time to construct TimeSpentEvents. Each event reports a bounded time window; a heartbeat is not itself proof that a Caliper event or analytics fact was persisted.

```json theme={null}
{
	"@context": "http://purl.imsglobal.org/ctx/caliper/v1p2",
	"id": "urn:uuid:a1b2c3d4-e5f6-7890-abcd-ef1234567890",
	"type": "TimeSpentEvent",
	"action": "SpentTime",
	"profile": "TimebackProfile",
	"eventTime": "2026-01-27T10:00:15.000Z",
	"actor": {
		"id": "https://api.example.com/ims/oneroster/rostering/v1p2/users/student-123",
		"type": "TimebackUser",
		"email": "student@example.com"
	},
	"object": {
		"id": "https://myapp.example.com/activities/Math/g3/lesson-1",
		"type": "TimebackActivityContext",
		"subject": "Math",
		"app": { "name": "My App" },
		"course": { "id": "https://api.example.com/ims/oneroster/rostering/v1p2/courses/course-example", "name": "Math Grade 3" },
		"activity": { "name": "Fractions lesson" },
		"process": true
	},
	"generated": {
		"id": "https://api.example.com/ims/metrics/collections/time-spent/def-456",
		"type": "TimebackTimeSpentMetricsCollection",
		"items": [
			{
				"type": "active",
				"value": 15,
				"startDate": "2026-01-27T10:00:00.000Z",
				"endDate": "2026-01-27T10:00:15.000Z"
			},
			{
				"type": "inactive",
				"value": 0
			}
		]
	},
	"extensions": {
		"runId": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
		"courseId": "course-example"
	}
}
```

### Time metrics

The `generated.items` array contains time window metrics. Each item has a `type` (string), a `value` in **seconds** (max 86400), and optional fields.

#### Time categories

<ResponseField name="active" type="number">
  Seconds the student was actively engaged (tab visible, not paused).
</ResponseField>

<ResponseField name="inactive" type="number">
  Seconds the student was inactive (paused or tab hidden).
</ResponseField>

<ResponseField name="waste" type="number">
  Seconds classified as non-productive.
</ResponseField>

<ResponseField name="unknown" type="number">
  Seconds that could not be classified.
</ResponseField>

<ResponseField name="anti-pattern" type="number">
  Seconds flagged as anomalous behavior.
</ResponseField>

#### Per-item fields

<ResponseField name="subType" type="string">
  Optional sub-classification providing additional detail on the time category.
</ResponseField>

<ResponseField name="startDate" type="string">
  ISO 8601 timestamp for the start of the time window.
</ResponseField>

<ResponseField name="endDate" type="string">
  ISO 8601 timestamp for the end of the time window.
</ResponseField>

## Acceptance and delivery

An accepted Caliper request does not confirm XP, reporting, or progression completion. Preserve each event ID and the original payload across transport retries. A new event ID can represent new work to a downstream consumer. An activity's `Completed` action does not by itself assert that the whole course is complete.

## Shared structure

Both event types share these top-level fields:

<ResponseField name="@context" type="string" required>
  Caliper JSON-LD context. Always `"http://purl.imsglobal.org/ctx/caliper/v1p2"`.
</ResponseField>

<ResponseField name="id" type="string" required>
  Unique event identifier in `urn:uuid:...` format.
</ResponseField>

<ResponseField name="type" type="string" required>
  `"ActivityEvent"` for completions, `"TimeSpentEvent"` for time windows.
</ResponseField>

<ResponseField name="action" type="string" required>
  `"Completed"` for completions, `"SpentTime"` for time windows.
</ResponseField>

<ResponseField name="profile" type="string" required>
  Always `"TimebackProfile"`.
</ResponseField>

<ResponseField name="eventTime" type="string" required>
  ISO 8601 timestamp of when the event occurred.
</ResponseField>

<ResponseField name="actor" type="TimebackUser" required>
  The student who performed the action. See [Identity](/beta/build-on-timeback/sdk/identity).

  <Expandable title="properties">
    <ResponseField name="id" type="string" required>
      URL identifying the user (e.g. OneRoster user URL).
    </ResponseField>

    <ResponseField name="type" type="string" required>
      Always `"TimebackUser"`.
    </ResponseField>

    <ResponseField name="email" type="string" required>
      The student's email address.
    </ResponseField>

    <ResponseField name="name" type="string">
      Display name.
    </ResponseField>

    <ResponseField name="role" type="string">
      One of `student`, `teacher`, `admin`, or `guide`.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="object" type="TimebackActivityContext" required>
  The activity context where the event was recorded.

  <Expandable title="properties">
    <ResponseField name="id" type="string" required>
      URL identifying the activity.
    </ResponseField>

    <ResponseField name="type" type="string" required>
      Always `"TimebackActivityContext"`.
    </ResponseField>

    <ResponseField name="subject" type="string" required>
      One of `Reading`, `Language`, `Vocabulary`, `Social Studies`, `Writing`, `Handwriting`, `Science`,
      `FastMath`, `Math`, `None`, `Other`.
    </ResponseField>

    <ResponseField name="app" type="object" required>
      The application. Must include `name`. Optional `id` (URL) and `extensions`.
    </ResponseField>

    <ResponseField name="course" type="object">
      The course context. Must include `name`. Optional `id` (URL) and `extensions`.
    </ResponseField>

    <ResponseField name="activity" type="object">
      The specific activity. Must include `name`. Optional `id` (URL) and `extensions`.
    </ResponseField>

    <ResponseField name="process" type="boolean">
      Controls TimebackProfile activity processing. The SDK defaults this to true.
      When sending direct activity events, include it when your integration expects the activity consumer to process them.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseField name="generated" type="object" required>
  Metrics collection. Shape depends on event type:

  <Tabs>
    <Tab title="ActivityCompletedEvent">
      Type: `TimebackActivityMetricsCollection`

      <Expandable title="properties">
        <ResponseField name="id" type="string" required>
          URL identifying this collection.
        </ResponseField>

        <ResponseField name="type" type="string" required>
          Always `"TimebackActivityMetricsCollection"`.
        </ResponseField>

        <ResponseField name="items" type="TimebackActivityMetric[]" required>
          Array of activity metrics. Each item has `type` (`xpEarned`, `totalQuestions`,
          `correctQuestions`, `masteredUnits`) and `value` (number). See [Activity
          metrics](#activity-metrics).
        </ResponseField>

        <ResponseField name="extensions" type="object">
          Additional attributes, e.g. `pctCompleteApp` (course completion percentage,
          0–100).
        </ResponseField>
      </Expandable>
    </Tab>

    <Tab title="TimeSpentEvent">
      Type: `TimebackTimeSpentMetricsCollection`

      <Expandable title="properties">
        <ResponseField name="id" type="string" required>
          URL identifying this collection.
        </ResponseField>

        <ResponseField name="type" type="string" required>
          Always `"TimebackTimeSpentMetricsCollection"`.
        </ResponseField>

        <ResponseField name="items" type="TimeSpentMetric[]" required>
          Array of time metrics. Each item has `type` (`active`, `inactive`, `waste`,
          `unknown`, `anti-pattern`), `value` (seconds), and optional
          `startDate`/`endDate`. See [Time metrics](#time-metrics).
        </ResponseField>

        <ResponseField name="extensions" type="object">
          Additional attributes not defined by the model.
        </ResponseField>
      </Expandable>
    </Tab>
  </Tabs>
</ResponseField>

<ResponseField name="edApp" type="string">
  Application context on the event. Its accepted representation depends on the target contract.
</ResponseField>

<ResponseField name="extensions" type="object">
  Custom attributes including `runId` and `courseId` for event correlation.
</ResponseField>

The legacy event schema also defines the following optional event-level fields. They are not envelope metadata, and compatibility parsers on another destination may reject them:

<ResponseField name="target" type="string">
  Entity representing a particular segment or location within the object.
</ResponseField>

<ResponseField name="referrer" type="string">
  Entity representing the referring context.
</ResponseField>

<ResponseField name="group" type="string | object">
  An Organization representing the group context. Can be a URL or an Organization entity object.
</ResponseField>

<ResponseField name="membership" type="string">
  The relationship between the actor and the group in terms of roles and status.
</ResponseField>

<ResponseField name="session" type="string">
  The current user session.
</ResponseField>

<ResponseField name="federatedSession" type="string">
  If the event occurs within an LTI platform launch, the tool's LtiSession.
</ResponseField>

<CardGroup cols={2}>
  <Card title="Custom Activities" icon="chart-line" href="/beta/build-on-timeback/sdk/activity-tracking/intro">
    How the SDK emits these events
  </Card>

  <Card title="Caliper API" icon="plug" href="/beta/build-on-timeback/clients/caliper">
    Direct Caliper API access for custom event submission
  </Card>
</CardGroup>
