← Proctoring Documentation

Webhooks and events

Use webhooks for live evidence and completion summaries.

Outbound events

Event When it sends Payload summary
violation.detected After a violation is saved One violation and the Endpoint session UUID.
recording.uploaded After a recording uploads to your S3 bucket One partner_s3 recording.
exam.completed After the session reaches completed and no recording upload is pending Session summary, all violations, deliverable recordings, and upload errors if any.

To receive events, configure a webhook URL and webhook secret on your tenant. Event filtering is tenant-level; an empty event list means all events are enabled.

Transport

POST <your webhook URL>
Content-Type: application/json
X-Webhook-Signature: <64 hex chars>
X-Webhook-Timestamp: <unix seconds>
User-Agent: EndpointProctoring/1.0

Any 2xx response is success. Current delivery does not guarantee redelivery for all failure types. Return 2xx quickly, store the signed payload or event_id, and process asynchronously.

Every payload has a top-level event_id. Use it as your deduplication key.

Webhook signatures

Endpoint signs the exact JSON body it sends with:

HMAC-SHA256(webhook_secret, json_body)

The header value is lower-case hex with no sha256= or v1= prefix. Verify against the raw request body bytes before parsing JSON. The timestamp header is not part of the signed value; use it only for your own replay window.

Ruby example:

expected = OpenSSL::HMAC.hexdigest("SHA256", secret, request.raw_post)
valid = ActiveSupport::SecurityUtils.secure_compare(expected, request.headers["X-Webhook-Signature"].to_s)

Node example:

const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
const valid = Buffer.byteLength(expected) === Buffer.byteLength(signature || '') &&
  crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature || ''));

Payloads

violation.detected

{
  "event": "violation.detected",
  "event_id": "5c3e1f8a-2b6d-57e2-9f1a-6d0c2e7b4a91",
  "timestamp": "2026-09-10T14:41:03Z",
  "session_id": "9d2b6c31-1f45-4a80-b7de-5c1e0a3f9b22",
  "violation": {
    "id": 4412,
    "type": "tab_switch",
    "severity": "medium",
    "description": "Switched away from the exam tab",
    "occurred_at": "2026-09-10T14:41:03Z",
    "auto_detected": true
  }
}

recording.uploaded

{
  "event": "recording.uploaded",
  "event_id": "9b1deb4d-3b7d-5bad-9bdd-2b0d7b3dcb6d",
  "timestamp": "2026-09-10T15:30:44Z",
  "session_id": "9d2b6c31-1f45-4a80-b7de-5c1e0a3f9b22",
  "recording": {
    "id": 771,
    "type": "camera",
    "url": "s3://your-bucket/proctoring/9d2b6c31/camera.webm",
    "storage_provider": "partner_s3",
    "duration_seconds": 5189,
    "file_size_bytes": 104857600,
    "uploaded_at": "2026-09-10T15:30:44Z"
  }
}

exam.completed

{
  "event": "exam.completed",
  "event_id": "0a128f02-a18b-5558-9df7-058104ed5272",
  "timestamp": "2026-09-10T15:31:12Z",
  "session": {
    "id": "9d2b6c31-1f45-4a80-b7de-5c1e0a3f9b22",
    "exam_id": "exam-204",
    "reservation_uuid": "3f6a1d92-7c40-4b1e-9a55-0e2f8b4c1d77",
    "candidate_email": "ada@example.com",
    "candidate_name": "Ada Lovelace",
    "exam_name": "Network Security Final",
    "course_name": "Network Security",
    "started_at": "2026-09-10T14:02:11Z",
    "ended_at": "2026-09-10T15:28:40Z",
    "duration_minutes": 86,
    "compliance_score": 90
  },
  "violations": [
    {
      "id": 4412,
      "type": "tab_switch",
      "severity": "medium",
      "description": "Switched away from the exam tab",
      "occurred_at": "2026-09-10T14:41:03Z"
    }
  ],
  "recordings": [
    {
      "type": "camera",
      "url": "s3://your-bucket/proctoring/9d2b6c31/camera.webm",
      "storage_provider": "partner_s3",
      "duration_seconds": 5189,
      "file_size_bytes": 104857600
    }
  ],
  "recording_errors": [
    {
      "type": "screen",
      "error": "AccessDenied",
      "failed_at": "2026-09-10T15:30:02Z"
    }
  ]
}

duration_minutes is elapsed exam time, not scheduled length. recording_errors is omitted when there are no upload errors.

Deliverable recordings

exam.completed includes only recordings with storage_provider partner_s3 or local_only.

Provider Meaning
partner_s3 Uploaded to your bucket. The URL is an s3:// URI; fetch it with your AWS credentials.
local_only Endpoint is holding the file because no partner S3 bucket was configured. The URL is a signed HTTPS URL valid for 7 days.
failed Not listed in recordings; represented in recording_errors when an upload error was recorded.
skipped Not listed in recordings. Screen upload is skipped unless explicitly enabled.

Current attempt recordings use camera and screen; room-scan video may appear as room_scan_video. Do not reject a payload solely because a future recording type is unfamiliar. Check-in still images are not delivered by webhook in the current flow.

Violation catalogue

Severity is always low, medium, high, or critical. Treat type as an open catalogue: handle unknown future types generically by severity.

Type Typical severity
tab_switch, window_blur, alt_tab_attempt medium
copy_attempt, cut_attempt, paste_attempt, excessive_selection low to high
right_click, drag_attempt, form_submission_blocked low
print_attempt, print_shortcut, screenshot_attempt medium to high
screen_capture_attempt, screen_share_stopped, devtools_detected critical
devtools_shortcut, console_accessed, camera_access_denied, screen_share_denied high
screen_share_muted medium
navigation_attempt, navigation_blocked, navigation_violation low to high
multiple_displays_detected medium
iframe_injection, suspicious_dom_injection, spoofed_end_signal medium to high
no_person_visible, multiple_people critical
phone_visible, ai_detected_violation high
looking_away, suspicious_object medium
improper_lighting, background_activity low

test_violation is used during staging and is deleted before the real exam starts.

Completion timing

For LTI Proctoring Services sessions, Endpoint sends exam.completed after it receives LtiEndAssessment, the session reaches completed, and no recording upload is pending.

Last verified: 2026-09-03 against main.