A WordPress diagnostic plugin for tracing xAPI completion tracking failures on LearnDash + Tin Canny sites running Articulate Rise (or any xAPI-compliant content).
If your site runs Articulate Rise (or other xAPI content) in LearnDash with Tin Canny as your LRS, you may hit intermittent failures where courses show incomplete even though learners finished them. The statements silently disappear somewhere in this pipeline:
Rise/xAPI Content (iframe) → Tin Canny Endpoint → WordPress DB → LearnDash Completion
xAPI Statement Monitor makes those invisible failures visible — capturing every statement at multiple points in the pipeline, detecting mismatches automatically, and giving you the tools to fix them without writing SQL or digging through logs.
- 5-Layer Statement Capture — 4 server-side Tin Canny hooks + a JavaScript beacon that monitors client-side delivery from Rise iframes
- Automated Diagnostic Engine — WP-Cron job runs every 15 minutes, flagging completion mismatches (Tin Canny has data but LearnDash doesn't), statement gaps, and endpoint failures
- Admin Dashboard — 5-tab interface: Live Feed, Alerts, User Diagnostic, System Health, Settings
- Email Alerts — Configurable notifications for critical failures
- Fix Actions — Force LearnDash completion, reset xAPI data, resend statements — all from the dashboard without touching the database directly
- JavaScript Beacon — Intercepts XHR/Fetch from Rise iframes, detecting timeouts and network errors that server-side hooks can't see
- Zero Impact — Purely observational. Never blocks, modifies, or interferes with normal xAPI, Tin Canny, or LearnDash processing
- WordPress 6.0+
- PHP 7.4+
- LearnDash LMS
- Tin Canny Reporting for LearnDash (Uncanny Owl)
- xAPI content (Articulate Rise, Storyline, or any xAPI-compliant authoring tool)
- Download or clone this repository
- Upload the
xapi-statement-monitorfolder to/wp-content/plugins/ - Activate the plugin in Plugins → Installed Plugins
- Navigate to xAPI Monitor in the WordPress admin sidebar
- Configure email alerts and thresholds in the Settings tab
The diagnostic cron and JavaScript beacon activate automatically on plugin activation.
The plugin intercepts xAPI statements at every point in the pipeline to maximize coverage:
Fires before Tin Canny processes any incoming xAPI request. Captures the raw request body, current user, and page context. Marks the statement as captured.
Fires after Tin Canny successfully processes a statement. Upgrades the status to processed. A gap between Layer 1 and Layer 2 indicates a processing failure inside Tin Canny.
Monitors Tin Canny's decision about whether to write the statement to its database. If Tin Canny skips capture (returns false), the statement is logged with status blocked. The filter return value is never modified.
Passively intercepts REST API requests to Tin Canny's endpoint (/ucTinCan or similar). Provides a catch-all for statements that bypass the standard hook path. Never modifies the response.
Injected on LearnDash lesson/topic pages. Intercepts XHR and Fetch calls from the xAPI content iframe, capturing delivery success, failure, timeout, and network errors. Data is sent back to the server via navigator.sendBeacon (XHR fallback on unsupported browsers). This is the only layer that can detect client-side failures such as network drops, CORS issues, and browser-level blocks.
Real-time table of all captured xAPI statements. Color-coded by verb (green = completed/passed, red = failed, blue = experienced/progressed) and delivery status. Expandable rows show the full raw JSON. Filterable by user, verb, status, course, and date range. Includes a Resend button to resubmit any statement to the Tin Canny endpoint.
All alerts generated by the cron diagnostic engine. Each alert includes the affected user/course, supporting diagnostic data, and a recommended action. Available actions: Resolve, Force Complete, Reset xAPI. Includes a Run Diagnostic Now button.
Search any user by name, email, or username. Side-by-side comparison of Tin Canny data vs LearnDash completion status, with mismatches highlighted. Full statement timeline showing every verb received in chronological order. Per-lesson Force Complete buttons.
Endpoint connectivity status, statement capture rates, quick checks for Tin Canny/LearnDash active status, permalink configuration, object cache, PHP memory, cron scheduling, and environment info. Includes Test Endpoint Now and Run Full Diagnostic buttons.
Configure email alert recipients, alert thresholds (gap timeout, failure rate), JS beacon on/off, log retention period, and email notifications. Manual operation buttons: Run Diagnostic, Send Test Statement, Export All Logs to CSV, Purge Old Logs.
(Screenshots will be added after initial deployment.)
- Live Statement Feed
- Alerts & Diagnostics
- User Diagnostic
- System Health
- Settings
All settings are in xAPI Monitor → Settings:
| Setting | Default | Description |
|---|---|---|
| Alert Email Recipients | Admin email | Comma-separated list of addresses for critical alerts |
| Statement Gap Timeout | 30 minutes | How long to wait for a completion verb after activity verbs |
| Failure Rate Threshold | 10% | Alert when this percentage of statements fail in the last hour |
| JS Beacon | Enabled | Inject the client-side beacon on LearnDash lesson/topic pages |
| Log Retention | 30 days | How long to keep log entries before auto-purge |
| Email Notifications | Enabled | Send email alerts for critical issues |
Does this plugin interfere with Tin Canny or LearnDash? No. All filter hooks return their original values unchanged. The JS beacon observes requests without modifying them. The plugin only reads LearnDash data unless you explicitly use a "Fix" action.
Does it work with content other than Articulate Rise? Yes. It captures all xAPI statements flowing through Tin Canny — Rise, Storyline, Captivate, iSpring, Lectora, H5P, or any xAPI-compliant tool.
The endpoint health check fails but everything works fine? This is common on Hostinger/LiteSpeed where server-to-self loopback requests are blocked. The plugin detects this and falls back to verifying Tin Canny is active with its database tables intact. Real browser traffic to the endpoint works normally.
What does "Force Complete" actually do?
It calls LearnDash's learndash_process_mark_complete() for a specific user/lesson — the same function triggered when a user clicks Mark Complete. Use it when Tin Canny has recorded the completion but LearnDash wasn't notified.
How do I fully remove the plugin and its data?
Deactivate the plugin, then delete it via Plugins → Installed Plugins → Delete. The uninstall.php script drops all custom tables, removes all plugin options, clears scheduled cron events, and deletes any exported CSV files.
Contributions are welcome! See CONTRIBUTING.md for guidelines on reporting bugs, requesting features, and submitting pull requests.
GPL-2.0 or later. See LICENSE for the full license text.
Barry Schoedel — schoedel.design