Skip to main content

What is a webhook event?

Think of it like a notification your server receives after something happens on the SmartAI platform. There are two types of events your server will receive:
EventWhen it fires
assessment.sentA recruiter sends an assessment to candidates (webhook mode)
assessment.completedA candidate submits the exam
Both come from client.getWebhookEvents() in the same queue. This page covers the assessment.completed event in detail. For assessment.sent see What Gets Stored in Redis.
When a candidate finishes (or times out on) an assessment, SmartAI puts a message in a queue. Your server picks it up by calling client.getWebhookEvents(). That message is called a webhook event.
Candidate submits assessment

SmartAI stores the result in a queue

Your server calls getWebhookEvents() every 30 seconds

You receive an array of events like this:

[
  {
    eventId: "evt_01J9XYZABC",
    candidateName: "Priya Sharma",
    score: 78,
    passed: true,
    ...
  }
]

You save it to your database

You call acknowledgeWebhookEvents(["evt_01J9XYZABC"])

Done ✓

Quick reference — every field at a glance

FieldTypeWhat it tells you
eventIdstringUnique ID for this event — use as your DB primary key
eventstringAlways "assessment.completed"
assessmentIdstringWhich assessment template was used
candidateIdstringSmartAI’s internal ID for the candidate
candidateNamestringCandidate’s full name
candidateEmailstringCandidate’s email — use to match them in your DB
assessmentNamestringName of the assessment
jobTitlestringRole the assessment was for
scorenumberHow many marks the candidate got
totalMarksnumberTotal marks available (always 100)
passMarksnumberMinimum marks needed to pass
passedbooleantrue if score ≥ passMarks
statusstringcompleted / timeout / abandoned
submittedAtstringWhen the candidate submitted
durationMinutesnumberHow long they took (in minutes)
aiFeedbackstringAI-written summary of their performance
reportUrlstringLink to the full report page
skillsstring[]Skills tested in this assessment
questionAnswersarrayEvery question + the candidate’s answer
verification.imageUrlstringSelfie taken at the start (identity check)
proctoringobjectCheating/integrity data
recording.urlstringVideo recording of the session
storedAtstringWhen SmartAI put this in the queue

Field-by-field breakdown

Identity fields

eventId
string
required
A unique ID that SmartAI generates for every event.Why it matters: Use this as your database primary key. If the same event gets delivered twice (which can happen if your server crashes mid-save), an upsert on eventId will prevent duplicate records.
"evt_01J9XYZABC"
event
string
required
The type of event. Right now this is always "assessment.completed" — every event means a candidate finished (or was timed out from) an assessment.
assessmentId
string
required
The ID of the assessment template that was used. One template can be used for many candidates, so this is not unique per candidate.
candidateId
string
SmartAI’s internal ID for this candidate. Not the same as your own user ID — use candidateEmail to match the candidate back to your database.

Candidate info

candidateName
string
The candidate’s full name, exactly as you passed it when creating the session.In your code: c.name ?? c.fullName ?? ''
candidateEmail
string
required
The candidate’s email address. This is the most important field for matching — use it to find the person in your own user table.In your code: c.email ?? ''

Assessment info

assessmentName
string
The display name of the assessment template, e.g. "Full Stack Developer Assessment".
jobTitle
string
The job role this assessment was created for, e.g. "Senior Software Engineer".
skills
string[]
The skills that were tested. Example: ["JavaScript", "React", "Node.js"].

Result fields

These four fields are what you’ll use most often — they tell you the outcome.
score
number
required
The number of marks the candidate scored. Always between 0 and totalMarks.Example: 78
totalMarks
number
required
The total marks available. Always 100.
passMarks
number
required
The minimum score needed to pass. Configured when the assessment was created.Example: 60
passed
boolean
required
true if score >= passMarks, false otherwise.This is pre-calculated for you — you don’t need to compare score and passMarks yourself.
// Using it in code
if (event.passed) {
  sendOfferEmail(event.candidateEmail);
} else {
  sendRejectionEmail(event.candidateEmail);
}
status
string
required
How the assessment ended.
ValueWhat it meansDid we get a score?
completedCandidate submitted normally✅ Yes
timeoutTime ran out — auto-submitted✅ Yes (partial)
abandonedCandidate closed without submitting❌ No score

Timing fields

submittedAt
string
required
The exact date and time the candidate submitted, in ISO-8601 format.Example: "2026-06-09T10:30:00.000Z"To display it in your UI: new Date(event.submittedAt).toLocaleString()
durationMinutes
number | null
How many minutes the candidate spent on the assessment.Example: 45 means they finished in 45 minutes. null means timing wasn’t tracked for this session.
storedAt
string
When SmartAI put this event in the queue (slightly after submittedAt). Usually a few seconds difference.

AI feedback & report

aiFeedback
string | null
A short paragraph written by AI summarising how the candidate performed.Example: "Strong in algorithms; needs improvement in system design."This is null if AI feedback is not enabled for this assessment template.
reportUrl
string
A direct URL to the full candidate report page on the SmartAI platform. Share this link with your recruiting team so they can review the detailed breakdown.Example: "https://platform.smartai.app/reports/evt_01J9XYZABC"

