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.