Webhooks & Events
Subscribe to platform events and receive signed HTTP notifications the moment they happen — or poll them on your own schedule.
WebhookEndpoint registered for your organization, with an
HMAC-SHA256 signature so you can verify authenticity.GET /api/v1/events/, so you can poll on your own
schedule with no public endpoint to host.Endpoints are provisioned by a Scholé admin — there's no self-serve registration yet. Setup is one short exchange between you and your Scholé contact.
- The HTTPS URL events should be POSTed to.
- The event types you want — or “all events”.
- The endpoint's signing secret.
- Store it securely — you'll use it to verify the
X-Schole-Signatureheader on every delivery.
Every delivery shares the same envelope regardless of event type.
{
"id": "018e9f3a-cf12-7000-a1b2-3c4d5e6f7a8b",
"event": "lesson.completed",
"created_at": "2026-03-18T14:23:01.456789+00:00",
"data": {
"curriculum_id": 42,
"user_id": 7,
"template_id": 3,
"title": "Introduction to Machine Learning"
}
}
X-Schole-Signature: sha256=<hex>
header. Compute HMAC-SHA256(secret, raw_body) over the raw request
body and compare to verify the delivery came from Scholé.
Every POST to your endpoint carries these headers.
| Header | Example | Purpose |
|---|---|---|
Content-Type | application/json | The body is always UTF-8 JSON. |
X-Schole-Event | lesson.completed | The event type — route on this without parsing the body. |
X-Schole-Signature | sha256=<hex> | HMAC-SHA256 of the raw body, keyed with your endpoint secret. |
X-Schole-Delivery | 018e9f3a-cf12-7000-… | The event UUID (same as the body's id). Use it to process idempotently. |
Register a WebhookEndpoint with an empty event_types list
to receive all events, or filter to a specific subset.
User events
| Event | When it fires | Key payload fields |
|---|---|---|
user.created |
A new user profile is provisioned (via API, bulk import, or signup) | user_id, email, first_name, last_name, access_role, language |
user.updated |
An existing user profile is saved with changed fields | user_id, email, first_name, last_name, access_role, language |
Subscription events
| Event | When it fires | Key payload fields |
|---|---|---|
subscription.created |
A new organization subscription is activated | organization_subscription_id, organization_id, status, billing_interval |
subscription.updated |
Status or billing fields change | organization_subscription_id, organization_id, status, billing_interval |
subscription.canceled |
Subscription status transitions to canceled |
organization_subscription_id, organization_id, status |
Learning events
| Event | When it fires | Key payload fields |
|---|---|---|
lesson.started |
A user begins a lesson (curriculum instance created) | curriculum_id, user_id, template_id, title |
lesson.completed |
A user reaches the last subsection of a lesson | curriculum_id, user_id, template_id, title |
lesson.feedback_submitted |
A user submits a star rating after completing a lesson | feedback_id, curriculum_id, user_id, rating |
assignment.created |
A lesson template is assigned to a user | user_id, template_id |
assignment.deleted |
A lesson assignment is removed from a user | user_id, template_id |
Document events
| Event | When it fires | Key payload fields |
|---|---|---|
document.processed |
A document finishes processing and is indexed successfully | document_id, filename, status, error |
document.failed |
Document processing fails after all retries | document_id, filename, status, error |
Complete delivered bodies for the events most integrations build on. Every event uses
the same envelope — only the data object changes per type.
user.created
A learner has been provisioned — your cue to link them to a local record.
{
"id": "018e9f3a-cf12-7000-a1b2-3c4d5e6f7a8b",
"event": "user.created",
"created_at": "2026-03-18T14:23:01.456789+00:00",
"data": {
"user_id": 7,
"email": "ada@hagen.edu",
"first_name": "Ada",
"last_name": "Lovelace",
"access_role": "learner",
"language": "en"
}
}
lesson.started
A learner opened a lesson; a curriculum instance was created for them.
{
"id": "018e9f40-1a22-7000-bc3d-4e5f6a7b8c9d",
"event": "lesson.started",
"created_at": "2026-03-18T14:30:11.012345+00:00",
"data": {
"curriculum_id": 42,
"user_id": 7,
"template_id": 3,
"title": "Introduction to Machine Learning"
}
}
lesson.completed
The learner reached the final subsection. Match on template_id to update your own progress and (if applicable) billing.
{
"id": "018e9f55-7b33-7000-cd4e-5f6a7b8c9d0e",
"event": "lesson.completed",
"created_at": "2026-03-18T15:02:48.778901+00:00",
"data": {
"curriculum_id": 42,
"user_id": 7,
"template_id": 3,
"title": "Introduction to Machine Learning"
}
}
lesson.feedback_submitted
The learner rated the lesson. rating is an integer (1–5).
{
"id": "018e9f56-9c44-7000-de5f-6a7b8c9d0e1f",
"event": "lesson.feedback_submitted",
"created_at": "2026-03-18T15:03:20.114002+00:00",
"data": {
"feedback_id": 88,
"curriculum_id": 42,
"user_id": 7,
"rating": 5
}
}
assignment.created
A lesson template was assigned to a learner (one event per learner/template pair).
{
"id": "018e9f60-ad55-7000-ef60-7b8c9d0e1f2a",
"event": "assignment.created",
"created_at": "2026-03-18T16:10:05.660400+00:00",
"data": {
"user_id": 7,
"template_id": 3
}
}
document.processed
An uploaded document finished indexing and is ready to use. On failure you receive document.failed with a non-null error.
{
"id": "018e9f72-be66-7000-f071-8c9d0e1f2a3b",
"event": "document.processed",
"created_at": "2026-03-18T17:45:33.220100+00:00",
"data": {
"document_id": "6f1c2d3e-4a5b-6c7d-8e9f-0a1b2c3d4e5f",
"filename": "syllabus.pdf",
"status": "indexed",
"error": null
}
}
An alternative to HTTP webhooks: poll GET /api/v1/events/ on your own
schedule and receive a page of events since the last ID you saw. No persistent
connection required — any HTTP client works.
curl -s "https://app.schole.ai/api/v1/events/" \
-H "Authorization: Bearer $TOKEN"
next_after{
"events": [
{
"stream_id": "1742300000000-0",
"id": "018e9f3a-cf12-7000-a1b2-3c4d5e6f7a8b",
"event": "lesson.completed",
"created_at": "2026-03-18T14:23:01.456789+00:00",
"data": { "curriculum_id": 42, "user_id": 7, "title": "Intro to ML" }
}
],
"count": 1,
"has_more": false,
"next_after": "1742300000000-0"
}
next_aftercurl -s "https://app.schole.ai/api/v1/events/?after=1742300000000-0&limit=100" \
-H "Authorization: Bearer $TOKEN"
events is empty you are caught up — wait before polling again.
If has_more is true there are more pages; call again immediately
with the returned next_after.
| Parameter | Default | Description |
|---|---|---|
after | 0 (beginning) | Return events strictly after this cursor (the next_after / stream_id from a prior response) |
limit | 100 | Events per page, max 500 |
| Property | Value |
|---|---|
| Max delivery attempts | 5 |
| HTTP timeout per attempt | 10 s |
| Success condition | HTTP 2xx response from your endpoint |
| Failure handling | Task is re-queued by the worker; after 5 failures the event is marked failed and logged |
| Ordering | Best-effort — events for the same company are ordered by creation time but concurrent workers may deliver out of order under high load |
| Deduplication | Each event has a stable UUID (id field). Your endpoint should be idempotent on that ID. |