questionAnswers — per-question breakdown

This is an array with one entry per question. Use it if you want to show recruiters which specific questions the candidate got right or wrong.
questionAnswers
array

verification — identity check

At the start of every assessment, the candidate takes a selfie. This confirms the right person is taking the test.
verification
object

proctoring — integrity monitoring

The AI watches the candidate’s webcam throughout the test and flags suspicious behaviour. This object summarises what was detected.
proctoring
object

recording — session video

The entire assessment session is recorded (webcam + screen). This is the strongest evidence when reviewing a suspicious session.
recording
object

How to use this data in your code

Here’s the pattern your backend should use — based on your actual implementation:
// Run this every 30 seconds on your server
async function pollAssessmentResults() {
  const client = new AssessmentClient({
    apiKey:    process.env.ASSESSMENT_API_KEY,
    secretKey: process.env.ASSESSMENT_SECRET_KEY,
  });

  const { events } = await client.getWebhookEvents({ limit: 50 });

  for (const event of events) {
    // 1. event.candidateEmail  → find this person in your user table
    // 2. event.passed          → did they pass?
    // 3. event.score           → what was their score?
    // 4. event.proctoring.score → were they trustworthy?
    // 5. event.reportUrl       → share this link with your recruiter
    // 6. event.recording.url   → watch if you need to investigate

    await db.assessmentResults.upsert({
      where:  { eventId: event.eventId },   // ← prevents duplicates
      create: {
        eventId:        event.eventId,
        candidateEmail: event.candidateEmail,
        candidateName:  event.candidateName,
        score:          event.score,
        totalMarks:     event.totalMarks,
        passed:         event.passed,
        status:         event.status,
        proctoringScore:event.proctoring?.score ?? 100,
        reportUrl:      event.reportUrl,
        submittedAt:    new Date(event.submittedAt),
      },
      update: {}, // don't overwrite if already saved
    });
  }

  // Remove processed events from the queue
  if (events.length) {
    await client.acknowledgeWebhookEvents(events.map(e => e.eventId));
  }
}

setInterval(pollAssessmentResults, 30_000);
pollAssessmentResults(); // run once immediately on startup

Complete example event

This is exactly what one event object looks like:
{
  "eventId": "evt_01J9XYZABC",
  "event": "assessment.completed",
  "assessmentId": "asmt_xyz789",
  "candidateId": "cand_abc123",
  "candidateName": "Priya Sharma",
  "candidateEmail": "priya@example.com",
  "assessmentName": "Full Stack Developer Assessment",
  "jobTitle": "Senior Software Engineer",
  "score": 78,
  "totalMarks": 100,
  "passMarks": 60,
  "passed": true,
  "status": "completed",
  "submittedAt": "2026-06-09T10:30:00.000Z",
  "durationMinutes": 45,
  "aiFeedback": "Strong in algorithms; needs improvement in system design.",
  "reportUrl": "https://platform.smartai.app/reports/evt_01J9XYZABC",
  "skills": ["JavaScript", "React", "Node.js"],
  "questionAnswers": [
    {
      "questionId": "q_abc123",
      "type": "MCQ",
      "question": "What is the time complexity of binary search?",
      "options": ["O(n)", "O(log n)", "O(n²)", "O(1)"],
      "correctAnswer": "O(log n)",
      "marks": 5,
      "difficulty": "MEDIUM",
      "candidateAnswer": "O(log n)",
      "isCorrect": true,
      "marksAwarded": 5,
      "timeSpent": 45,
      "order": 1
    },
    {
      "questionId": "q_coding_01",
      "type": "CODING",
      "question": "Write a function to reverse a linked list.",
      "options": [],
      "correctAnswer": "See test cases",
      "marks": 20,
      "difficulty": "HARD",
      "candidateAnswer": "function reverseList(head) { ... }",
      "isCorrect": true,
      "marksAwarded": 20,
      "timeSpent": 420,
      "order": 2
    }
  ],
  "verification": {
    "imageUrl": "https://cdn.smartai.app/selfies/cand_abc123.jpg"
  },
  "proctoring": {
    "score": 88,
    "violationCount": 2,
    "tabSwitchCount": 1,
    "fullscreenExitCount": 0,
    "noFaceCount": 1,
    "multipleFaceCount": 0,
    "lookawayCount": 0,
    "externalObjectCount": 0,
    "recentViolations": [
      {
        "type": "TAB_SWITCH",
        "severity": "LOW",
        "timestamp": "2026-06-09T10:05:00.000Z"
      },
      {
        "type": "NO_FACE",
        "severity": "LOW",
        "timestamp": "2026-06-09T10:12:30.000Z"
      }
    ]
  },
  "recording": {
    "status": "available",
    "url": "https://cdn.smartai.app/recordings/evt_01J9XYZABC.mp4"
  },
  "storedAt": "2026-06-09T10:31:00.000Z"
}