Guide

Webhooks

Register an HTTPS endpoint and Klasstack sends it a signed POST when something happens in the school. Deliveries are retried, logged and can be replayed.

Registering an endpoint

  • Add an endpoint to the app in the Developer section and pick the events it receives. An app can have up to ten endpoints.
  • The URL must be HTTPS on a public address. Private and internal addresses are refused.
  • Each endpoint has its own signing secret, starting whsec_, shown when you create or rotate it.
  • Changing an endpoint URL later needs an administrator to approve, because it redirects school data.
  • Send test event delivers a sample of any event, marked with the header Klasstack-Test-Event: true, so you can build the receiver before real traffic.

The payload

json
{
  "id": "evt_0123456789abcdef0123456789abcdef",
  "type": "student.created",
  "occurred_at": "2026-09-01T08:00:00.000Z",
  "api_version": "v1",
  "data": { "id": 812 }
}

data is shaped exactly as the matching read route returns it, so an event never carries a field the API would not. The reference lists every event schema.

Verifying the signature

Every delivery carries Klasstack-Signature: t=<unix seconds>,v1=<hex>. v1 is HMAC-SHA256, keyed with the endpoint whsec_ secret, over the timestamp, a full stop and the raw request body. Compute it yourself, compare in constant time, and reject a timestamp more than 300 seconds old.

The check, step by step
# header: Klasstack-Signature: t=1788249600,v1=5f2b...
signed_payload="1788249600.$raw_body"
expected=$(printf '%s' "$signed_payload" | openssl dgst -sha256 -hmac "$WHSEC_SECRET" -hex | cut -d' ' -f2)
# accept only if expected equals v1 and the timestamp is within 300 seconds

Retries

  • Any 2xx answer acknowledges the event. Answer quickly and do the work afterwards: a delivery times out after 10 seconds.
  • A timeout, a network error, 408, 429 or any 5xx is retried with exponential backoff and jitter, starting at about 30 seconds and capped at 4 hours between attempts, for up to 10 attempts.
  • A Retry-After header on your 429 or 503 is honoured, up to one hour.
  • Any other status, including a redirect, is not retried.
  • Deliveries can arrive out of order and more than once. Dedupe on id: a retry or a replay sends the same id.

Delivery log and replay

The Developer section keeps a log of every delivery with its attempts and your answers. Replay any delivery from there once your endpoint is fixed. A replay carries the original event id.

Every event

An endpoint receives an event only while the app holds the read scope listed next to it. This table is generated from the API reference.

EventScope neededWhen it fires
student.createdstudents:readA student joined the roll: enrolled, or an applicant admitted.
student.updatedstudents:readAn on-roll student's profile changed.
student.withdrawnstudents:readA student left the roll (withdrawn, graduated or expelled). Carries the id only.
guardian.linkedguardians:readA guardian was linked to an on-roll student.
class.enrolment_changedstudents:readAn on-roll student moved to another class or arm.
attendance.markedattendance:readAttendance was marked or changed. One event per register write.
result.publishedresults:readA subject's results were published for a term.
assignment.createdassignments:readAn assignment was published to its class.
timetable.updatedtimetable:readA class timetable was published.
payment.succeededfees:readAn online fee payment settled through the school provider. Never sent for a manual payment.
fee_account.updatedfees:readA settled online payment changed an on-roll student's fee account. Account totals only, no items.
withdrawal.approvedmoneyOut:readA platform admin approved a queued withdrawal; the payout is in flight.
withdrawal.rejectedmoneyOut:readA queued withdrawal was declined by a platform admin or at the end of the approval window; funds returned.
withdrawal.cancelledmoneyOut:readThe school cancelled a withdrawal still awaiting approval; funds returned.
withdrawal.completedmoneyOut:readThe provider confirmed a withdrawal's payout.
withdrawal.failedmoneyOut:readThe provider failed a withdrawal's payout; funds returned.
withdrawal.reversedmoneyOut:readThe provider returned a completed withdrawal's payout; funds credited back.