Once you can see which learners finished a video, stalled, or skipped to the end, you usually want to act on it without doing it by hand: send the reminder, issue the certificate, flag the one to review, cut the clip. Those signals are already in your video data.
This article is a cookbook. Each recipe reads one signal and returns one action for your engine to send, and every recipe runs against the sample data below.
Here's what a nightly run produces from the sample: certificates for two learners, one completion held for review, two re-engagement nudges, three at-risk flags, and four clip requests. Every entry comes from running the recipes on the sample export.
How the data flows
FastPix supplies the signal. Your campaign or LMS engine sends the message. The recipes are the wiring between them, and there is not much of it.
Sample dataset:
https://drive.google.com/file/d/1iMRkZEryzGc0_7GdAc_eTzd4Xluz-ZCu/view?usp=sharing
https://drive.google.com/file/d/1XzMtleO6oyhFRTdxD_wHEcXF-uRdL5s1/view?usp=sharing
Download the sample dataset →fastpix-video-data-sample-export.csvandcourse-learner-roster.csv80 real (anonymized) course views plus a sample roster mapping viewers to learner names. Every recipe below runs against them. No FastPix account yet? The sample works without one - or start free with $25 credit (no credit card) and automate on your own learners.
Part 1: Get view data flowing from your player
Playback happens in your player. FastPix Video Data captures every interaction from inside it: it dedupes the beacons, stitches events into sessions, adds geo and device data, and computes quality scores, across millions of concurrent views when you need it. What you export is one clean row per view, with a viewer ID on every row.
Already collecting views with FastPix? Skip to Part 2. New here? Setup is about ten minutes.
Step 1: Add the player (or add monitoring to the one you have)
<script src="https://unpkg.com/@fastpix/[email protected]/dist/player.js"></script>
<fastpix-player playback-id="YOUR_PLAYBACK_ID" stream-type="on-demand"></fastpix-player>Own player? The monitoring SDK captures the same data from hls.js, video.js, dash.js, and native mobile players:
import fastpixMetrix from "@fastpix/data-core";
fastpixMetrix.tracker(videoElement, {
hlsjs: hls,
Hls: Hls, // both are required
data: { workspace_id: "YOUR_WORKSPACE_KEY" },
});Step 2: Pass your user ID and course context
Every view carries a stable viewer_id. Add your own IDs in custom dimensions so a workflow can address the right learner and lesson:
fastpixMetrix.tracker(videoElement, {
hlsjs: hls,
Hls: Hls,
data: {
workspace_id: "YOUR_WORKSPACE_KEY",
custom_2: `${user.id}::${user.email}`, // who to nudge / certify
custom_5: lesson.slug, // which lesson to link back to
custom_9: course.title, // which course / cohort
},
});Step 3: Export the views
Export from the Video Data dashboard (Views → Export CSV) or the export API. For a scheduled job you pull the export API on a cron, which is where these recipes run.
Part 2: Compute the signals once
Every recipe reads from the same three measurements, computed with chdb (embedded ClickHouse, pip install chdb). Reach is how far the playhead got; coverage is how much of the runtime they actually watched; opens is how many times they pressed play.
import chdb, csv
roster = {r["viewer_id"]: r["name"]
for r in csv.DictReader(open("course-learner-roster.csv"))}
vl = chdb.query("""
SELECT final_viewer_id AS viewer, video_title AS lesson, count() AS opens,
any(CAST(video_duration AS Int64)) AS dur,
max(CAST(view_max_playhead_position AS Int64)) AS reach_ms,
sum(CAST(view_total_content_playback_time AS Int64)) AS watch_ms
FROM file('fastpix-video-data-sample-export.csv', CSVWithNames)
WHERE video_duration > 0
GROUP BY viewer, lesson
""", "DataFrame")
vl["reach"] = (vl["reach_ms"] / vl["dur"]).clip(upper=1.0)
vl["coverage"] = vl["watch_ms"] / vl["dur"]
# roll up to one row per learner: totals + the furthest lesson's reach and coverage,
# joined to your roster so a recipe can address a person, not a viewer id
tot = vl.groupby("viewer", as_index=False).agg(opens=("opens", "sum"),
watch_ms=("watch_ms", "sum"))
tot["watch_min"] = tot["watch_ms"] / 60000
cand = vl.loc[vl.groupby("viewer")["reach"].idxmax(), ["viewer", "reach", "coverage"]]
learners = tot.merge(cand, on="viewer")
learners["name"] = learners["viewer"].map(roster)
learners = learners[learners["name"].notna()] # only the enrolled learners in your rosterEach recipe below loops for L in learners.itertuples(), so L.reach, L.coverage, L.watch_min, and L.opens are the signals for one learner. The conditions are written so each learner matches at most one recipe, which is what you want for a nightly run: one action per person. In your own app you would address the action by the user ID you passed in custom_2; here we use L.name from the roster.
The most-replayed sections (for the clip recipe) come from the backward-jump logic in the most replayed moments article; the full helper is in the notebook.
Start using FastPix for free - $25 credit, no credit card required. Wire these recipes to your own learners this week. Create your workspace →
Part 3: The recipes
Each recipe is a filter on the signals plus a call to your engine. The action functions here (nudge, issue_certificate, reassign, clip) are stubs: swap them for your email, WhatsApp, push, or LMS API.
Completion and progress: nudges and re-engagement
Re-engagement nudge for learners who opened but never watched. Repeated opens with almost no watch time is a clear sign to start the learner over. On the sample this catches two learners who opened lessons repeatedly and watched seconds.
for L in learners.itertuples():
if L.watch_min < 1.0 and L.opens >= 3: # opened repeatedly, watched seconds
nudge(L.name, "Pick up where you left off")
# Liam Walsh - opened 4x, watched 20s
# Jack Thompson - opened 9x, watched 12sAt-risk flag for learners skimming ahead. A learner who reaches halfway on a fraction of the watch time is moving through the material without absorbing it. The watch_min >= 1 guard keeps this separate from the opened-but-never-watched case above, so each learner gets one action. Flag them for an instructor before they stall.
for L in learners.itertuples():
if 0.3 <= L.reach < 0.9 and L.coverage < 0.30 and L.watch_min >= 1:
flag_for_instructor(L.name, reach=L.reach, coverage=L.coverage)
# Ava Sullivan - reached 67% but watched 13%
# Ethan Brooks - reached 64% but watched 18%
# Mason Reed - reached 49% but watched 18%Stalled-cohort reminder. Filter a cohort to the learners who have not progressed in a set window and send one reminder to just that segment. (This recipe needs a date filter on view_start; it is in the notebook rather than the sample run, which covers a single window.)
Completion: certificates, review, and reassignment
Issue a certificate on genuine completion. Credit requires reaching the end and watching enough of the runtime, so a certificate never goes to someone who dragged the bar.
for L in learners.itertuples():
if L.reach >= 0.9 and L.coverage >= 0.6:
issue_certificate(L.name)
# Sophie Turner, Daniel HayesHold a suspicious completion for review. Reaching the end on light watch time is a review flag, not a failure: it can be faster playback or a rewatch. Withhold the certificate and let a human look.
for L in learners.itertuples():
if L.reach >= 0.9 and L.coverage < 0.6:
hold_for_review(L.name, coverage=L.coverage)
# Emma Carter - reached 98%, watched 15% of runtimeReassign incomplete mandatory training. For compliance content, the workflow that matters is chasing the learners who are not done by the deadline. Combine the completion signal with your due date and reassign or escalate. (Deadline is your data, so this recipe lives in the notebook.)
Replays: turn rewatched moments into clips
Clip the sections learners rewatch. The moments learners jump back to are the ones worth pulling into a short or a micro-lesson. On the sample this produces four clip requests with timestamps.
for section in most_replayed_sections(min_replays=3):
clip(section.lesson, section.start, section.end)
# Orientation 21:45-25:00 (7 replays), 30:00-33:15 (5 replays)
# Workshop 23:05-26:30 (3 replays), 26:00-29:25 (3 replays)Part 4: Run it on a schedule
The recipes decide who and why. A cron runs them each night and your engine sends the actions.
# every night at 2:00: compute signals from yesterday's export, fire the actions
0 2 * * * python compute_signals.py && python run_recipes.pyThe action stubs are the only thing you replace. nudge becomes your email or WhatsApp campaign call; issue_certificate and reassign become LMS API calls; clip becomes a request to the FastPix clipping API. FastPix never sends the message; it hands your engine the list of who and why.
How different teams use this
| Team | The recipes they run | Their engine |
|---|---|---|
| Frontline training (compliance) | Reassign incomplete mandatory training, hold reached-end-low-coverage for review, progress reminders | LMS API, in-app push |
| Online course academy | Re-engagement nudges for inactive learners, certificates on completion, replay-to-clip | Email + WhatsApp campaigns |
| B2B SaaS onboarding | Nudge users who stalled in onboarding videos, flag accounts skimming setup | Product messaging, CRM |
For compliance teams, reassign and hold-for-review are the recipes that do the real work: a completion that carries legal weight cannot be a drag to the end.
Running this on your own data
Point Part 2 at your own export and wire each stub to your engine. The two decisions before you ship: the thresholds (opens, coverage floor, replay minimum) tuned to your catalog, and which channel each action uses. Everything else, the signals and the filters, runs unchanged.
Put your video data to work
FastPix Video Data records every view with a stable viewer ID, custom dimensions for your user and course IDs, and the reach, coverage, and rewatch signals these recipes run on. $25 credit to start, no credit card required; the free tier includes 100K plays a month. Start free · Read the docs
Frequently Asked Questions (FAQs)
How do I automate reminders based on video progress?
Calculate each learner's furthest playback position and total watch time from view-level analytics, then identify learners who have stalled or opened a video without making meaningful progress. Send this list to your reminder or campaign platform to trigger personalized follow-up messages.
Can I automatically issue certificates when someone completes a video?
Yes. To prevent misuse, require learners to both reach the end of the video and watch a sufficient percentage of its runtime before issuing a certificate. This ensures that certificates are awarded only for genuine participation.
How do I re-engage learners who stopped watching?
Identify learners who have made no recent progress or repeatedly opened a video without spending much time watching it. Trigger a reminder through email, WhatsApp, push notifications, or another messaging channel that links them directly back to the point where they left off.
How do I turn the most rewatched parts of a video into clips?
Analyze playback events for backward playhead jumps, identify sections that multiple learners replay, and submit those timestamps to a clipping API. This process automatically generates candidate highlights based on real viewer engagement.
Does FastPix send the emails and notifications?
No. FastPix generates the engagement signals and identifies which learners should receive reminders, but your existing campaign platform, messaging service, or learning management system is responsible for delivering emails, push notifications, or other communications.
What does it cost to start?
Getting started is free. You receive a $25 credit with no credit card required. The Video Data free tier includes 100,000 plays per month, and the FastPix Player is free for FastPix customers.






