S
Scholé
Integration API
How it works
1 — Outbox
Every domain event is written atomically to the database in the same transaction as the triggering change — no event is ever lost due to a crash between the write and the delivery.
2 — HTTP delivery
A background worker POSTs the event JSON to every active WebhookEndpoint registered for your organization, with an HMAC-SHA256 signature so you can verify authenticity.
3 — Polling alternative
Prefer to pull rather than receive? Every event is also available from GET /api/v1/events/, so you can poll on your own schedule with no public endpoint to host.
Setting up an endpoint

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.

Send your Scholé admin
  • The HTTPS URL events should be POSTed to.
  • The event types you want — or “all events”.
Scholé sends back
  • The endpoint's signing secret.
  • Store it securely — you'll use it to verify the X-Schole-Signature header on every delivery.
For the Scholé admin
In Django admin under Webhooks → Webhook endpoints, add a record: pick the customer's account, paste the URL, and set the event types (leave empty to send all). The signing secret is generated automatically — copy it from the saved record and share it with the partner over a secure channel.
Event payload

Every delivery shares the same envelope regardless of event type.

json
{
  "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"
  }
}
Signature verification
Every HTTP request includes an 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é.
Delivery headers

Every POST to your endpoint carries these headers.

HeaderExamplePurpose
Content-Typeapplication/jsonThe body is always UTF-8 JSON.
X-Schole-Eventlesson.completedThe event type — route on this without parsing the body.
X-Schole-Signaturesha256=<hex>HMAC-SHA256 of the raw body, keyed with your endpoint secret.
X-Schole-Delivery018e9f3a-cf12-7000-…The event UUID (same as the body's id). Use it to process idempotently.
Event catalogue

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
Full event examples

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.

json
{
  "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.

json
{
  "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.

json
{
  "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).

json
{
  "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).

json
{
  "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.

json
{
  "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
  }
}
Event stream — REST polling

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.

1
First request — fetch from the beginning
bash
curl -s "https://app.schole.ai/api/v1/events/" \
  -H "Authorization: Bearer $TOKEN"
2
Response — store next_after
json
{
  "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"
}
3
Subsequent requests — pass next_after
bash
curl -s "https://app.schole.ai/api/v1/events/?after=1742300000000-0&limit=100" \
  -H "Authorization: Bearer $TOKEN"
If 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.
ParameterDefaultDescription
after0 (beginning)Return events strictly after this cursor (the next_after / stream_id from a prior response)
limit100Events per page, max 500
Reliability & retries
PropertyValue
Max delivery attempts5
HTTP timeout per attempt10 s
Success conditionHTTP 2xx response from your endpoint
Failure handlingTask is re-queued by the worker; after 5 failures the event is marked failed and logged
OrderingBest-effort — events for the same company are ordered by creation time but concurrent workers may deliver out of order under high load
DeduplicationEach event has a stable UUID (id field). Your endpoint should be idempotent on that ID.