Webhook endpoints
Available events:exam_participant.registered— a participant was registered for an examexam_participant.unregistered— a participant’s registration was cancelledexam_result.completed— a participant finished a testexam_result.scored— a test score was published or changedquestion_import.completed— a question package import finishedquestion_import.failed— a question package import failed
When events are sent
exam_participant.registeredis sent for registrations made through the API and in the admin panel. Participants who register themselves through the exam registration page don’t trigger this event.exam_participant.unregisteredis sent when a registration is cancelled through the API.exam_result.completedandexam_result.scoredcome from a scan of exam results that runs every minute, so an event may arrive about 1–2 minutes after the participant finishes.exam_result.completedmay carryscore: nullfor tests scored with IRT or manually.exam_result.scoredfollows once the score has been calculated.exam_result.scoredis sent again every timescoreormeta_scorechanges, for example after an IRT recalculation or a score recalculation. The same score is never sent twice.question_import.completedandquestion_import.failedare sent when a question package import through the Question Import API finishes or fails.- Events are not guaranteed to arrive in order. Use
updated_atfrom the Exam Results API to determine the latest data, and resync regularly withGET /exam-results?updated_since=...so that missed events are still handled.
GET /webhooks — List webhook endpoints
GET /webhooks — List webhook endpoints
POST /webhooks — Register a webhook endpoint
POST /webhooks — Register a webhook endpoint
PATCH /webhooks/{id} — Update a webhook endpoint
PATCH /webhooks/{id} — Update a webhook endpoint
GET /webhooks/{id}/deliveries — Webhook delivery history
GET /webhooks/{id}/deliveries — Webhook delivery history
POST /webhooks/{id}/test — Send a test webhook
POST /webhooks/{id}/test — Send a test webhook
If
event is not sent or not recognised, the default event is exam_result.completed. A test delivery uses the selected event name and the same signature as a real delivery, but data contains a test message: { "message": "...", "sent_at": "..." }. An inactive endpoint is rejected with status 409.Parameters
Example request
Example response
Webhook payloads and signatures
Every webhook is sent as aPOST with a JSON body containing event, created_at (when the event was queued) and data. The same body is resent unchanged on every retry.
Headers
exam_result.completed and exam_result.scored payload
Both events use the same data structure. The contents of meta_score for each scoring method are described in the meta_score structure section of the Exam Results API.
exam_participant.registered payload
exam_participant.unregistered payload
Verify the signature
v1 is the HMAC-SHA256 of <t>.<raw body> using the endpoint’s signing secret. The t value is regenerated on every delivery attempt. Reject requests whose signature doesn’t match or whose t differs from your server time by more than 300 seconds.
Retries
- Respond to the webhook with a
2xxstatus within 10 seconds. Store the event first, then process it in the background. - Non-
2xxresponses, timeouts and connection failures are retried up to 4 attempts in total, with delays of 1 minute, 5 minutes, then 30 minutes. If the last attempt fails, the delivery status becomesfailed. - An endpoint that fails 10 times in a row is disabled automatically (
is_disabled_by_system: true), and the project owner is notified in the admin panel. Once the endpoint is fixed, re-enable it withPATCH /webhooks/{id}containingis_active: true. - Deliveries that are already
failedare not resent. UseGET /exam-results?updated_since=...to fetch the results you missed.

