Integration · Reference
Webhooks
Send signed learner and notification events to your systems.
Webhooks queue learner and notification events for delivery to your application. They are the preferred alternative to repeatedly polling the API for changes.
Create a Webhook#
- Open Admin > Webhooks.
- Create a webhook and enter a public endpoint URL.
- Add an optional description.
- Select the events to receive, or leave the event list empty to receive all events.
- Add custom request headers if your endpoint requires them.
- Enable the webhook and save it.
- Copy the signing secret and store it securely.
An organization can configure up to five webhooks. Disable a webhook to pause new deliveries without deleting its configuration or logs.
Event Types#
Kokobi supports learner progress events and the events used by course and collection notifications.
| Event | Sent when |
|---|---|
learner.started | A learner starts an activity. |
learner.updated | A learner's saved attempt changes. |
learner.completed | An attempt becomes terminal: completed, passed, or failed. |
course.invite | A learner is invited to a course. |
course.request | A learner requests access to a course. |
course.request.accepted | A course access request is accepted. |
course.request.rejected | A course access request is rejected. |
course.completion | A course completion notification is produced. |
course.inactive | An inactive-course notification is produced. |
collection.invite | A learner is invited to a collection. |
collection.request | A learner requests access to a collection. |
collection.request.accepted | A collection access request is accepted. |
collection.request.rejected | A collection access request is rejected. |
collection.completion | A collection completion notification is produced. |
Selecting specific events limits deliveries to that list. An empty event selection means all current events, including event types added later.
Request Format#
Kokobi sends an HTTP POST request with a JSON body:
{ "event": "learner.completed", "data": {}}The shape of data depends on the event:
- Learner events contain the learner, resource connection, latest attempt, module, status, score, and SCORM runtime data when available.
- Course and collection notification events contain recipient addresses and compact event-specific data, usually resource or learner identifiers.
Inspect the delivery JSON in Admin > Webhooks > Webhook > Logs while developing an integration. Treat added fields as backward-compatible and ignore properties your handler does not use.
Request Headers#
Every delivery includes:
Content-Type: application/jsonwebhook-timestamp: 2026-07-24T12:00:00.000Zwebhook-signature: <hex-encoded-signature>Kokobi also includes the custom headers configured on the webhook.
Do not configure custom headers named Content-Type, webhook-timestamp, or webhook-signature; those names are reserved for delivery and signature verification.
Verify Signatures#
The webhook-signature value is an HMAC-SHA256 digest of the timestamp and the exact request body:
HMAC-SHA256(secret, timestamp + "." + raw_request_body)Always verify the signature before parsing or processing the event. Use the raw bytes received over HTTP; serializing a parsed object again can produce different bytes.
Node.js and Express Example#
import { createHmac, timingSafeEqual } from "node:crypto";import express from "express";
const app = express();
app.post( "/kokobi-webhook", express.raw({ type: "application/json" }), (request, response) => { const timestamp = request.get("webhook-timestamp"); const signature = request.get("webhook-signature"); const body = request.body;
if (!timestamp || !signature || !Buffer.isBuffer(body)) { return response.sendStatus(400); }
const expected = createHmac("sha256", process.env.KOKOBI_WEBHOOK_SECRET) .update(timestamp + ".") .update(body) .digest(); const provided = Buffer.from(signature, "hex"); const valid = provided.length === expected.length && timingSafeEqual(provided, expected);
if (!valid) return response.sendStatus(401);
const payload = JSON.parse(body.toString("utf8")); response.sendStatus(204);
// Process payload.event and payload.data asynchronously. queueEvent(payload); },);Also reject timestamps outside a short tolerance window to reduce replay risk. Keep the signing secret in a secret manager and never expose it in client-side code.
Delivery and Retries#
A delivery succeeds when the endpoint returns an HTTP status from 200 through 299. Network failures and non-2xx responses remain pending and are retried automatically with increasing delays.
For reliable handling:
- Verify the signature.
- Persist or enqueue the event.
- Return a 2xx response quickly.
- Process slow work outside the request.
- Make processing idempotent because retries can produce duplicate requests.
Kokobi records pending, successful, and failed deliveries and retries within a bounded schedule. Completed records become eligible for cleanup after seven days.
Logs and Troubleshooting#
Open a webhook to review its overview and logs. Each delivery records the event, payload, status, retry count, next retry time, creation time, and completion time.
Deliveries remain pending#
- Confirm the endpoint is public and uses HTTPS in production.
- Confirm it accepts
POSTrequests with a JSON content type. - Return a 2xx response after safely recording the event.
- Check firewall, proxy, and application logs for rejected requests.
Signature verification fails#
- Use the signing secret from the same webhook that sent the delivery.
- Compute the digest from
timestamp + "." + raw bodywith no extra newline. - Read the timestamp and signature from the exact header names above.
- Confirm no middleware parsed or modified the body before verification.
Events are missing#
- Confirm the webhook is enabled.
- Review its selected event list; an empty list receives all events.
- Confirm the action occurred in the same organization as the webhook.
- Check delivery logs before investigating the receiving application.