Skip to main content
Each delivery is a JSON object with a common envelope. The event-specific fields live under data.

Common envelope

FieldTypeDescription
idstringUnique delivery ID, e.g. dlv_01abc…. Same value as the X-Quippy-Delivery-Id header. Use it to dedupe on retries.
typestringThe event type — e.g. exam.completed. Same as the X-Quippy-Event header.
createdISO 8601 stringTimestamp of this delivery attempt.
institutionIdstringYour institution’s ID. Handy if you run one receiver for many tenants.
testboolean (optional)Present and true only for synthetic test deliveries.
dataobjectEvent-specific payload — see below.

Events

{
  "id": "dlv_01abc...",
  "type": "exam.started",
  "created": "2026-04-21T09:05:12.456Z",
  "institutionId": "inst_acme",
  "data": {
    "examId": "exm_01...",
    "sessionId": "ses_01...",
    "studentId": "usr_01...",
    "studentEmail": "jane@acme.com",
    "attempt": 1,
    "startedAt": "2026-04-21T09:05:12.012Z"
  }
}
Fires once per new proctoring session, immediately after the session document is persisted. studentId may be null for guest / link-based exam modes. attempt is 1-indexed — the student’s nth attempt at this exam. Great for driving real-time attendance dashboards.
{
  "id": "dlv_01abc...",
  "type": "exam.completed",
  "created": "2026-04-20T10:15:30.123Z",
  "institutionId": "inst_acme",
  "data": {
    "examId": "exm_01...",
    "sessionId": "ses_01...",
    "studentId": "usr_01...",
    "score": {
      "percentage": 87,
      "totalScore": 87,
      "maxScore": 100,
      "correct": 26,
      "total": 30
    },
    "submittedAt": "2026-04-20T10:15:29.998Z"
  }
}
Fires once per submission, after the session is scored. studentId may be null for guest / link-based exam modes.
{
  "id": "dlv_01abc...",
  "type": "violation.detected",
  "created": "2026-04-21T09:12:05.789Z",
  "institutionId": "inst_acme",
  "data": {
    "examId": "exm_01...",
    "sessionId": "ses_01...",
    "studentId": "usr_01...",
    "violationId": "vio_01...",
    "type": "multiple_faces",
    "severity": "critical",
    "message": "Multiple faces detected in frame",
    "riskScore": 30,
    "totalViolations": 2,
    "recordingTimestamp": 342,
    "recordingTimeFormatted": "00:05:42",
    "detectedAt": "2026-04-21T09:12:05.701Z"
  }
}
Only critical violations fire this event. Warnings (e.g. one quick head turn) are too noisy to push to customer systems by default — they’re visible inside Quippy’s own dashboard but don’t emit.
Fields:
FieldMeaning
typeThe violation classifier — multiple_faces, face_not_visible, tab_switch, copy_paste, etc.
severityAlways "critical" for webhook deliveries.
riskScoreCumulative session risk score (0–100), recalculated on each violation.
totalViolationsCount of all violations on this session so far, critical + warning combined.
recordingTimestampSeconds from recording start — useful to link directly into the playback. null if no recording.
recordingTimeFormattedSame timestamp as HH:MM:SS.
{
  "id": "dlv_01abc...",
  "type": "grading.done",
  "created": "2026-04-21T09:20:44.321Z",
  "institutionId": "inst_acme",
  "data": {
    "assessmentId": "asm_01...",
    "userId": "usr_01...",
    "assessmentType": "cefr-speaking",
    "sessionId": "ses_01...",
    "assignmentId": null,
    "score": {
      "overall": 82,
      "pronunciation": 78,
      "fluency": 85,
      "accuracy": 80,
      "content": 84,
      "cefrLevel": "B2"
    },
    "language": "en-US",
    "completedAt": "2026-04-21T09:20:44.111Z"
  }
}
Fires once per new graded assessment across all grading entry points — `POST /api/grading/grade-open-ended`, `grade-pronunciation`, `grade-writing`, `cefr/speaking`, `ielts/speaking`, `ielts/writing`, `hskk/grade-speaking`, `dels-speaking/grade`, `dels-writing/grade`.score fields you’ll see depend on assessmentType:
Assessment typeScore fields populated
speaking, open-ended, mixedoverall, pronunciation, fluency, accuracy
cefr-speaking, cefr-writingoverall, cefrLevel (A1–C2)
ielts-speaking, ielts-writing-task1, ielts-writing-task2overall, IELTS band in overall (0–9 scale)
hskk_speakingoverall, pronunciation, fluency
writtenoverall, content, grammar (under score.*)
Unpopulated score fields are null — don’t key off their presence.
Teacher edits to an existing assessment (reviewing, overriding a score) do not re-fire grading.done. The event is strictly “first grade was persisted.”
{
  "id": "dlv_01abc...",
  "type": "user.provisioned",
  "created": "2026-04-20T10:15:30.123Z",
  "institutionId": "inst_acme",
  "data": {
    "userId": "usr_01...",
    "email": "jane@acme.com",
    "displayName": "Jane Doe",
    "role": "member",
    "accessLevel": 2,
    "provisionedAt": "2026-04-20T10:15:29.998Z",
    "via": "sso"
  }
}
Fires when a new user is added to the institution. The via field tells you how it happened:
viaFires on
"invite" (or omitted)Admin invites a user via POST /api/v2/users/invite or the Members page
"sso"A user authenticates via SSO for the first time (JIT provisioning) or a soft-deleted user is reactivated through SSO
"scim"The customer’s IdP pushes a new user via POST /api/scim/v2/Users
role is one of admin, manager, editor, member, viewer. accessLevel is the numeric equivalent (5 / 4 / 3 / 2 / 1).
{
  "id": "dlv_01abc...",
  "type": "user.deprovisioned",
  "created": "2026-04-20T10:15:30.123Z",
  "institutionId": "inst_acme",
  "data": {
    "userId": "usr_01...",
    "email": "jane@acme.com",
    "displayName": "Jane Doe",
    "deactivatedAt": "2026-04-20T10:15:29.998Z",
    "via": "scim"
  }
}
Fires on soft-delete. via tells you the trigger:
viaFires on
"admin" (or omitted)An admin removes the user from the Members page
"scim"The customer’s IdP deactivates the user (active: false PATCH or DELETE /api/scim/v2/Users/:id)
Pair with user.provisioned to drive user lifecycle syncs in external HRIS, CRM, or SIEM systems.
{
  "id": "dlv_01abc...",
  "type": "subscription.updated",
  "created": "2026-04-20T10:15:30.123Z",
  "institutionId": "inst_acme",
  "data": {
    "userId": "usr_01...",
    "stripeSubscriptionId": "sub_1Nx...",
    "status": "active",
    "cancelAtPeriodEnd": false,
    "currentPeriodEnd": "2026-05-20T10:15:29.000Z",
    "planId": "plan_pro_monthly"
  }
}
Fires when Stripe tells Quippy a subscription has changed — active, past_due, canceled, etc. planId may be null if the plan isn’t recorded on our side.
{
  "id": "dlv_01abc...",
  "type": "webhook.test",
  "created": "2026-04-20T10:15:30.123Z",
  "test": true,
  "institutionId": "inst_acme",
  "data": {
    "message": "This is a synthetic test delivery from Quippy.",
    "at": "2026-04-20T10:15:30.000Z"
  }
}
Fires exactly once when you click Send test on an endpoint’s detail page. Test deliveries are not retried on failure — they’re a one-shot check.

Delivering to multiple endpoints

If you subscribe two endpoints to the same event, each gets its own delivery row with a distinct id — they don’t share attempts or retry state.