Receive watch progress, scores, variables, and recorded interactions for a viewing session.
Receive a view snapshot with watch progress, question scores, viewer details, and recorded interactions. The top-level id identifies the view. viewer_id identifies the viewer.
Use a view webhook to update a learner's course record or store video engagement in your application. See Webhooks for setup, key validation, and delivery handling.
Example POST body
Alex watched the full 120-second video, answered one question, and clicked the safety guide button. This example includes all top-level fields and both complete interaction records. The data is fictional.
{
"id": "60000000-0000-4000-8000-000000000001",
"owner_id": "20000000-0000-4000-8000-000000000001",
"video_id": "40000000-0000-4000-8000-000000000001",
"video_token": "exampleVideo",
"video_title": "Workplace safety introduction",
"started": "2026-09-08T14:00:00.000Z",
"finished": "2026-09-08T14:02:00.000Z",
"elapsed": 120,
"watched": 120,
"percentage": 100,
"last_point": 119,
"furthest_point": 119,
"viewer_id": "50000000-0000-4000-8000-000000000001",
"viewer_name": "Alex Morgan",
"viewer_email": "[email protected]",
"viewer_phone": null,
"viewer_custom_id": "employee-1042",
"interaction_count": 2,
"has_exact_location": false,
"latitude": "28.0836",
"longitude": "-80.6081",
"postal_number": null,
"postal_code": null,
"postal_street": null,
"postal_county": null,
"postal_continent": null,
"postal_label": null,
"postal_map_url": null,
"postal_confidence": null,
"city": "Melbourne",
"region": "Florida",
"country": "United States",
"flag": "🇺🇸",
"url": "https://share.mindstamp.com/w/exampleVideo",
"browser": "Chrome",
"screen_width": "1440",
"screen_height": "900",
"participation": 100,
"correctness": 100,
"plink_id": null,
"tag1": null,
"tag2": null,
"tag3": null,
"tag4": null,
"tag5": null,
"video_duration": 120,
"group_id": "30000000-0000-4000-8000-000000000001",
"group_name": "New employee training",
"video_owner_id": "20000000-0000-4000-8000-000000000001",
"export_count": 0,
"prompt_count": 1,
"response_count": 1,
"correct_count": 1,
"wrong_count": 0,
"device_brand": null,
"device_model": null,
"device_os": "macOS",
"series_id": null,
"variables": {
"department": "Operations",
"course_id": "safety-intro"
},
"created_at": "2026-09-08T14:00:00.000Z",
"updated_at": "2026-09-08T14:02:00.000Z",
"interactions": [
{
"id": "70000000-0000-4000-8000-000000000002",
"video_title": "Workplace safety introduction",
"type_of": "click",
"value": "Open safety guide",
"time": 90,
"finish": null,
"parent_id": "80000000-0000-4000-8000-000000000002",
"parent_type": "button",
"parent_value": "Open safety guide",
"click_action": "link",
"click_value": "https://example.com/safety-guide",
"viewer_name": "Alex Morgan",
"viewer_email": "[email protected]",
"viewer_phone": null,
"viewer_custom_id": "employee-1042",
"style": null,
"answers": null,
"correct_answer": null,
"correct": null,
"view_id": "60000000-0000-4000-8000-000000000001",
"video_id": "40000000-0000-4000-8000-000000000001",
"url": "https://share.mindstamp.com/w/exampleVideo",
"viewer_id": "50000000-0000-4000-8000-000000000001",
"created_at": "2026-09-08T14:01:32.000Z",
"updated_at": "2026-09-08T14:01:32.000Z",
"group_id": "30000000-0000-4000-8000-000000000001",
"group_name": "New employee training",
"series_id": null,
"series_name": null,
"variables": {
"department": "Operations",
"course_id": "safety-intro"
},
"export_count": 0,
"video_owner_id": "20000000-0000-4000-8000-000000000001",
"response_time": null,
"internal_label": null
},
{
"id": "70000000-0000-4000-8000-000000000001",
"video_title": "Workplace safety introduction",
"type_of": "reply",
"value": "Report the hazard",
"time": 30,
"finish": null,
"parent_id": "80000000-0000-4000-8000-000000000001",
"parent_type": "question",
"parent_value": "What should you do if you find a hazard?",
"click_action": null,
"click_value": null,
"viewer_name": "Alex Morgan",
"viewer_email": "[email protected]",
"viewer_phone": null,
"viewer_custom_id": "employee-1042",
"style": "multiple",
"answers": null,
"correct_answer": "Report the hazard",
"correct": true,
"view_id": "60000000-0000-4000-8000-000000000001",
"video_id": "40000000-0000-4000-8000-000000000001",
"url": "https://share.mindstamp.com/w/exampleVideo",
"viewer_id": "50000000-0000-4000-8000-000000000001",
"created_at": "2026-09-08T14:00:34.000Z",
"updated_at": "2026-09-08T14:00:34.000Z",
"group_id": "30000000-0000-4000-8000-000000000001",
"group_name": "New employee training",
"series_id": null,
"series_name": null,
"variables": {
"department": "Operations",
"course_id": "safety-intro"
},
"export_count": 0,
"video_owner_id": "20000000-0000-4000-8000-000000000001",
"response_time": null,
"internal_label": null
}
],
"responses": {
"What should you do if you find a hazard?": "Report the hazard"
}
}
Use this payload
Store the view by id. Join viewer_id to the id from a lead webhook. If your application supplied viewer_custom_id, use it to find the learner. Use video_id or variables.course_id to select the course.
Your application sets the completion rule. In this example, a rule that requires full viewing and all graded answers correct would pass. Check for null scores before you apply a rule.
const learnerProgress = {
mindstampViewId: body.id,
mindstampViewerId: body.viewer_id,
learnerId: body.viewer_custom_id,
courseId: body.variables?.course_id ?? body.video_id,
watchedPercent: body.percentage,
questionScore: body.correctness,
updatedAt: body.updated_at
};
Read the responses
Use responses for a prompt-to-answer summary. Use interactions for record IDs, question IDs, timestamps, and correctness.
The summary uses prompt text as the key. If a viewer answers the same prompt more than once, or two questions have the same text, one entry replaces another. The selected entry depends on record iteration order. Do not treat the summary as a reliable latest-answer record. Use interactions and sort by created_at when your application needs an ordered answer history.
Field reference
Times ending in Z use UTC. Durations and video positions use seconds. Location and device fields can be null.
| Field | JSON type | Description |
|---|---|---|
id | string | Mindstamp record ID. See the page introduction for its record type. |
owner_id | string or null | Owner ID stored on this record. |
video_id | string | Video ID. |
video_token | string | Video token used in share and embed URLs. |
video_title | string or null | Video title. |
started | string | Start of the view, as an ISO 8601 timestamp. |
finished | string | Last recorded activity time for this view snapshot. This does not prove that the viewer reached the end. |
elapsed | integer | Recorded session time in seconds. The current calculation uses the smaller of wall time and five times the tracked seconds. |
watched | integer | Count of distinct video seconds watched. Rewatching a second does not add another second to this count. |
percentage | integer or null | Distinct seconds watched divided by video duration, as a rounded percentage. Stored values are capped at 100. |
last_point | integer or null | Recorded resume position in video seconds. Without a resume position, this falls back to the last tracked second. |
furthest_point | integer or null | Highest video second tracked during this view. |
viewer_id | string or null | Viewer ID. Matches id in a lead webhook. Some older interactions use a user ID as a fallback. |
viewer_name | string or null | Viewer name. An unidentified viewer can have an anonymous display name. |
viewer_email | string or null | Viewer email, when available. |
viewer_phone | string or null | Viewer phone, when available. |
viewer_custom_id | string or null | Your identifier for the viewer, when supplied. |
interaction_count | integer or null | Number of interaction records in the view. |
has_exact_location | boolean or null | Whether exact-location enrichment succeeded. Coordinates can exist without this flag. |
latitude | string or null | Recorded latitude. Can come from approximate location data. |
longitude | string or null | Recorded longitude. Can come from approximate location data. |
postal_number | string or null | Street number from location enrichment. |
postal_code | string or null | Postal code from location enrichment. |
postal_street | string or null | Street name from location enrichment. |
postal_county | string or null | County from location enrichment. |
postal_continent | string or null | Continent from location enrichment. |
postal_label | string or null | Formatted location label. |
postal_map_url | string or null | Map URL from location enrichment. |
postal_confidence | string or null | Location provider confidence value, stored as text. |
city | string or null | Recorded city. Location data can be approximate or unavailable. |
region | string or null | Recorded state or region. |
country | string or null | Recorded country name or code, depending on the location source. |
flag | string or null | Country flag emoji, when available. |
url | string or null | Recorded playback URL. The host and path depend on how the viewer opened the video. |
browser | string or null | Recorded browser description. |
screen_width | string or null | Recorded screen width in pixels, stored as text. |
screen_height | string or null | Recorded screen height in pixels, stored as text. |
participation | integer or null | Response count divided by question prompt count, times 100, rounded. Null with no prompts. Repeated responses can make this exceed 100. |
correctness | integer or null | Correct count divided by correct count plus wrong count, times 100, rounded. With no graded records, this can be 0 or null. |
plink_id | string or null | Personalized link ID. |
tag1 | string or null | Stored value of this legacy tag field. Check variables for custom data from the current player. |
tag2 | string or null | Stored value of this legacy tag field. Check variables for custom data from the current player. |
tag3 | string or null | Stored value of this legacy tag field. Check variables for custom data from the current player. |
tag4 | string or null | Stored value of this legacy tag field. Check variables for custom data from the current player. |
tag5 | string or null | Stored value of this legacy tag field. Check variables for custom data from the current player. |
video_duration | integer or null | Video duration stored on the view, in seconds. |
group_id | string or null | Group ID, when available. |
group_name | string or null | Group name, when available. |
video_owner_id | string or null | ID of the video owner. |
export_count | integer | View export counter before this request. The sender increments it after an HTTP response, including a failed status. This is not a unique delivery ID. |
prompt_count | integer or null | Number of question prompts in the video. |
response_count | integer or null | Number of interaction records linked to those question prompts. |
correct_count | integer or null | Number of interaction records with correct set to true. |
wrong_count | integer or null | Number of interaction records with correct set to false. |
device_brand | string or null | Device brand, when detected. |
device_model | string or null | Device model, when detected. |
device_os | string or null | Operating system, when detected. |
series_id | string or null | Playlist ID. The field name uses the older term series. |
variables | object or null | Stored variables. Values can have different JSON types. Privacy settings can remove values. The lowercase setting can change key names. |
created_at | string | Same value as started in this payload. |
updated_at | string | Same value as finished in this payload. |
interactions | array of objects | Interaction records for the view. Each object uses the Interaction Webhooks schema. Empty array when none exist. |
responses | object | Question prompt text mapped to a recorded response value. Empty object with no question responses. See the duplicate-prompt note below. |
Update an existing view
Playback can resume after a view webhook was sent. A later request can have the same view id and updated progress. Update the existing record. Do not count the request as a new view. Use updated_at to reject an older snapshot.
The webhook does not include an is_complete field. finished is the last recorded activity time. Use your own rules for course completion.