Documentation Index
Fetch the complete documentation index at: https://docs.hawkings.education/llms.txt
Use this file to discover all available pages before exploring further.
If you already have students in your LMS or your CRM, you don’t need to
re-enroll them in Hawkings by hand. Pick the strategy that matches how
much sync you need.
Option A: One-time bulk import
For a single migration:
import { parse } from "csv-parse/sync";
import { readFileSync } from "node:fs";
const rows = parse(readFileSync("students.csv"), { columns: true });
await hk.cohorts.bulk({
rows: rows.map(r => ({
cohort_id: "coh_123",
student_email: r.email,
student_name: r.full_name,
external_id: r.canvas_user_id, // your LMS' id, stored as metadata
})),
});
bulk is idempotent on (cohort_id, student_email) — re-running won’t
duplicate.
Option B: Daily reconcile
Run a cron job that pulls your LMS’ roster and applies a diff:
import { Hawkings } from "@hawkings/sdk";
import { Canvas } from "@canvas/sdk";
const hk = new Hawkings();
const canvas = new Canvas(/* ... */);
async function reconcile(cohort_id: string, canvas_course_id: string) {
const [hkStudents, lmsStudents] = await Promise.all([
hk.students.list({ cohort_id: cohortId }).then(p => p.data),
canvas.users.list({ course_id: canvasCourseId, role: "student" }),
]);
const lmsByEmail = new Map(lmsStudents.map(s => [s.email, s]));
const hkByEmail = new Map(hkStudents.map(s => [s.email, s]));
const toAdd = lmsStudents.filter(s => !hkByEmail.has(s.email));
const toRemove = hkStudents.filter(s => !lmsByEmail.has(s.email));
await hk.cohorts.bulk({
rows: toAdd.map(s => ({ cohort_id: cohortId, student_email: s.email, student_name: s.name })),
});
for (const s of toRemove) {
await hk.cohorts.unenroll(cohortId, s.id);
}
}
We recommend running this nightly. Reconciliation is cheap; the only
billable side effect is the AI generation kicked off when a brand-new
student joins.
Option C: Real-time webhooks (LMS → Hawkings)
If your LMS supports outbound webhooks, point them at your backend and
forward to Hawkings:
// Express handler for Canvas' enrollment webhook
app.post("/webhooks/canvas/enrollment", async (req, res) => {
const { event, user, course } = req.body;
const cohortId = await mapCanvasCourseToCohort(course.id);
if (event === "enrollment.created") {
await hk.cohorts.bulk({
rows: [{
cohort_id: cohortId,
student_email: user.email,
student_name: user.name,
external_id: user.id,
}],
});
}
if (event === "enrollment.deleted") {
await hk.cohorts.unenroll(cohortId, user.email);
}
res.sendStatus(200);
});
Option D: First-class Moodle bridge
If you’re on Moodle, install the Hawkings plugin. It handles enrollment,
grade-passback, and SCORM in one shot. See the Moodle integration guide.
Storing external IDs
Always pass external_id (or a metadata object) when creating users
or cohorts. It lets you:
const sub = await hk.submissions.retrieve(submissionId);
console.log(sub.student.metadata.canvas_user_id);
// → push the grade back to Canvas with their own user id
Pitfalls
Email is not a good primary key. Students change emails. Always
also pass an external_id from your LMS — that’s what survives email
changes.
Don’t sync passwords. Hawkings doesn’t need them — students log in
through your token flow (Authentication),
not through Hawkings’ own login form.