ProctorSafe Integration Guide

Welcome to ProctorSafe! This guide will walk you through integrating our proctoring SDK into your website or application. The integration is straightforward and can be completed in just a few steps.

Table of Contents

  1. Quick Start
  2. Multi-Tenancy Setup
  3. SDK Installation
  4. Basic Integration
  5. Signature
  6. Security Architecture
  7. Event Handling
  8. Dashboard Access
  9. API Access to Session Data
  10. Data Retention
  11. Advanced Features
  12. Troubleshooting
  13. Roadmap

Quick Start

The fastest way to get started:

  1. Add the SDK script to your HTML
  2. Initialize and start a session
  3. Handle events via callback
  4. End the session when complete
  5. View results in the dashboard

Let's dive into each step.


Multi-Tenancy Setup

ProctorSafe uses a multi-tenant architecture where each organization is a tenant with its own configuration, security settings, and session data.

Tenant Identifier (Slug)

Every session must specify a tenantName (also referred to as a tenant slug). This is a unique, URL-friendly identifier for your organization provided by your administrator. It is required for all SDK configurations.

CORS Configuration

Before sessions can be initiated, your administrator must configure allowed origins in the tenant dashboard. This prevents unauthorized domains from using your tenant quota.

Important CORS setup steps:

  1. Contact your tenant administrator to add your domain(s) to the allowed origins list

  2. Supported formats:

    • Exact origins: https://example.com, http://localhost:3000
    • Wildcard domains: *.example.com (matches all subdomains)
    • Port variations are handled automatically: http://localhost will also match http://localhost:3000
  3. Common origins to configure:

    • Production domain: https://yourdomain.com
    • Development domain: http://localhost:3000 (or your dev port)
    • Staging domain: https://staging.yourdomain.com

Note: If you receive a "CORS Error: Origin not allowed" when starting a session, your tenant administrator needs to add your origin to the allowed list in the tenant settings.

What You'll Need

Before integrating the SDK, ensure you have:

  • Tenant name (slug): Provided by your tenant administrator
  • API key (optional): Only needed if you plan to use the create API helper for signature generation
  • CORS configuration: Your domain must be added to the allowed origins list by your tenant administrator

SDK Installation

1. Include the SDK Script (Required)

Add the ProctorSafe SDK to your HTML page using a single script tag. The SDK will automatically load all required dependencies (TensorFlow.js and modern-face-api) for you:

<!DOCTYPE html> <html> <head> <title>Your Exam Application</title> </head> <body> <!-- Your application content --> <!-- ProctorSafe SDK (auto-loads all dependencies) --> <script src="https://test.proctorsafe.eu/sdk/proctor.iife.js"></script> <!-- Your application scripts --> <script src="your-app.js"></script> </body> </html>

Note: The SDK automatically loads the following dependencies when needed:

  • TensorFlow.js Core (for AI face detection)
  • modern-face-api (for face detection models)

You don't need to manually include these scripts - the SDK handles it for you!

TypeScript Support: TypeScript definition files are available via CDN. See Option 2: TypeScript Support for details.

To get full type safety for the window.Proctor object and configuration interfaces, include our type definition file in your project.

Note: This file only provides type information; you must still load the SDK script via the HTML tag mentioned above.

Step 1: Download the Type Definition File

Download the type definition file from our CDN:

  • URL: https://test.proctorsafe.eu/sdk/proctor.d.ts

Step 2: Add to Your TypeScript Project

Copy the downloaded file to your TypeScript project. A common location is a types directory:

your-project/
├── src/
│   └── ...
├── types/
│   └── proctor.d.ts    ← Copy the file here
└── tsconfig.json

Step 3: Configure TypeScript

Ensure your tsconfig.json includes the types directory:

