LearnexaFor your ICT team

For your ICT team: how Learnexa is built.

This is the technical deep-dive — tenancy by schoolId, permission-composed roles, JWT with HTTP-only cookies, SchoolPay integration internals, and audit logs that distinguish platform context from school context. Written for the person your head teacher asks to check us out.

Not the ICT person? The plain-language version of everything here is on the platform page.

01 / Tenancy

Every request is scoped by schoolId. No exceptions.

Data is tenant-isolated at the model layer, not just at the UI layer. One school cannot see another's data. The platform operator stays out of school-scoped records unless explicitly invited.

LAYER 01
Edge & Transport
HTTPS only. Helmet headers, HPP, XSS-clean. Redis-backed rate limiting.
Enforced
LAYER 02
Auth · JWT + HTTP-only cookie
Access & refresh tokens, password OTP recovery, logout-all, session protection.
Enforced
LAYER 03
School context
Every request resolves a schoolId from the user's session and is scoped to it.
Enforced
LAYER 04
System role · SUPER_ADMIN / USER
Determines platform-level reach. Most users never see this layer.
Enforced
LAYER 05
Composed school role
Built from a permission catalog. School Admin, Bursar, Teacher, Registrar — and anything in between.
Enforced
LAYER 06
Permission guard
Every API route checks a granular permission before the operation executes.
Enforced
LAYER 07
Audit log
Action, actor, school context and platform context — recorded for replay.
Enforced
02 / Roles & permissions

Familiar role names. Custom access. No custom development.

A platform permission catalog is seeded at startup. Your school composes roles from it. Bursars stay bursars — but what your bursar can actually do is configured per school.

  • One staff member can hold multiple composed roles
  • System roles are protected and clearly separated
  • Permission guards fire on every API route, every module
Bursar
Composed · 14 permissions · 2 users
Sample school
Permission
View
Create
Edit
Delete
finance.invoices
finance.schoolpay
students.profile
exams.marks
payroll.runs
03 / SchoolPay integration internals

Three ingest paths, one idempotent transaction store.

On the buyer-facing pages we just say "payments post themselves." Here is what that actually means: webhook delivery as the primary path, polling as the reconciliation net, and an ad-hoc queue so unresolvable payments surface instead of disappearing.

INGEST 01
Webhook receiver
SchoolPay's server-to-server callback posts each transaction as it happens. Signature-checked, idempotent — a replayed delivery can't double-post.
Live
INGEST 02
Polling sync
A scheduled reconciliation pass pulls anything the webhook missed (downtime, network partitions). Same idempotency keys, same transaction store.
Live
INGEST 03
Ad-hoc callback
Payments made without a resolvable student reference land in an unmatched queue for manual resolution — nothing is silently dropped.
Live
STORAGE
Encrypted credentials
Each school's SchoolPay API credentials are encrypted at rest, never round-tripped to the UI, never written to logs.
Live
TRAIL
Source tracking
Every transaction records its ingest source (webhook · poll · adhoc), timestamp and resolution history on the audit trail.
Live
04 / Audit

Every action, replayable.

Audit logs distinguish platform-level events from school-level events. Who, what, when, in which school — all recorded. No silent state changes.

  • Actor, action, target & school context on every event
  • Archived (not deleted) users preserve marks & finance history
  • Closed academic years can be locked for compliance
10:42:18
jkato
marks.update · S.3-C · Math · TermEnd
10:41:02
bursar
invoice.allocate · INV-202602-0418 · UGX 240,000
10:40:55
schoolpay
tx.posted · TX-4F9A2C1 · webhook
10:38:11
head
report.publish · S.3-C · Term 2 · 2026
10:37:46
bursar
tx.resolve · TX-229001E → Okello, J.
10:35:09
ict
role.update · "Bursar" · +payroll.runs.edit
10:32:14
admin
year.lock · 2025 · finance
10:30:00
system
login.refresh · cookie · jkato@st-teresa
Engineering principles

The non-negotiables we won't trade for speed.

P-01

Tenancy never leaks

If a request can't resolve a school context, it doesn't run. Cross-tenant joins are physically prevented at the query layer.

P-02

Permissions are explicit

Every API route names the permission it requires. No silent escalation. No “admins can do anything by default.”

P-03

Data has a lifecycle

Users archive, not delete. Years lock, not vanish. Finance records survive staff turnover and audit season.

P-04

Audit precedes feature

If an action mutates school-scoped data, it carries an audit log entry. New module ships with logs from day one.

P-05

Credentials never appear in app

SchoolPay API passwords and similar secrets are encrypted at rest, never round-tripped to the UI, never logged.

P-06

You own your school's data

Structured exports, clear access policies, and the right to walk away with a copy of your records.

Security review

Bring your ICT team. We'll show our work.

Walk through tenancy, permission composition, audit logging, and credential handling with our team — slide-free, just the actual code paths and data models.