← Proctoring Documentation

Webhooks and events

Use webhooks for live evidence and completion summaries. Use the REST read endpoints as a pull fallback.

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.

Inbound lifecycle events

Integration Stop signal
LTI Proctoring Services POST /lti/end_assessment with LtiEndAssessment
REST POST /proctoring/api/v1/webhooks/exam/status with event_type: "assessment_ended"

assessment_finalized marks the session finalized after completion. It is a partner/platform lifecycle marker, not proof that every recording was delivered.

Last verified: 2026-09-03 against main.