Skip to main content

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.