Skip to content

Webhooks

AIR delivers events to your endpoint as signed HTTP POSTs. Verify the HMAC signature on the raw body before trusting the payload — the SDKs do this for you.

Event types include overseer.alert, conversation.opened, conversation.ended, the air.job.* lifecycle (started, image.generating, completed, failed), and quiz.assignment.completed.

  • JS: @air/sdk-js — handleWebhook({ rawBody, signingSecret, signatureHeader })
  • Python: air-sdk — handle_webhook(raw_body=..., signing_secret=..., signature_header=...)

Both return the verified, typed event (or a structured rejection). Always verify against the raw request body, before any JSON parsing/re-serialisation.

quiz.assignment.completed

Fired when a quiz you launched has been answered. Scores and ids only — never a question or an answer. It is sent only for your own participants: an organisation giving a set to its own staff on the same team is never reported to you.

If any answer was waiting for a person to mark (needs_review above 0, passed null), the event is sent again for the same assignment_id once the last of them is marked, with the settled numbers. Treat the later one as the result.

Your door only receives it if it is subscribed to quiz.assignment.completed. A door with a webhook URL but no subscription to this kind receives nothing.

{
  "event": "quiz.assignment.completed",
  "event_id": "01H8…",
  "occurred_at": "2026-09-25T10:14:02.118Z",
  "delivery_attempt": 1,
  "tenant_id": "943d…",
  "team_id": "632d…",
  "data": {
    "conversation_id": "cdf9…",
    "assignment_id": "91c4…",
    "session_id": "7e21…",
    "set_id": "5b0e…",
    "set_name": "Fire safety refresher",
    "stakes": "practice",
    "subject_kind": "person",
    "contact_id": "a2a0…",
    "total": 10,
    "correct": 8,
    "needs_review": 0,
    "score": 0.8,
    "passed": null
  }
}
Field Type Notes
conversation_id uuid The thread the quiz ran in
assignment_id uuid Matches assignment_id from the launch response
session_id uuid The sitting itself
set_id uuid The set that was answered
set_name string The set's name
stakes enum compliance | practice | play
subject_kind string person for a launch
contact_id uuid Your own handle for the participant you launched it for
total int Questions in the sitting
correct int How many were right
needs_review int Answers still waiting for a person to mark
score number 0–1
passed bool | null null when the set has no pass mark, or while an answer is still waiting for a person

Dedupe on event_id (also sent as X-AIR-Event-Id) — it is the same across retries of one event.

SDK support

quiz.assignment.completed is newer than the SDKs' typed event union. Handle it as an unknown kind until the SDK ships it — the signature is verified either way.