Webhook events
Outbound events. When an exam event happens, Next Exams sends an HTTP POST to each endpoint subscribed to it, so another system can react without polling.
A webhook says something happened; the REST API reads the full record. A gradebook typically listens for attempt.graded, then fetches the attempt if it needs every response.
Configured in Next Exams → Webhooks.
Delivered as POST with Content-Type: application/json.
Envelope
{
"event": "attempt.graded",
"timestamp": "2026-10-18T12:51:30+00:00",
"site": "https://your-site.example",
"data": []
}
| Field | Type | Meaning |
|---|---|---|
event |
string |
The event name. The same as the X-NextExams-Event header. |
timestamp |
string |
ISO 8601 with offset, when the event was sent. Unchanged on a retry. |
site |
string |
The sending site's root address, so one receiver can serve several sites. |
data |
object |
The event's own fields. |
Signing
Each delivery carries X-NextExams-Signature in the form sha256=<hex>, computed with HMAC-SHA256.
The signature covers the The raw request body, exactly as received..
- Every delivery is signed with the endpoint's own secret. An endpoint URL is not a secret — without verification, anyone who learns it can post invented results to your integration.
- Compute the HMAC over the bytes as received: re-encoding the parsed JSON changes key order and whitespace, and the check will fail. Compare with a constant-time function.
- Regenerating a secret takes effect on the next delivery, so update the receiver first.
X-NextExams-Deliveryis the delivery's id and is the same on every retry of it — dedupe on it. A retry resends the identical body, timestamp included.
Verify a Next Exams signature in PHP
$body = file_get_contents('php://input');
$expected = 'sha256=' . hash_hmac('sha256', $body, $secret);
$given = $_SERVER['HTTP_X_NEXTEXAMS_SIGNATURE'] ?? '';
if (!hash_equals($expected, $given)) {
http_response_code(401);
exit;
}
$event = json_decode($body, true);
Delivery
Retries: 4.
Next Exams → Webhooks lists each endpoint's recent deliveries with the status code and any error — the first place to look when an integration "didn't get" something.
- Any 2xx answer is a success. Anything else, a timeout (8 seconds), or a redirect is a failure.
- Failed deliveries are retried 10, 20, 30 and 40 minutes apart, then marked failed after five attempts in all. Retries are sent by the Webhooks scheduled task.
- Be idempotent. Dedupe on
X-NextExams-Delivery; a retry carries the same id. - Respond quickly. Acknowledge receipt and do the slow work asynchronously.
- The endpoint must be public. Next Exams refuses loopback, private, reserved and link-local addresses, at send time as well as save time, and does not follow redirects.
- Ignore fields you do not recognise. New fields may be added to payloads.
An endpoint receives only the events it subscribes to. An endpoint with no events ticked receives nothing.
Events
| Event | When |
|---|---|
attempt.submitted |
An attempt is handed in |
attempt.graded |
An attempt's grade is final |
certificate.issued |
A certificate is issued |
ping |
You press Send test |