View Webhooks

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.

FieldJSON typeDescription
idstringMindstamp record ID. See the page introduction for its record type.
owner_idstring or nullOwner ID stored on this record.
video_idstringVideo ID.
video_tokenstringVideo token used in share and embed URLs.
video_titlestring or nullVideo title.
startedstringStart of the view, as an ISO 8601 timestamp.
finishedstringLast recorded activity time for this view snapshot. This does not prove that the viewer reached the end.
elapsedintegerRecorded session time in seconds. The current calculation uses the smaller of wall time and five times the tracked seconds.
watchedintegerCount of distinct video seconds watched. Rewatching a second does not add another second to this count.
percentageinteger or nullDistinct seconds watched divided by video duration, as a rounded percentage. Stored values are capped at 100.
last_pointinteger or nullRecorded resume position in video seconds. Without a resume position, this falls back to the last tracked second.
furthest_pointinteger or nullHighest video second tracked during this view.
viewer_idstring or nullViewer ID. Matches id in a lead webhook. Some older interactions use a user ID as a fallback.
viewer_namestring or nullViewer name. An unidentified viewer can have an anonymous display name.
viewer_emailstring or nullViewer email, when available.
viewer_phonestring or nullViewer phone, when available.
viewer_custom_idstring or nullYour identifier for the viewer, when supplied.
interaction_countinteger or nullNumber of interaction records in the view.
has_exact_locationboolean or nullWhether exact-location enrichment succeeded. Coordinates can exist without this flag.
latitudestring or nullRecorded latitude. Can come from approximate location data.
longitudestring or nullRecorded longitude. Can come from approximate location data.
postal_numberstring or nullStreet number from location enrichment.
postal_codestring or nullPostal code from location enrichment.
postal_streetstring or nullStreet name from location enrichment.
postal_countystring or nullCounty from location enrichment.
postal_continentstring or nullContinent from location enrichment.
postal_labelstring or nullFormatted location label.
postal_map_urlstring or nullMap URL from location enrichment.
postal_confidencestring or nullLocation provider confidence value, stored as text.
citystring or nullRecorded city. Location data can be approximate or unavailable.
regionstring or nullRecorded state or region.
countrystring or nullRecorded country name or code, depending on the location source.
flagstring or nullCountry flag emoji, when available.
urlstring or nullRecorded playback URL. The host and path depend on how the viewer opened the video.
browserstring or nullRecorded browser description.
screen_widthstring or nullRecorded screen width in pixels, stored as text.
screen_heightstring or nullRecorded screen height in pixels, stored as text.
participationinteger or nullResponse count divided by question prompt count, times 100, rounded. Null with no prompts. Repeated responses can make this exceed 100.
correctnessinteger or nullCorrect count divided by correct count plus wrong count, times 100, rounded. With no graded records, this can be 0 or null.
plink_idstring or nullPersonalized link ID.
tag1string or nullStored value of this legacy tag field. Check variables for custom data from the current player.
tag2string or nullStored value of this legacy tag field. Check variables for custom data from the current player.
tag3string or nullStored value of this legacy tag field. Check variables for custom data from the current player.
tag4string or nullStored value of this legacy tag field. Check variables for custom data from the current player.
tag5string or nullStored value of this legacy tag field. Check variables for custom data from the current player.
video_durationinteger or nullVideo duration stored on the view, in seconds.
group_idstring or nullGroup ID, when available.
group_namestring or nullGroup name, when available.
video_owner_idstring or nullID of the video owner.
export_countintegerView 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_countinteger or nullNumber of question prompts in the video.
response_countinteger or nullNumber of interaction records linked to those question prompts.
correct_countinteger or nullNumber of interaction records with correct set to true.
wrong_countinteger or nullNumber of interaction records with correct set to false.
device_brandstring or nullDevice brand, when detected.
device_modelstring or nullDevice model, when detected.
device_osstring or nullOperating system, when detected.
series_idstring or nullPlaylist ID. The field name uses the older term series.
variablesobject or nullStored variables. Values can have different JSON types. Privacy settings can remove values. The lowercase setting can change key names.
created_atstringSame value as started in this payload.
updated_atstringSame value as finished in this payload.
interactionsarray of objectsInteraction records for the view. Each object uses the Interaction Webhooks schema. Empty array when none exist.
responsesobjectQuestion 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.