Training Platform - LMS
A bespoke compliance training platform for the staff of around 250 home-improvement and renewables retailers, with a full machine API for partner integration. FCA-grade audit, signed webhooks, OIDC federation, all built on Next.js and AWS in eu-west-2. The platform is designed to be the single source of truth that partner front-ends, regulators, and our own compliance team agree on.
A click-through of the full lifecycle with synthetic data: retailer onboarding, partner provisioning, webhooks, learner sign-in, certificate issue, lapse and override. No real data is used.
Why this exists
Shermin is the broker between a panel of UK lenders and around 250 home-improvement and renewables retailers. The FCA expects every member of retailer staff dealing with regulated finance to be trained, and the firm to hold evidence of that training that survives audit. Until now, the evidence story was a patchwork of spreadsheets, training providers' export CSVs, and goodwill.
The platform replaces all of that with one auditable system, integrated into the broker front-end so the retailer's staff complete training inside the same portal they already use. It carries the FCA evidence, it pushes lifecycle events to the partner integration, and it blocks a learner at the broker login if their compliance lapses.
I built this end to end. Architecture, every line of code, the API guide, the internal handbook, the operational runbooks. All my work. The brief I set myself was to ship something that a regulator could audit on a Tuesday morning and not find anything to argue about.
What it does
A learning surface for retailer staff. MDX-authored courses with embedded SCORM 2004 packages, a quiz at the end, and a signed PDF certificate at the end of the quiz. The certificate carries a QR code linking to a public verification page, so a regulator with a paper copy can confirm it is real without a Shermin account.
An allocation engine that scales. Allocation rules pick courses for a learner based on their role at the retailer. New starters arrive pre-enrolled. Role changes recompute the matrix. Old enrolments are superseded rather than deleted, so training history is never lost.
A machine API for the partner integration. OAuth 2.0 client_credentials, scoped tokens, signed webhooks. The partner provisions users, polls compliance status, subscribes to lifecycle events, and reconciles from the audit log when something has gone wrong on either side.
An expiry and lapse pipeline. Most certificates carry a 12-month validity. Reminders fire at 60, 30, 14 and 7 days. If a learner lapses, the broker front-end blocks them at session check until they complete the course. Compliance can grant a time-bound override for genuine exceptions; every grant, revoke and expiry is audited.
An audit log that survives compromise. Every state change writes a row inside the same database transaction as the business write. A daily export lands in S3 under Object Lock COMPLIANCE, so even an AWS root account cannot delete an event inside the retention window.
Architecture
One Next.js 16 app on Vercel, one Postgres on RDS, one S3 audit bucket on Object Lock. The platform is small enough not to want a microservice fleet and big enough to need real transactions. The whole synchronous surface (UI, API, cron entry points) is colocated; the data layer is in eu-west-2, in a private VPC, with Secrets Manager rotating the database password every 30 days.
Three independent auth paths into one application. Shermin staff sign in via Auth.js magic-link (no passwords, no MFA orchestration, the email account is the second factor). Retailer learners sign in via OIDC federation through Cognito, originating in the broker front-end's iframe; the platform never sees their password. Partner machine traffic uses OAuth 2.0 client_credentials with a 30-minute HS256 JWT, scoped per client, revocable live.
Audit is fail-hard for FCA evidence, fail-soft for telemetry. The fail-hard path runs the audit insert inside the same database transaction as the business write; if the audit fails, the business operation rolls back. The fail-soft path is fire-and-forget for things like sign-in events where the audit is operationally useful but not regulator evidence. Two helpers in one file; every route picks the right one.
Webhooks via the outbox pattern. Outgoing events are written to an outbox table inside the same transaction as the business write, so a state change either lands with its event queued or doesn't land at all. A cron dispatcher drains the outbox every minute with FOR UPDATE SKIP LOCKED, signs each delivery with HMAC-SHA256, and retries on a seven-step curve out to 72 hours before dead-lettering.
SSRF guard on webhook subscribe. Subscription URLs go through a multi-layer check: https only, port 443 or 8443, every resolved A record must be a public IP. The multi-A exhaustive check defeats the round-robin DNS smuggling attack where an attacker returns one public address and one internal one.
Multi-tenant by retailer organisation. Every learner-bearing query joins to retailer_organisation_id, whether through the table directly or through one of its parents. There is no row in the learner-side tables that doesn't trace back to a specific retailer.
Compliance-grade pseudonymisation. A GDPR Article 17 erasure replaces the personal fields, releases the email back to the unique index, and freezes the user. The FCA-evidence-bearing fields stay so the audit story holds; the personal identifiers are gone. Irreversible by design.
Who it's for
The single largest audience by headcount. Around 2,200 staff across 250 retailers. They never see a Shermin domain; they click "Training" inside the broker portal they already use, complete a course, and download a certificate. The platform's job is to be invisible to them most of the time and clear when it isn't.
The compliance team grant overrides, redeliver webhooks, run pseudonymisations, and read the audit log. They are the population the regulator interviews if anything goes wrong. The platform is built to put evidence in their hands, not in mine.
Not a user. An audience for the artefacts the platform produces. The audit log, the S3 export under Object Lock, the certificate verification page. Designed so a regulator can confirm, on their own, that the training evidence is real and has not been tampered with.
Stack
Documentation surface
Three audiences, three layers of documentation, all checked into the same monorepo as the code, all written by me.
Partner-facing API guide. An Astro Starlight site with a reference for every endpoint plus concept pages for error recovery, idempotency, rate-limit strategy, audit trail, and the security model.
Internal handbook. Eight chapters covering how the platform works end-to-end, written for the team and any future engineer joining the project. Lives in docs/handbook/.
Operational runbooks. Click-by-click procedures for the things that come up rarely enough to need notes: RDS password rotation (Secrets Manager auto-rotates faster than we can update Vercel), database migrations under failure, DNS cutover, AWS keys staged-swap.
Status
Production. Cutover seed of the existing ~2,200 retailer staff is paced through the bulk endpoint at the documented 7-second cadence. The partner integration is live.
The most recent change (May 2026) closed the SSRF gap on webhook subscribe and fixed dead-letter redelivery to reset attempts so the redeliver button is actually useful. The full April 2026 code review is closed. The only public-facing item left is the DNS cutover from *.vercel.app to training.staxpay.co.uk.