An exam integrity score condenses a session’s proctoring signals into one number from 0 to 100, computed server-side so the client cannot influence it. Start at 100 and subtract weighted penalties for each finding. Let face analysis drive the verdict and treat browser events as context for a reviewer. Band the result (low, medium, high) and use it to triage sessions for human review, never to fail a candidate automatically. Everything below is grounded in the ProctorLink API reference and scoring model.
A proctored exam produces far more than a reviewer can read by hand: a frame a minute, a stream of browser events, a heartbeat, and an identity comparison on every frame. An integrity score collapses all of that into one comparable number so you can decide which handful of sessions out of thousands actually need a human to look. The score does not decide guilt. It ranks sessions by how much they need attention, and it has to be explainable: every point lost should trace back to a specific, named finding.
ProctorLink returns the score as part of the report you fetch on your backend with GET /v1/sessions/:id. The integrity block is the headline verdict.
// From GET /v1/sessions/:id — the headline verdict,
// computed entirely server-side. The browser cannot influence it.
"integrity": {
"score": 72,
"level": "medium",
"flagged": true,
"analysis_complete": true,
"identity_match": "pass",
"reasons": [
{ "code": "no_face", "detail": "No face detected in 3 frames", "weight": -18 },
{ "code": "browser_activity", "detail": "Tab hidden 2x, 1 clipboard copy", "weight": -10 }
]
}If you are new to the integration, read handling the exam session lifecycle for how a session is minted, run, and resumed, and proctoring SDK vs API vs LMS plugin for where the SDK fits. This page is about the number that falls out at the end.
The first design decision is where the score is calculated, and there is only one defensible answer. The browser is the one part of the system the candidate controls. Anything computed in page JavaScript can be edited before it reaches you. So the score, and everything feeding it, is computed entirely server-side from the frames and events uploaded during the attempt. The browser SDK reports what it saw; it never decides what any of it is worth, and it never produces a verdict.
This is the core anti-tamper property. Frames upload straight from the browser to object storage, the session token is scoped to one attempt and expires with it, and the analysis that turns frames into a score runs on infrastructure the candidate never touches. It is also why you read the score from the API on your backend rather than from the SDK in the page. For the full reasoning behind that trust boundary, see why proctoring runs in a cross-origin iframe.
The model is deliberately simple to keep it explainable. Every session starts at 100, a clean sheet. Each finding subtracts a weighted penalty, and the weight reflects how much the finding bears on whether the exam was taken honestly by the right person. The result is clamped at 0. The penalties below are the ceilings; the actual deduction scales with how often and how strongly a finding occurred.
| Signal | reasons[].code | Penalty | What it measures |
|---|---|---|---|
| Identity mismatch | impersonation | up to -50 | Who sat the exam. The heaviest single finding. |
| Multiple faces in frame | multiple_faces | up to -30 | A second person visible to the camera. |
| No face detected | no_face | up to -25 | Frames where no face was found. Not read as "left the room". |
| Browser activity (pooled) | browser_activity | up to -20 combined | Tab hidden, left fullscreen, clipboard, denied camera — one shared cap. |
| Supervision gap | liveness_gap | can flag alone | Heartbeat stopped; the candidate could not be observed. |
Every deduction is itemised in the reasons array, each entry carrying a code you branch on, a human-readable detail you show a reviewer, and a negative weight. Because the entries sum to the gap between 100 and the final score, the number is never a black box: you can always say exactly why a session scored what it did.
Not all signals are equal, and treating them as if they were is how a scoring model manufactures false accusations. The findings that drive the verdict come from face analysis: an identity mismatch, more than one face in frame, or no face at all. These speak to who was present and whether they sat the exam themselves, which is what the whole exercise is about. That is why an identity mismatch can take off up to 50 points on its own.
Browser events are different. A tab switch, leaving fullscreen, a clipboard action, or a denied camera are worth recording, but each fires often for innocent reasons. So they are pooled into a single browser_activity reason, capped at a combined 20 points. No amount of ordinary browser activity can, by itself, push a session into the flagged band. This restraint is a feature, not a weakness: it means a candidate who rotated their phone or whose headset reconnected is not marked down as a cheat. Describe browser events to a reviewer as context, and let face analysis carry the verdict. The same split, and what a browser tab genuinely can and cannot see, is covered in what browser-based proctoring can and cannot detect.
A note on wording that matters for defensibility: the model reports frames where no face was detected, not “the candidate left the room”. Face analysis is probabilistic, and the score is built from what was measured, not from what you might infer about it.
The score alone hides a distinction that matters. identity_match carries the identity verdict separately: pass means frames were compared and none fell below the match threshold, fail means at least one did, and pending means analysis has not finished. The one to handle carefully is inconclusive: it means analysis finished but nothing could be compared, because no frame had exactly one clear face or the reference photo was unusable. Identity was never checked either way.
switch (report.integrity.identity_match) {
case 'pass': // frames were compared; none fell below threshold
break;
case 'fail': // at least one frame did not match the reference
queueForReview(report);
break;
case 'inconclusive': // nothing could be compared — UNKNOWN, not clean
queueForReview(report);
break;
case 'pending': // analysis not finished — fetch again when validated
break;
}Treat inconclusive as unknown, not as a clean pass. It is easy to misread a session that scored well on everything else as fine when identity was never actually verified. The usual cause is an unusable reference photo, which you can catch at mint time with verify_reference_face so the candidate retakes it while they are still present, rather than surfacing it after the exam is over.
A raw number out of 100 is hard to act on consistently, so the score is bucketed into three risk bands. The level field gives you the band directly, and flagged is true for any session that needs review.
| level | Score | What it means for review |
|---|---|---|
low | score >= 80 | Clean. Safe to auto-pass under most policies. |
medium | score 50 to 79 | Flagged. Review the reasons and evidence frames. |
high | score < 50 | Flagged. One or more heavy findings; always review. |
Any session scoring below 80 is flagged. The bands and the weights behind them are produced server-side and are identical for every session, which is what makes a result comparable across candidates and defensible if challenged later. What you decide is the policy on top: branch on integrity.level and the reasons[].code values to route sessions. A high-stakes certification exam and a low-stakes practice quiz can treat the same score differently without changing how it was measured.
// On your backend. Fetch only once analysis has finished.
const report = await getSession(sessionId); // GET /v1/sessions/:id
if (report.status !== 'validated') return; // not final yet — poll later
const { level, flagged, identity_match, reasons } = report.integrity;
// A score is a triage signal, not a verdict. Route, do not auto-fail.
if (level === 'low' && identity_match === 'pass') {
autoApprove(report);
} else {
// Hand the reviewer the codes and the evidence frames behind them.
queueForReview(report, reasons); // reasons[].code + report.evidence
}reasons and the evidence frames.browser_activity reason with a shared cap, and let face analysis carry the verdict.identity_match: inconclusive as clean. It means identity was never checked, not that it passed. Fix: treat it as unknown, route it to review, and catch unusable reference photos at mint with verify_reference_face.GET /v1/sessions/:id on your backend, where it was computed server-side from the uploaded evidence.identity_match: pending, which is an unfinished result, not a clean one. Fix: fetch when status is validated or analysis_complete is true.The design choices reinforce one another. Server-side computation makes the score tamper-resistant. A clean-sheet start with itemised, weighted deductions makes it explainable down to the point. Letting face analysis drive the verdict while browser events stay pooled and capped keeps ordinary behaviour from manufacturing a flag. And banding plus a human review step turns the number into a decision process rather than an automatic sentence. A score built this way is one you can defend to an exam board, a candidate who appeals, or an auditor.
For how an automated score compares with a human watching a live feed, and where each is the better fit, read AI proctoring vs live proctoring. Across published deployments, ProctorLink has supported more than one million proctored exam sessions (methodology note below). Full cohort sizes and outcomes are on the case studies page.
Institutions evaluating proctoring tools often look for independent feedback outside vendor case studies. ProctorLink is listed on G2, where Moodle administrators and training teams share verified product reviews.
Read ProctorLink reviews on G2 →Deployment statistics and product behaviour described in this guide link to the sources below.
Want to see a real integrity score with its reasons and evidence frames? Sign up at app.proctorlink.com to get your API keys, run a session, and read the report end to end.
Create an account at app.proctorlink.com to get your SDK credentials and read a live report.
Install @proctorlink/sdk, the thin loader that mounts the enclave and captures keyframes.
See the integrity score, its reasons, and the evidence frames in a 30-minute walkthrough.
Per-session and subscription models for custom applications and LMS exams.
Which signals feed the score, and what a browser tab genuinely cannot see.
How a session is minted, resumed, and finally validated before the report is ready.
Back to the knowledge hub for the full SDK and proctoring series.
Online proctoring supervises remote exams via webcam and microphone inside your LMS, logging rule violations with timestamps so reviewers judge flagged cases, not every session.
AI proctoring uses machine learning to automatically detect suspicious exam behaviour such as multiple faces, tab switching, and absence from the camera.
A comparison of leading Moodle proctoring plugins for universities that need native LMS integration, AI monitoring, and institution-owned data.
How university exam offices choose online proctoring software for entrance grids, finals, and multi-faculty calendars, including stakeholder RFPs and centre-vs-online cost tradeoffs.
How certification bodies and training providers use online proctoring for identity proofing, item-bank protection, and defensible, audit-ready credentialing exams.
A side-by-side comparison of AI proctoring and live human proctoring for cost, scale, accuracy, and exam security.
Practical steps to reduce cheating in Moodle quizzes using proctoring settings, question banks, timing controls, and AI monitoring.
Feature-by-feature comparison of Moodle proctoring plugins covering integration, AI detection, pricing, and data storage.
A guide to online exam proctoring for Indian universities and certification providers, including compliance, pricing in INR, and local case studies.
How to choose between a proctoring LMS plugin, a browser SDK, and a REST API when adding proctoring to software you already own, with a decision guide and code examples.
A step-by-step guide to adding exam proctoring to a web app you own: mint a session on your backend, run the browser SDK, and read a server-side integrity report, with code from the API reference.
What browser-based proctoring can detect (tab switches, clipboard, faces, identity) and what it cannot (phones, a second monitor, a virtual machine), and why the tiering exists.
A step-by-step guide to adding exam proctoring to a React app: mint a session on your backend, wrap the browser SDK in a useEffect hook, handle StrictMode and token expiry, and read a server-side integrity report.
A step-by-step guide to adding exam proctoring to an Angular app: mint a session on your backend, hold the browser SDK in an injectable service, start it in ngOnInit and tear it down in ngOnDestroy, handle NgZone and token expiry, and read a server-side integrity report.
How LTI 1.3 connects a proctoring tool to your LMS: the OpenID Connect launch handshake, why it replaces LTI 1.1 shared secrets, how it compares to a Moodle plugin and the browser SDK, and how to read the same server-side integrity report.
A step-by-step guide to adding exam proctoring to an assessment platform you built: map your users, exams, and attempts to a session, mint it on your backend, run the browser SDK in your exam runner, and route the server-side integrity report into your own review workflow.
Why the ProctorLink SDK mounts a cross-origin iframe enclave instead of running in your page: the camera permission binds to the enclave origin and persists across sites, the session token stays out of host-page JavaScript, and the integrity score is computed server-side.
How a proctored exam survives a tab close, a refresh, and a network drop without fragmenting into several reports: resume by a stable attempt_id, an offline-tolerant event queue, token-expired handling, and tearing down an SPA route change without ending the attempt.
A server-side score, itemised reasons, face analysis driving the verdict, and a human review step. Run a real session and read the integrity report before you commit.