{ "compilerOptions": { // ... your options }, "include": [ "src/**/*", "types/**/*" ] }

Step 4: Use Types in Your Code

Once the type definition file is in your project, you can use all ProctorSafe types with full TypeScript support:

// The global Window.Proctor is automatically typed const proctor = window.Proctor; // Full TypeScript support with all types const config: ProctorConfig = { tenantName: 'your-tenant-slug', applicationReference: 'exam-123', settings: { hudMode: 'full', monitorVisibility: true, detectionInterval: 1000, enableOfflineSync: true, audioSensitivity: 0.5, }, }; const listener = (event: ProctorEvent) => { // Handle events with full type safety console.log('Event:', event.type, event.metadata); }; await proctor.start(config, listener);

You can also import specific types in your TypeScript/TSX files:

import type { ProctorConfig, ProctorEvent } from './types/proctor'; // Use the types in your code function createProctorConfig(): ProctorConfig { return { tenantName: 'your-tenant-slug', applicationReference: 'exam-123', settings: { hudMode: 'full', monitorVisibility: true, detectionInterval: 1000, enableOfflineSync: true, audioSensitivity: 0.5, }, }; } function handleProctorEvent(event: ProctorEvent) { // Type-safe event handling switch (event.type) { case 'FACE_MISSING': // ... break; case 'TAB_BLUR': // ... break; } }

Important Notes:

  • The global Window.Proctor declaration is automatically included in proctor.d.ts - you don't need to declare it yourself
  • You still need to load the SDK via the script tag for the actual implementation
  • The type definitions only provide TypeScript support - they don't include the runtime code

Tenant configuration: After installing the SDK, you'll need your tenant name (slug) and may need an API key if using the create API helper. See the Multi-Tenancy Setup section for details.

Advanced: Self-Hosted Dependencies

If you prefer to host the dependencies yourself (for performance, security, or compliance reasons), you can load them before the ProctorSafe script. The SDK will detect them and skip auto-loading:

<!-- Load TensorFlow.js first (optional - SDK will load if missing) --> <script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs-core@4.22.0/dist/tf-core.min.js"></script> <!-- Load modern-face-api (optional - SDK will load if missing) --> <script src="https://cdn.jsdelivr.net/npm/modern-face-api@0.22.5/dist/index.js"></script> <!-- Then load ProctorSafe --> <script src="https://test.proctorsafe.eu/sdk/proctor.iife.js"></script>

Basic Integration

Step 1: Check Requirements

Before starting a session, check if the user's environment is ready:

// Check if the user's browser and hardware are ready const requirements = await Proctor.checkRequirements(); if (!requirements.success) { console.error('Requirements not met:', requirements); // Handle the error - show user what's missing if (requirements.camera !== 'available') { alert('Camera access is required for this exam'); } if (requirements.microphone !== 'available') { alert('Microphone access is required for this exam'); } return; } // Optional: surface screen share support to the user if your exam requires it if (requirements.screenShareSupported === false) { console.warn('Screen sharing is not supported in this browser environment.'); } console.log('All requirements met!');

Step 2: Start a Session

When the user begins their exam, start the proctoring session. Note: The SDK uses a secure handshake protocol that runs automatically. See the Signature section for details on optional signature configuration.

// Start the proctoring session // Proctor.start() returns the session ID for your records const sessionId = await Proctor.start( { // Tenant name (slug) - required for multi-tenancy tenantName: 'your-tenant-slug', // Your unique application identifier applicationReference: 'exam-platform-2024', // Signature (optional but recommended) - see Signature section // If your tenant requires signatures, you must provide this signature: 'base64-encoded-signature', timestamp: Date.now(), // Configuration options settings: { hudMode: 'full', // Controls proctoring overlay: 'full' (pill + camera), 'camera' (camera only), 'standard' (pill only), 'initial' (pill with "Session started" then auto-hide), 'off' (no overlay) monitorVisibility: true, // Track tab switching detectionInterval: 1000, // Face detection frequency (ms, minimum 500ms) enableOfflineSync: true, // Queue events if connection drops audioSensitivity: 0.5, // Microphone sensitivity (0.0-1.0) }, }, // Event callback function (event) => { console.log('Proctor event:', event); // Handle events in real-time (see Event Handling section) } ); // Store the session ID for later reference console.log('Session started with ID:', sessionId); // You can store this in your database to link the exam to the proctoring session

Important notes:

  • tenantName is required - this is your tenant's unique identifier (slug)
  • signature and timestamp are optional but recommended for security. See the Signature section for details on how to generate signatures
  • If your tenant has signature requirement disabled, you can omit signature and timestamp
  • All security handshaking is handled automatically by the SDK - no additional configuration needed
  • Proctor.start() returns the session ID - Store this ID in your system to reference the proctoring session later (e.g., for audit purposes or linking to exam results)

Reusing an applicationReference

By default, each applicationReference may only be used once. Calling Proctor.start() again with an applicationReference that already has a session returns an error ("Application reference already used"), so best practice is to generate a new, unique applicationReference for every attempt (e.g. append a timestamp or attempt number).

If your tenant administrator has enabled Automatic Session Takeover (Dashboard → Tenant Settings → Features), reusing an applicationReference is allowed when the prior session has gone quiet — no heartbeat received in the last 15 seconds. In that case the stale session is preserved (marked as aborted) and a fresh session starts transparently in its place, so Proctor.start() succeeds instead of throwing. A completed or terminated session is never taken over, even with this setting enabled — recover from those cases with a new applicationReference instead. Because this is a tenant-wide setting intended for recovering dropped sessions (e.g. a crashed browser or lost connection), a new applicationReference per attempt remains the recommended approach for the clearest audit trail.

Device clock and session initiation

During POST /api/proctor/init, the server compares your device wall clock to server time. The official SDK sends client_datetime for you: a UTC ISO-8601 string ending with Z (the same format as new Date().toISOString() in JavaScript). The server rejects the handshake with HTTP 400 when:

  • client_datetime is missing or not a valid UTC instant with a Z suffix
  • The absolute difference between client_datetime and the server clock exceeds the allowed skew (default 30 seconds)

The error body explains the failure (for example, clock skew too large). Ensure end-user devices have correct time and time zone (NTP enabled). Very wrong clocks will block proctoring from starting until corrected.

Step 3: Mark Custom Events and Manage Sections

ProctorSafe provides two ways to track exam progress: marks for point-in-time events and sections for duration-based tracking.

Mark Custom Events (Point-in-Time)

Use Proctor.mark() to record important milestones as single events in the timeline:

// When a question is answered Proctor.mark('QUESTION_ANSWERED', { questionId: 5, answer: 'A', timestamp: new Date().toISOString(), }); // When a section is completed Proctor.mark('SECTION_COMPLETE', { sectionId: 'math-section', questionsAnswered: 10, }); // When user flags a question for review Proctor.mark('QUESTION_FLAGGED', { questionId: 12, });

Manage Sections (Duration-Based)

Use Proctor.startSection() and Proctor.endSection() to track time spent on different parts of the exam. Sections are useful for analyzing how long students spend on each exam section.

Important: Only one section can be active at a time. You must end the current section before starting a new one.

// Start a new section (e.g., when user begins "Mathematics Section") await Proctor.startSection('Mathematics Section', { sectionId: 'math-section', totalQuestions: 20, }); // ... user works on the section ... // End the section when user moves to the next part await Proctor.endSection(); // Start the next section await Proctor.startSection('Reading Comprehension', { sectionId: 'reading-section', totalQuestions: 15, }); // End the section when complete await Proctor.endSection();

Use Cases for Sections:

  • Track time spent on different exam sections (e.g., "Mathematics", "Reading", "Writing")
  • Monitor duration of specific activities (e.g., "Essay Writing", "Multiple Choice")
  • Analyze pacing and identify where students spend the most time

Use Cases for Marks:

  • Record when specific questions are answered
  • Flag important moments (e.g., "User requested help", "Question reviewed")
  • Track discrete events that don't have a duration

Step 4: End the Session

When the exam is complete, end the session:

// End the session and get summary const summary = await Proctor.end(); console.log('Session Summary:', { sessionId: summary.sessionId, duration: summary.endTime - summary.startTime, totalEvents: summary.totalEvents, trustScore: summary.trustScore, // 0-100 timeline: summary.timeline, // All events }); // You can now submit this to your backend or display to the user

Event Handling

ProctorSafe generates events in real-time. You handle them via a callback function:

Callback Function (Real-time)

Events are delivered immediately via the callback function:

await Proctor.start( { tenantName: 'your-tenant-slug', applicationReference: 'exam-platform-2024', settings: { /* ... */ }, }, (event) => { // Handle events as they occur switch (event.type) { case 'FACE_MISSING': // User's face is not detected showWarning('Please ensure your face is visible'); break; case 'MULTIPLE_FACES': // Multiple faces detected showWarning('Only one person should be in the frame'); break; case 'TAB_BLUR': // User switched tabs showWarning('You switched away from the exam window'); logViolation('Tab switch detected'); break; case 'MIC_PEAK': // Audio detected console.log('Audio activity detected'); break; case 'SECOND_SPEAKER_SUSPECTED': // A voice pattern different from the enrolled candidate was detected console.warn('Possible second speaker detected'); break; default: // Custom marks or other events if (event.source === 'CUSTOM') { console.log('Custom event:', event.type, event.metadata); } } } );

(Planned) Webhook Delivery

In a future version, ProctorSafe will support backend-driven webhooks for server-side processing. See the Roadmap section for details.


Signature

ProctorSafe supports cryptographic signatures to prevent unauthorized use of your tenant. Signatures are highly recommended for production environments.

Signature Overview

Signatures provide an additional layer of security by ensuring that only authorized clients can initiate sessions for your tenant. Here's how it works:

  • ECDSA P-256 Cryptography: Signatures use industry-standard ECDSA P-256 (prime256v1) elliptic curve cryptography
  • Payload Signing: The signature covers a comma-separated string: $tenant,$reference,$timestamp
  • Server Verification: The server verifies signatures before allowing session initiation
  • Optional but Recommended: While signatures are recommended, tenant administrators can disable the requirement (with appropriate security warnings)

Signature Payload Structure

The exact payload that must be signed is a comma-separated string in the following format:

tenant-name,application-reference,timestamp

Format: $tenant,$reference,$timestamp

Example:

my-tenant,exam-platform-2024,1704067200000

Timestamp format: The timestamp is in JavaScript timestamp format (milliseconds since Unix epoch, January 1, 1970 UTC). You can generate it using Date.now() in JavaScript or equivalent in other languages.

This string must be signed using ECDSA P-256 with SHA-256. The signature is returned as a base64-encoded DER format.

Important: The payload is a simple comma-separated string, not JSON. This makes the signing process more deterministic and avoids JSON serialization edge cases.

For production environments, we recommend generating and managing your own ECDSA P-256 key pair. This gives you full control over the signing process.

Steps:

  1. Generate an ECDSA P-256 key pair (client-side or server-side)
  2. Upload the public key to the tenant admin dashboard (Settings > Custom Public Key)
  3. Sign the payload using your private key
  4. Include the signature and timestamp in the SDK config

Example: Generating a key pair and signing (Node.js)

const crypto = require('crypto'); // Generate ECDSA P-256 key pair const { privateKey, publicKey } = crypto.generateKeyPairSync('ec', { namedCurve: 'prime256v1', publicKeyEncoding: { type: 'spki', format: 'der', }, privateKeyEncoding: { type: 'sec1', format: 'der', }, }); // Store the public key (hex-encoded) - upload this to tenant dashboard const publicKeyHex = publicKey.toString('hex'); console.log('Public key (upload to dashboard):', publicKeyHex); // Keep the private key secure (never expose to client-side) const privateKeyHex = privateKey.toString('hex'); // Sign a payload (comma-separated format: tenant,reference,timestamp) function signPayload(tenantName, applicationReference, timestamp, privateKeyHex) { // Build payload string in format: $tenant,$reference,$timestamp const payloadString = `${tenantName},${applicationReference},${timestamp}`; const privateKey = crypto.createPrivateKey({ key: Buffer.from(privateKeyHex, 'hex'), format: 'der', type: 'sec1', }); const sign = crypto.createSign('SHA256'); sign.update(payloadString); sign.end(); const signature = sign.sign(privateKey); return signature.toString('base64'); } // Use in your application const timestamp = Date.now(); const signature = signPayload('your-tenant-slug', 'exam-platform-2024', timestamp, privateKeyHex); // Include in SDK config await Proctor.start({ tenantName: 'your-tenant-slug', applicationReference: 'exam-platform-2024', signature: signature, timestamp: timestamp, settings: { /* ... */ }, }, eventCallback);

Using OpenSSL to generate key pairs:

You can also use OpenSSL to generate key pairs. Here's a complete script you can use:

#!/bin/bash # Generate ECDSA P-256 key pair for tenant custom signing # Outputs both public and private keys in hex format (DER encoding) set -e # Generate private key echo "Generating ECDSA P-256 key pair..." openssl ecparam -genkey -name prime256v1 -noout -out private_key.pem echo "" echo "=== Public Key (SPKI DER, hex) ===" echo "Use this value for customPublicKey in tenant settings:" PUBLIC_KEY_HEX=$(openssl ec -in private_key.pem -pubout -outform DER | xxd -p -c 0) echo "$PUBLIC_KEY_HEX" echo "" echo "=== Private Key (SEC1 DER, hex) ===" echo "Keep this secure - you'll need it to sign payloads:" PRIVATE_KEY_HEX=$(openssl ec -in private_key.pem -outform DER | xxd -p -c 0) echo "$PRIVATE_KEY_HEX" echo "" # Clean up rm private_key.pem echo "=== Usage ===" echo "1. Copy the PUBLIC_KEY_HEX and set it as customPublicKey in your tenant settings" echo "2. Keep the PRIVATE_KEY_HEX secure - use it to sign payloads in format: tenant,reference,timestamp" echo "" echo "To test signing a payload (save private key hex to a file first):" echo " echo \"\$PRIVATE_KEY_HEX\" | xxd -r -p > private_key.der" echo " PAYLOAD=\"tenant-name,app-ref-123,1704067200000\"" echo " echo -n \"\$PAYLOAD\" | openssl dgst -sha256 -sign private_key.der | base64" echo " rm private_key.der"

Save this script to a file (e.g., generate-tenant-keypair.sh), make it executable with chmod +x generate-tenant-keypair.sh, and run it. It will output both the public key (for uploading to the dashboard) and private key (for signing) in hex format.

Important:

  • Keep your private key secure and never expose it to client-side code
  • Upload only the public key to the tenant dashboard
  • The server will verify signatures using your uploaded public key

Method 2: Using the Create API Helper

For development and testing, or if you prefer not to manage your own keys, you can use the create API helper. This endpoint uses the tenant's auto-generated private key (stored securely server-side) to generate signatures.

Endpoint: POST /api/proctor/create

Authentication: Bearer token (API key)

Request Body:

{ "tenant_name": "your-tenant-slug", "application_reference": "exam-platform-2024" }

Response:

{ "tenant_name": "your-tenant-slug", "application_reference": "exam-platform-2024", "signature": "base64-encoded-signature", "timestamp": 1234567890 }

Example: Using the create API helper

// Obtain an API key from your tenant admin dashboard (Settings > API Keys) const apiKey = 'pk_your-api-key-here'; // Generate signature using the create API async function generateSignature(tenantName, applicationReference, apiKey) { const response = await fetch('https://test.proctorsafe.eu/api/proctor/create', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}`, }, body: JSON.stringify({ tenant_name: tenantName, application_reference: applicationReference, }), }); if (!response.ok) { throw new Error('Failed to generate signature'); } return await response.json(); } // Use in your application const { signature, timestamp } = await generateSignature( 'your-tenant-slug', 'exam-platform-2024', apiKey ); // Include in SDK config await Proctor.start({ tenantName: 'your-tenant-slug', applicationReference: 'exam-platform-2024', signature: signature, timestamp: timestamp, settings: { /* ... */ }, }, eventCallback);

Note: This method uses the tenant's auto-generated private key, which is stored securely on the server. For production, Method 1 (your own private key) is recommended for better security control.

When Signature is Not Required

Tenant administrators can disable the signature requirement in the tenant settings. However, this is not recommended as it may allow unauthorized use of your tenant.

If signature requirement is disabled:

  • You can omit signature and timestamp from the SDK config
  • Sessions will still be initiated, but without signature verification
  • A warning will be logged on the server when sessions are initiated without signatures

Example without signature (only if requirement is disabled):

await Proctor.start({ tenantName: 'your-tenant-slug', applicationReference: 'exam-platform-2024', // No signature or timestamp needed settings: { /* ... */ }, }, eventCallback);

Security Architecture

ProctorSafe uses a secure architecture to protect session integrity and prevent tampering:

  • Automatic encryption: All session data is encrypted before transmission
  • Tamper protection: Events are cryptographically signed to prevent modification
  • Integrity validation: Canvas fingerprinting and environment checks detect bot/automated environments
  • No configuration needed: The SDK handles all security automatically

When you call Proctor.start(), the SDK automatically:

  1. Generates secure session keys
  2. Establishes an encrypted channel with the server
  3. Validates the session environment
  4. Begins monitoring and encrypting all telemetry data

You don't need to implement any of this - it's all handled internally by the SDK. Simply call Proctor.start() with your configuration, and the security handshake happens transparently in the background.


Dashboard Access

Access the ProctorSafe dashboard to view and analyze all proctoring sessions.

Logging In

  1. Navigate to: https://test.proctorsafe.eu/dashboard
  2. Log in with your credentials (provided by ProctorSafe support)
  3. You'll see the sessions list page

Viewing Sessions

The dashboard provides several views:

Sessions List

  • Live Sessions: Active sessions in real-time
  • All Sessions: Complete history with filtering options
  • Filters: By application reference, status, date range
  • Sorting: By start time, trust score, event count

Session Details

Click any session to view:

  • Session Metadata: ID, duration, application reference
  • Trust Score: Overall session integrity score (0-100)
  • Event Timeline: Chronological list of all events
    • System events (face detection, tab switching, etc.)
    • Custom marks (question answers, section completions)
  • Statistics: Violation counts, event breakdowns

Understanding Trust Scores

Trust scores range from 0-100:

  • 80-100: Excellent - Minimal or no violations
  • 60-79: Good - Some minor violations
  • 40-59: Fair - Moderate violations detected
  • 0-39: Poor - Significant violations detected

API Access to Session Data

ProctorSafe provides a REST API endpoint to retrieve complete session data in JSON format. This is useful for audit purposes, integration with your own systems, or programmatic access to session information.

Endpoint

GET /api/proctor/sessions/{sessionId}

Retrieves complete session data including all events, sections, trust score, and AI analysis (if available).

Authentication

The endpoint supports two authentication methods:

  1. API Key (Recommended for automated access)

    • Use your tenant's API key (obtained from Settings > API Keys in the dashboard)
    • Include in the Authorization header as a Bearer token
    • API keys can only access sessions belonging to their associated tenant
  2. User Session (For dashboard users)

    • Requires a valid user session cookie
    • Super admins can access all sessions
    • Tenant admins can only access their tenant's sessions

Request Example

// Using API key authentication const sessionId = 'cmkpc7vbw0000csm6r7vmvqb2'; // Session ID from Proctor.start() const apiKey = 'pk_your-api-key-here'; const response = await fetch(`https://test.proctorsafe.eu/api/proctor/sessions/${sessionId}`, { method: 'GET', headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json', }, }); if (!response.ok) { const error = await response.json(); throw new Error(error.error || 'Failed to fetch session'); } const sessionData = await response.json(); console.log('Session data:', sessionData);

Response Format

The API returns a complete JSON object with all session information:

{ "id": "cmkpc7vbw0000csm6r7vmvqb2", "applicationRef": "exam-platform-2024", "status": "COMPLETED", "heartbeatAt": "2024-01-15T10:30:00.000Z", "createdAt": "2024-01-15T09:00:00.000Z", "updatedAt": "2024-01-15T10:30:00.000Z", "clientInfo": { "userAgent": "Mozilla/5.0...", "audioDevices": [...], "videoDevices": [...], "screen": {...} }, "events": [ { "id": "evt_123", "timestamp": "2024-01-15T09:05:00.000Z", "source": "SYSTEM", "type": "FACE_DETECTED", "metadata": {} }, { "id": "evt_124", "timestamp": "2024-01-15T09:10:00.000Z", "source": "CUSTOM", "type": "QUESTION_ANSWERED", "metadata": { "questionId": 5, "answer": "A" } } ], "sections": [ { "id": "sec_123", "sessionId": "cmkpc7vbw0000csm6r7vmvqb2", "label": "Mathematics Section", "metadata": { "sectionId": "math-section", "totalQuestions": 20 }, "startTime": "2024-01-15T09:00:00.000Z", "endTime": "2024-01-15T09:30:00.000Z" } ], "trustScore": 85, "totalEvents": 150, "aiAnalysis": { "trustScore": 85, "summary": "Session completed successfully with minimal violations", "highlights": null, "recommendedAction": "APPROVE", "topRiskFactors": [], "estimatedReviewMinutes": 2, "status": "COMPLETED", "createdAt": "2024-01-15T10:30:00.000Z" }, "aiInsightsMode": "enabled" }

Use Cases

Audit and Compliance:

  • Export session data for compliance records
  • Generate reports for regulatory requirements
  • Archive session data in your own systems

Integration:

  • Link proctoring sessions to exam results in your database
  • Automate review workflows based on trust scores
  • Build custom dashboards using session data

Automation:

  • Automated session review processes
  • Batch processing of session data
  • Integration with learning management systems (LMS)

Error Responses

401 Unauthorized:

  • Missing or invalid API key
  • Missing or invalid user session

403 Forbidden:

  • API key does not belong to the session's tenant
  • User does not have permission to access the session

404 Not Found:

  • Session ID does not exist

Best Practices

  1. Store Session IDs: When Proctor.start() returns a session ID, store it in your database to reference later
  2. Use API Keys for Automation: API keys are ideal for server-side scripts and automated processes
  3. Handle Errors Gracefully: Check response status codes and handle errors appropriately
  4. Respect Rate Limits: Be mindful of API rate limits when making multiple requests
  5. Secure API Keys: Never expose API keys in client-side code - use them only in server-side applications

Example: Server-Side Integration

// Server-side Node.js example const express = require('express'); const app = express(); // Store session ID when exam starts (from client) app.post('/api/exam/start', async (req, res) => { const { examId, candidateId } = req.body; // Client calls Proctor.start() and sends sessionId // Store the mapping in your database const sessionId = req.body.proctorSessionId; await db.exams.create({ examId, candidateId, proctorSessionId: sessionId, // Store for later reference startedAt: new Date(), }); res.json({ success: true }); }); // Retrieve session data for audit app.get('/api/exam/:examId/audit', async (req, res) => { const exam = await db.exams.findByPk(req.params.examId); if (!exam || !exam.proctorSessionId) { return res.status(404).json({ error: 'Exam or session not found' }); } // Fetch session data from ProctorSafe API const response = await fetch( `https://test.proctorsafe.eu/api/proctor/sessions/${exam.proctorSessionId}`, { headers: { 'Authorization': `Bearer ${process.env.PROCTORSAFE_API_KEY}`, }, } ); if (!response.ok) { return res.status(response.status).json({ error: 'Failed to fetch session data' }); } const sessionData = await response.json(); res.json({ exam: exam, proctoring: sessionData, }); });

Data Retention

ProctorSafe does not keep session data indefinitely by default. Your tenant administrator sets a retention period in the dashboard under Settings → Data Retention, and session data older than that period is automatically and permanently deleted.

What Gets Deleted

Once a session (proctoring or liveness check) is older than your configured retention period, the following are permanently removed:

  • The session record itself and its event timeline
  • Exam sections associated with the session
  • Any stored screenshots or screen/webcam recordings (Mode 2 — see Session Recording)
  • The AI analysis result for that session

This applies per exam/application (applicationReference), so different exams under the same tenant are purged independently based on the same retention setting.

Billing/usage records are never deleted — they are retained separately for invoicing and audit purposes and contain no session content or personal data.

Configuring the Retention Period

In the dashboard, go to Settings → Data Retention and choose a period between 7 and 90 days. Changing this setting requires confirmation, since shortening the period can make existing older data eligible for deletion.

Retention cannot be disabled from the tenant dashboard — you can only shorten or lengthen it within the 7–90 day range. If you need retention turned off entirely, contact your ProctorSafe account representative.

When Purging Happens

Purging runs automatically once per day, overnight (Europe time). There is no way to trigger an ad-hoc purge from your side — plan any exports or audits (see API Access to Session Data) to happen before data ages past your configured retention period.

Implications for Your Integration

  • Export what you need in time. If you archive session data or reports in your own systems, do so well within the retention window — once purged, session data cannot be recovered.
  • Session IDs may stop resolving. Calls to the session data API for a session older than the retention period will return 404 Not Found once it has been purged.
  • Increasing retention doesn't restore already-deleted data. Retention changes only affect data going forward; anything already purged under a shorter period is gone.

Advanced Features

Custom Configuration

Fine-tune the SDK behavior:

await Proctor.start({ tenantName: 'your-tenant-slug', applicationReference: 'exam-platform-2024', settings: { hudMode: 'standard', // Controls proctoring overlay display monitorVisibility: true, // Track tab switching detectionInterval: 500, // Minimum allowed: 500ms (2 FPS) enableOfflineSync: true, // Queue events offline audioSensitivity: 0.7, // Higher mic sensitivity // Enable screen sharing capture (optional) captureScreenShare: true, // Tenant policy can require users to share their entire screen screenShareCapture: { requiredSurface: 'monitor', // Require \"Entire Screen\" selection in the browser dialog }, }, }, eventCallback);

HUD Mode Configuration

The hudMode parameter controls what proctoring overlay elements are displayed to the user. Note: This parameter replaces the deprecated showPreview parameter.

Available modes:

  • 'full': Shows both the status indicator (pill) and the camera preview. This is the most visible option, providing full transparency about the proctoring session.
  • 'camera': Shows only the camera preview. The status indicator (pill) is hidden. Use this mode when you want the webcam preview visible without the traffic-light indicator.
  • 'standard': Shows only the status indicator (pill) with "Proctoring Active" text. The camera preview is hidden, providing a less intrusive experience while still indicating that proctoring is active.
  • 'initial': Shows the status indicator with "Session started" text, then automatically hides after 2 seconds. This provides a brief confirmation that the session has started without maintaining a persistent overlay.
  • 'off': Completely disables the HUD. No status indicator or camera preview is shown. Use this mode when you want a completely non-intrusive experience (though users should still be informed that proctoring is active through other means).

Fixed webcam preview position (optional)

By default, the camera preview can be moved by the test-taker (draggable bubble). If your UI has layout conflicts, you can pin the webcam preview to a fixed position and size in the viewport with settings.webcamOverlay.

Example: bottom-right, 120×90px, with a 16px margin:

await Proctor.start({ tenantName: 'your-tenant-slug', applicationReference: 'exam-platform-2024', settings: { hudMode: 'full', monitorVisibility: true, detectionInterval: 1000, enableOfflineSync: true, audioSensitivity: 0.5, webcamOverlay: { position: 'bottom-right', // 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left' size: { width: 120, height: 90 }, // px offset: { x: 16, y: 16 }, // px from the anchored edges // zIndex: 999999, // optional }, }, }, eventCallback);

Defaults (when omitted):

  • position: 'bottom-right'
  • size: 200×150
  • offset: { x: 20, y: 20 }

Migration from showPreview:

  • showPreview: truehudMode: 'full'
  • showPreview: falsehudMode: 'standard'

The showPreview parameter is still supported for backwards compatibility but will show a deprecation warning in the console. We recommend migrating to hudMode for more granular control.

Network Monitoring

ProctorSafe can monitor and optionally block outbound network requests to prevent access to forbidden materials during exams. Network monitoring is configured by tenant administrators and automatically applied to all sessions.

How It Works

Network monitoring intercepts all fetch and XMLHttpRequest calls made by your application and checks them against configured whitelist and blacklist rules:

  • Whitelisted domains: Always allowed (includes CORS origins automatically)
  • Blacklisted domains: Flagged as violations and optionally blocked
  • Monitoring modes:
    • off: No network monitoring
    • monitor: Log network requests but allow them to proceed
    • block: Block requests to blacklisted domains and log violations

Configuration

Network monitoring is configured by tenant administrators in the tenant settings dashboard:

  1. Network Mode: Choose off, monitor, or block
  2. Whitelisted Domains: Domains that are always allowed (CORS origins are automatically included)
  3. Blacklisted Domains: Domains that trigger violations (can select from global suspect domains or add custom domains)

Network Events

When network monitoring is enabled, the SDK generates events for network requests:

  • NETWORK_REQUEST: A normal network request was made (not in blacklist)
  • NETWORK_SUSPECT: A suspicious network request was detected (blacklisted domain)
    • In monitor mode: blocked=false, request continues
    • In block mode: blocked=true, request is aborted

Network events include metadata:

// NETWORK_REQUEST events (normal requests) { type: 'NETWORK_REQUEST', metadata: { url: 'https://example.com/api/data', domain: 'example.com', method: 'GET', reason: 'whitelisted' | 'monitored' } } // NETWORK_SUSPECT events (blacklisted domains) { type: 'NETWORK_SUSPECT', metadata: { url: 'https://chatgpt.com/api', domain: 'chatgpt.com', method: 'GET', blocked: false, // true in block mode, false in monitor mode reason: 'blacklisted' | 'not_whitelisted' } }

Handling Network Events

Network events are delivered via the event callback like other system events:

await Proctor.start( { tenantName: 'your-tenant', applicationReference: 'exam-platform-2024', settings: { // Network monitoring is configured server-side by tenant admin // No SDK configuration needed - it's automatically applied }, }, (event) => { if (event.type === 'NETWORK_SUSPECT') { const metadata = event.metadata; if (metadata.blocked) { // Request was actually blocked (block mode) console.warn(`Blocked request to ${metadata.domain}: ${metadata.url}`); showWarning(`Access to ${metadata.domain} is not allowed during the exam`); } else { // Request was allowed but flagged (monitor mode) console.warn(`Suspicious request detected to ${metadata.domain}: ${metadata.url}`); // Optionally show a warning or log for review } } else if (event.type === 'NETWORK_REQUEST') { // Normal network request (not blacklisted) console.log(`Network request to ${event.metadata.domain}`); } } );

Important Notes

  • Automatic Configuration: Network monitoring settings are automatically provided by the server during session initialization - no SDK configuration needed
  • Minimum Enforcement: The tenant's monitoring mode setting enforces a minimum monitoring level. The SDK can use a more strict mode (e.g., "block" when minimum is "monitor"), but cannot use a less strict mode. For example:
    • If tenant sets block, SDK must use block (cannot use monitor or off)
    • If tenant sets monitor, SDK can use monitor or block (cannot use off)
    • If tenant sets off, SDK must use off (no monitoring allowed)
  • CORS Origins: Domains in the tenant's CORS allowed origins list are automatically whitelisted
  • API Endpoint: The ProctorSafe API endpoint is automatically whitelisted to ensure the SDK can communicate with the server
  • Relative URLs: Relative URLs (same-origin requests) are always allowed
  • Trust Score Impact: NETWORK_SUSPECT events (suspicious/blacklisted requests) affect the trust score similar to other violations. NETWORK_REQUEST events (normal requests) do not affect the trust score.

Tenant Administrator Setup

Tenant administrators can configure network monitoring in the tenant settings:

  1. Navigate to Settings > Network Monitoring
  2. Select minimum monitoring level: off, monitor, or block (this enforces a minimum - SDK can use same or more strict mode)
  3. Add whitelisted domains (optional - CORS origins are included automatically)
  4. Add blacklisted domains:
    • Select from global suspect domains (managed by super admins)
    • Add custom domains manually

Super administrators can manage the global suspect domains list (e.g., chatgpt.com, claude.ai) that all tenants can select from.

Handling Offline Scenarios

The SDK automatically queues events when offline:

// Events are automatically queued in IndexedDB when offline // They sync automatically when connection is restored // You can check connection status if (navigator.onLine) { console.log('Online - events sync in real-time'); } else { console.log('Offline - events will be queued'); }

Pre-flight Checks

Run hardware checks before starting:

const preflight = await Proctor.checkRequirements(); if (preflight.success) { // All good - proceed if (preflight.lighting === 'poor') { showMessage('Please improve your lighting'); } } else { // Handle missing requirements showRequirementsError(preflight); }

Camera framing preflight (what your users see)

In addition to the hardware check above, the SDK will by default run a camera framing preflight automatically when a proctoring session starts:

  • When it happens

    • After the GDPR/disclaimer is accepted.
    • After the session has been validated with the ProctorSafe backend.
    • Before continuous proctoring and violations begin.
  • What the test-taker sees

    • A “Camera setup” window over your exam.
    • Clear guidance to:
      • Center their face in the camera.
      • Make sure only they are visible.
      • Remove posters, photos, or screens showing other faces.
    • Live feedback:
      • If no face is visible → a warning message asking them to move into frame.
      • If multiple faces are visible → a warning asking them to ensure they are alone and remove extra faces from the background.
      • If the current frame looks good → a reassuring “framing looks good” state.
    • Once the SDK has seen a stable, valid framing for several consecutive moments, the window changes to:
      • “Framing validated” with a short “Starting your session…” message.
      • This success state stays on screen for about 2 seconds so the user clearly understands what’s happening.
  • What this means for violations

    • During camera preflight, no proctoring violations are emitted:
      • No MULTIPLE_FACES, FACE_MISSING, or gaze-related events are generated in this phase.
      • The goal is to help the user fix issues before the exam is considered started from a monitoring perspective.
    • Only after preflight has fully validated and the “Starting your session…” message clears does continuous monitoring begin and violations start to be recorded.
  • Configuration (optional)

    • Camera framing preflight is enabled by default for proctored sessions.
    • You can adjust its behavior when constructing the SDK settings (for example, in your proctoring bootstrap code) via settings.cameraPreflight, such as changing how long it waits before giving up.
    • For most users, we recommend keeping it enabled so candidates reliably start in a compliant camera setup and understand what is expected of them before the session begins.

Second Speaker Detection

ProctorSafe can detect when a voice that is significantly different from the exam candidate's own voice is heard during the session — for example, another person speaking in the room.

How It Works

During camera preflight, after the ambient noise baseline is set, the SDK runs a brief voice enrollment step: the candidate is prompted to speak aloud (say “aaaa” for 2–3 seconds, then read a full sentence). The SDK captures a log-mel spectral profile and, when enough clear voiced frames are available, a median fundamental frequency (F0).

For the rest of the session, when voice-like audio is detected, incoming audio is compared to the enrolled profile. A frame counts as divergent if smoothed mel similarity falls below secondSpeakerSimilarityThreshold or (when F0 was enrolled) estimated pitch differs by at least secondSpeakerPitchSemitoneThreshold semitones. If sustained divergence is detected, a SECOND_SPEAKER_SUSPECTED event is emitted.

This is a reviewer signal, not an automatic disqualification. It appears in the session timeline and contributes to the trust score so that human reviewers can investigate.

Limitations

  • Detection works for sequential speech (candidate silent, then a second person speaks). If both speak simultaneously, the mixed audio makes reliable attribution impossible.
  • Whispered speech below the microphone detection threshold will not be detected.
  • A voice profile is only captured if the candidate actively speaks during enrollment. If they do not speak, the feature is silently skipped and no SECOND_SPEAKER_SUSPECTED events will be generated for that session.
  • If enrollment does not yield enough reliable pitch estimates, only the mel branch is used until the next session (pitch gating is skipped).

Configuration

Voice enrollment is enabled automatically when camera preflight is enabled. You can adjust its behaviour via settings:

await Proctor.start({ tenantName: 'your-tenant-slug', applicationReference: 'exam-platform-2024', settings: { monitorVisibility: true, detectionInterval: 1000, enableOfflineSync: true, audioSensitivity: 0.5, // Voice enrollment duration (ms). Set to 0 to disable second-speaker detection. // Defaults to 5000 (5 seconds) — long enough for the candidate to say a full sentence. voiceProfileDurationMs: 5000, // Log-mel cosine similarity threshold. A speaking frame scoring below this // value is spectrally divergent. Range [0, 1]. Lower = more permissive. // Defaults to 0.30. secondSpeakerSimilarityThreshold: 0.3, // Optional. When enrollment captured median F0, also flag if current pitch // differs by at least this many semitones (1–12). Default 8. // secondSpeakerPitchSemitoneThreshold: 8, // Optional. Tuning knobs for second-speaker detection (defaults shown). // secondSpeakerCentroidMaxHz: 5000, // secondSpeakerMelAvgWindow: 7, // secondSpeakerNonPeakGraceTicks: 2, // audioMonitorIntervalMs: 1000, }, });

Handling the Event

(event) => { if (event.type === 'SECOND_SPEAKER_SUSPECTED') { // Log for reviewer attention — do not automatically penalise the candidate console.warn('Possible second speaker detected at', event.timestamp); } }

Multi-Monitor Detection

ProctorSafe can detect how many monitors a candidate has connected at session start using the browser's Window Management API. This helps reviewers flag sessions where a candidate may be using a secondary screen to view unauthorised material.

Browser Support

The Window Management API is supported in Chrome 100+ (and Chromium-based browsers). It is not available in Firefox or Safari. On unsupported browsers the feature is silently skipped — no error is thrown.

Tenant Administrator Setup

The feature has a four-state mode that your tenant administrator configures in the ProctorSafe dashboard under Settings → Features → Multi-Monitor Detection:

ModeBehaviour
disabled (default)Feature is off. The Window Management permission is never requested, regardless of the SDK config.
default_offFeature is off by default. Individual integrations can opt in via multiMonitorDetection: true.
default_onFeature is on by default. Individual integrations can opt out via multiMonitorDetection: false.
always_onFeature is always on. The SDK will always request Window Management permission regardless of the config value.

Configuration

When your tenant is set to default_off or default_on, you can control the feature per-session via settings.multiMonitorDetection:

await Proctor.start({ tenantName: 'your-tenant-slug', applicationReference: 'exam-platform-2024', settings: { monitorVisibility: true, detectionInterval: 1000, enableOfflineSync: true, audioSensitivity: 0.5, // Request Window Management permission to enumerate connected monitors. // Subject to tenant policy — see tenant administrator setup above. // Defaults to false. multiMonitorDetection: true, }, });

What Happens at Runtime

When enabled, the browser displays a "Allow this site to manage windows on all your screens?" permission dialog alongside the camera and microphone prompts at session start. If the candidate grants permission, the list of connected screens (label, width, height, whether primary, whether internal) is recorded in the session's client info payload sent to the server.

If the candidate denies the permission the SDK continues normally — the session is not blocked, but no screen list is recorded. Reviewers can see whether screen data was collected in the session details.

Session Recording (Mode 2)

ProctorSafe supports two operating modes:

  • Mode 1 (default): Events-only proctoring. No screen or webcam frames are stored anywhere. Full GDPR data-minimisation — the recommended default.
  • Mode 2 (opt-in): Sampled screen and webcam frames are encrypted client-side and optionally uploaded to the server alongside the event stream. Reviewers can scrub a synchronized recording alongside the event timeline.

Enabling Recording

Recording is controlled by a tenant-level setting (recordingMode) which your admin configures in the dashboard under Settings → Features. The available values are:

ModeDescription
disabledNo recording. Events-only (Mode 1).
on_deviceFrames are captured and encrypted client-side. The ReplayKey never leaves the browser during the session; reviewers can request on-demand access later. Highest privacy.
uploadEncrypted frames are uploaded to the server on a low-priority background channel throughout the session. Reviewer can play back a synchronized scrubber in the dashboard.

When recordingMode is upload, candidates are shown an additional consent screen before the session starts. If they decline, the session proceeds in Mode 1 (events-only). You cannot bypass this gate. Update your privacy policy and DPA before enabling upload mode.

Post-Session Upload Drain

After the session ends and the trust score is calculated, any remaining unuploaded frames are drained in the background. A progress indicator is shown to the candidate. Optionally, your admin can set a upload timeout (in seconds) after which a "Cancel upload" button appears, letting the candidate abort the drain without affecting their score.

Webcam privacy in screen recordings

When screen recording is enabled, the candidate's webcam preview overlay is automatically masked out of screen-share screenshots before they are encrypted and uploaded. The overlay region is replaced with an opaque black box, so reviewer screenshots never contain the webcam feed.

The webcam is still recorded separately on its own channel (captured directly from the camera stream, not from the screen), so reviewers have full access to the webcam recording independently.

No configuration is required — this masking is always active when recordingMode is not disabled.

SDK Configuration (per-session overrides)

You can pass optional recording hints via settings.recording. These are advisory — the effective mode is always dictated by the tenant configuration:

await Proctor.init('my-tenant', 'exam-ref-001', { settings: { recording: { screenFps: 1, // Default: 1 frame per second webcamFps: 0.5, // Default: 0.5 fps (webcam) quality: 0.6, // JPEG quality [0, 1]. Default: 0.6 }, }, });

Session Summary

After ending a session, you receive a complete summary:

const summary = await Proctor.end(); // summary contains: // - sessionId: Unique session identifier // - startTime: ISO timestamp // - endTime: ISO timestamp // - totalEvents: Number of events recorded // - timeline: Array of all events // - trustScore: Calculated trust score (0-100) // Submit to your backend await fetch('/api/exam/submit', { method: 'POST', body: JSON.stringify({ examId: currentExamId, proctorSessionId: summary.sessionId, trustScore: summary.trustScore, events: summary.timeline, }), });

Troubleshooting

Common Issues

"SDK not loaded" Error

Solution: Ensure the script tag is loaded before your code runs:

<script src="https://test.proctorsafe.eu/sdk/proctor.iife.js"></script> <script> // Your code here - Proctor is now available Proctor.start(/* ... */); </script>

Camera/Microphone Permission Denied

Solution: Guide users to enable permissions:

const requirements = await Proctor.checkRequirements(); if (requirements.camera === 'denied') { alert('Please enable camera access in your browser settings'); // Show instructions for your browser } if (requirements.microphone === 'denied') { alert('Please enable microphone access in your browser settings'); }

Events Not Appearing in Dashboard

Checklist:

  1. Verify applicationReference matches your configured value
  2. Check browser console for API errors
  3. Verify network connectivity (API endpoint is automatically detected from script origin)

Face Detection Not Working

Possible causes:

  1. Poor lighting - check preflight.lighting
  2. Camera not positioned correctly
  3. Face detection models not loaded (check console for errors)

Solution: Ensure good lighting and camera positioning. The SDK will show warnings if face detection fails.

CORS Error: Origin not allowed

Error message: "Origin not allowed" or "403 Forbidden" when starting a session

Solution:

  1. Your domain must be added to the tenant's allowed origins list
  2. Contact your tenant administrator to add your origin
  3. Supported formats:
    • Exact origin: https://yourdomain.com
    • Wildcard: *.yourdomain.com (matches all subdomains)
    • Port variations: http://localhost also matches http://localhost:3000
  4. After adding the origin, wait a moment and try again

Signature required but not provided

Error message: "Signature required but not provided" when starting a session

Solution:

  1. Your tenant has signature requirement enabled
  2. You must provide a signature and timestamp in the SDK config
  3. Two options:
    • Use your own private key: Generate a key pair, upload the public key to the tenant dashboard, and sign the payload yourself (see Method 1)
    • Use the create API helper: Call /api/proctor/create with your API key to get a signature (see Method 2)
  4. Include the signature and timestamp in your Proctor.start() config

Invalid signature

Error message: "Invalid signature" when starting a session

Solution:

  1. Verify the signature payload matches exactly (comma-separated format):

    tenant-name,application-reference,timestamp
    

    Example: my-tenant,exam-platform-2024,1704067200000

    The payload format is: $tenant,$reference,$timestamp (not JSON)

  2. Ensure you're using the correct public key:

    • If using a custom public key, verify it's uploaded correctly in the tenant dashboard
    • If using the create API helper, ensure you're using the correct tenant's API key
  3. Check that the timestamp is recent (not expired)

  4. Verify the signature is base64-encoded DER format

  5. Ensure the private key matches the public key configured in the tenant dashboard

  6. Make sure there are no extra spaces or newlines in the payload string

Automatic Diagnostics Reporting

To help us troubleshoot integration issues on your behalf, the SDK automatically reports session initialization failures (e.g. camera/microphone permission denials, consent declines, handshake errors) and uncaught client-side JavaScript errors to a lightweight diagnostics endpoint. This happens in the background via navigator.sendBeacon (or fetch as a fallback) and does not affect session behavior or add noticeable overhead.

Reported diagnostics include the failure stage, an error code/message, a short trail of recent init steps, and basic environment details (browser, URL) — no session recordings, captures, or candidate answers are ever included. This data is used solely to help us identify and fix integration problems faster; it is visible only to ProctorSafe support/operations, not to other tenants.

If you'd prefer to opt out, set enableDiagnostics: false in your Proctor.start() config:

await Proctor.start({ tenantName: 'your-tenant-slug', applicationReference: 'exam-2024-001', enableDiagnostics: false, // disable automatic diagnostics reporting settings: { /* ... */ }, });

For high-volume integrations, diagnosticsSampleRate (a number from 0 to 1, default 1) can be used to report only a fraction of events instead of disabling reporting entirely.

Getting Help

If you encounter issues:

  1. Check Browser Console: Look for error messages
  2. Review Dashboard: Check if events are being recorded
  3. Contact Support: support@proctorsafe.eu
  4. Documentation: https://test.proctorsafe.eu/docs

Complete Integration Example

Here's a complete example for an exam application:

<!DOCTYPE html> <html> <head> <title>Online Exam</title> </head> <body> <div id="exam-container"> <h1>Mathematics Exam</h1> <div id="questions"></div> <button id="submit-exam">Submit Exam</button> </div> <!-- Load ProctorSafe SDK (auto-loads all dependencies) --> <script src="https://test.proctorsafe.eu/sdk/proctor.iife.js"></script> <script> let currentQuestion = 1; const totalQuestions = 10; // Configuration - replace with your actual values const TENANT_NAME = 'your-tenant-slug'; const API_KEY = 'pk_your-api-key-here'; // Only needed if using create API helper async function generateSignature(tenantName, applicationReference) { // Generate signature using the create API helper // In production, you may want to do this server-side // The API endpoint is automatically detected from where the SDK is loaded try { const response = await fetch('https://test.proctorsafe.eu/api/proctor/create', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${API_KEY}`, }, body: JSON.stringify({ tenant_name: tenantName, application_reference: applicationReference, }), }); if (!response.ok) { const error = await response.json(); throw new Error(error.error || 'Failed to generate signature'); } return await response.json(); } catch (error) { console.error('Error generating signature:', error); throw error; } } async function startExam() { // Check requirements const requirements = await Proctor.checkRequirements(); if (!requirements.success) { alert('Please enable camera and microphone access'); return; } // Generate signature for session initiation let signature, timestamp; try { const sigData = await generateSignature(TENANT_NAME, 'math-exam-2024'); signature = sigData.signature; timestamp = sigData.timestamp; } catch (error) { alert('Failed to initialize session. Please try again.'); console.error('Signature generation failed:', error); return; } // Start proctoring session // Proctor.start() returns the session ID - store it for later reference let proctorSessionId; try { proctorSessionId = await Proctor.start( { tenantName: TENANT_NAME, applicationReference: 'math-exam-2024', signature: signature, timestamp: timestamp, settings: { hudMode: 'full', // Controls proctoring overlay. Note: This replaces the deprecated 'showPreview' parameter. monitorVisibility: true, detectionInterval: 1000, // Minimum: 500ms enableOfflineSync: true, audioSensitivity: 0.5, }, }, (event) => { // Handle violations if (event.type === 'TAB_BLUR') { alert('Warning: You switched away from the exam'); } if (event.type === 'FACE_MISSING') { alert('Warning: Please ensure your face is visible'); } } ); // Store session ID in your system (e.g., send to your backend) console.log('Proctoring session started:', proctorSessionId); // Optionally send to your backend to link exam with proctoring session // await fetch('/api/exam/start', { // method: 'POST', // body: JSON.stringify({ examId: 'math-2024-001', proctorSessionId }), // }); } catch (error) { // Handle CORS or signature validation errors if (error.message.includes('CORS') || error.message.includes('Origin not allowed')) { alert('Error: Your domain is not authorized. Please contact your administrator.'); } else if (error.message.includes('signature') || error.message.includes('Signature')) { alert('Error: Signature validation failed. Please try again.'); } else { alert('Failed to start proctoring session: ' + error.message); } console.error('Session start failed:', error); return; } // Mark exam start Proctor.mark('EXAM_STARTED', { examId: 'math-2024-001', totalQuestions: totalQuestions, }); loadQuestion(1); } function answerQuestion(questionId, answer) { Proctor.mark('QUESTION_ANSWERED', { questionId: questionId, answer: answer, }); if (currentQuestion < totalQuestions) { currentQuestion++; loadQuestion(currentQuestion); } else { completeExam(); } } async function completeExam() { // Mark exam complete Proctor.mark('EXAM_COMPLETED', { totalQuestions: totalQuestions, }); // End proctoring session const summary = await Proctor.end(); // Submit to your backend await fetch('/api/exam/submit', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ examId: 'math-2024-001', proctorSessionId: summary.sessionId, trustScore: summary.trustScore, }), }); alert('Exam submitted! Your trust score: ' + summary.trustScore); window.location.href = '/exam-complete'; } // Start exam when page loads window.addEventListener('load', startExam); // Handle submit button document.getElementById('submit-exam').addEventListener('click', completeExam); </script> </body> </html>

Next Steps

  1. Test Integration: Use the demo page at https://test.proctorsafe.eu/demo
  2. Access Dashboard: Log in to view your sessions
  3. Customize: Adjust settings based on your exam requirements

Roadmap

This section describes planned features that are not yet available in the current ProctorSafe release. Timelines and details may change.

Webhook Delivery (Planned)

In a future version, ProctorSafe will support server-side webhooks so you can receive proctoring events directly in your own backend.

Planned design:

  • Source of truth: Events are still sent from the SDK to the ProctorSafe backend and stored in our database.
  • Webhook fan-out: After persistence, our backend will optionally forward events to one or more customer-configured webhook URLs.
  • Configuration:
    • Webhook URL(s) per tenant / application
    • Optional shared secret for HMAC signatures
    • Ability to choose which event types to receive
  • Delivery model:
    • HTTP POST with JSON payload containing session ID, event details, and application reference
    • Retries on transient failures (e.g. network issues)
    • Signature header for integrity and authenticity (e.g. x-proctor-signature)

Example webhook payload (planned):

{ "sessionId": "clx123abc456", "event": { "id": "evt_789xyz", "timestamp": "2024-12-18T22:30:00.000Z", "source": "SYSTEM", "type": "FACE_MISSING", "metadata": {}, "confidence": 0.95 }, "applicationReference": "exam-platform-2024" }

Example webhook receiver (planned):

const express = require('express'); const app = express(); app.use(express.json()); // Webhook endpoint app.post('/webhooks/proctor', async (req, res) => { const { sessionId, event, applicationReference } = req.body; // Verify webhook signature (if configured) // const signature = req.headers['x-proctor-signature']; // verifySignature(signature, req.body); // Process the event console.log(`Event received for session ${sessionId}:`, event); // Store in your database await storeEvent(sessionId, event); // Take action based on event type if (event.type === 'TAB_BLUR') { // Log violation, notify admin, etc. await logViolation(sessionId, event); } // Always return 200 to acknowledge receipt res.status(200).json({ received: true }); }); app.listen(3000);

Example webhook signature verification (planned):

const crypto = require('crypto'); function verifySignature(signature, payload, secret) { const expectedSignature = crypto .createHmac('sha256', secret) .update(JSON.stringify(payload)) .digest('hex'); return crypto.timingSafeEqual( Buffer.from(signature), Buffer.from(expectedSignature) ); }

Important:

  • Webhooks will be in addition to the existing ProctorSafe API and database.
  • The dashboard will continue to work even if webhooks are misconfigured or temporarily failing.
  • Until this ships, you should rely on:
    • The client callback for real-time reactions, and
    • The ProctorSafe dashboard / API for persisted event data.

Data Export (Planned)

In a future version, ProctorSafe will support exporting session data from the dashboard.

Planned features:

  • Export formats: CSV and JSON
  • Filtered exports: Export only sessions matching current filters
  • Export contents:
    • Session metadata (ID, application reference, status, duration, trust score)
    • Complete event timeline (all system and custom events)
    • Event metadata and timestamps
  • Bulk export: Export multiple sessions at once
  • Scheduled exports: (Future consideration) Automated periodic exports

Until this ships, you can access session data via:

  • The dashboard UI for viewing sessions
  • The ProctorSafe API for programmatic access to session data

Support