Skip to main content
SCORM is the universal lingua franca for “I have a course; my customer has an LMS that isn’t mine.” Hawkings ships SCORM 1.2 and 2004 zips that bundle the lesson tree, the content, the activities, and a tiny runtime that talks back to Hawkings for grading.

1. Generate the package

const pkg = await hk.scorm.create({
  cohort_id: "coh_123",
  version: "2004",         // or "1.2"
  callback_url: "https://api.hawkings.education/v1/scorm/...",
});

console.log(pkg.id, pkg.status);   // pak_..., "pending"
$pkg = $hk->scorm->create([
    'cohort_id' => 'coh_123',
    'version' => '2004',                // or "1.2"
    'callback_url' => 'https://api.hawkings.education/v1/scorm/...',
]);

echo $pkg->id . ' ' . $pkg->status;     // pak_..., "pending"
Wait for it:
const ready = await hk.poll(
  () => hk.scorm.retrieve(pkg.id),
  { until: p => p.status === "ready" },
);
$ready = $hk->poll(
    fn () => $hk->scorm->retrieve($pkg->id),
    ['until' => fn ($p) => $p->status === 'ready'],
);

2. Download the zip

const stream = await hk.scorm.download(pkg.id);

// Node:
import { createWriteStream } from "node:fs";
const file = createWriteStream("./course.zip");
stream.pipe(file);

// Or browser:
const blob = await stream.blob();
const url = URL.createObjectURL(blob);
$stream = $hk->scorm->download($pkg->id);

// Stream straight to disk:
$file = fopen('./course.zip', 'w');
while (! $stream->eof()) {
    fwrite($file, $stream->read(8192));
}
fclose($file);

// Or load into memory:
// $bytes = (string) $stream;

3. Upload to your customer’s LMS

Each LMS has its own upload UI. The shape is always:
  • Moodle: Course → Add an activity → SCORM → Upload zip
  • Canvas: Course → Settings → Apps → SCORM → Add package
  • Cornerstone: Catalog → Add learning object → SCORM → Upload
The package boots the Hawkings runtime in an iframe and reports back to the parent LMS with standard SCORM events: cmi.completion_status, cmi.score.scaled, cmi.session_time.

What the package contains

course.zip
├── imsmanifest.xml          (SCORM-required)
├── adlcp_rootv1p2.xsd
├── runtime/
│   ├── index.html           (iframe entry point)
│   ├── runtime.js           (talks to Hawkings)
│   └── runtime.css
└── content/
    ├── lessons.json         (the lesson tree)
    └── assets/              (images, audio, …)
The runtime.js makes signed calls to your Hawkings backend. Students don’t need their own Hawkings login — the LMS’ SCORM session is authenticated as a runtime token under the hood.

Per-student progress lands back in Hawkings

When a student opens the SCORM package, completes activities, and submits an assignment, those events flow through runtime.js to:
POST /v1/scorm/{uuid}/course/{cohort_id}/course-module/{lesson_id}/session
POST /v1/scorm/{uuid}/.../activity-question/evaluate
POST /v1/scorm/{uuid}/.../evaluate
In your dashboard you’ll see new Submissions, identical in shape to those produced by the native Hawkings student app. The SDK’s submissions.list({ assignment_id }) reads them transparently.

Re-issuing a package

Cohorts evolve. To re-publish:
await hk.scorm.create({ cohort_id: "coh_123" });
$hk->scorm->create(['cohort_id' => 'coh_123']);
You’ll get a new id. Old packages keep working — they continue to report into the same cohort. Issue a new zip when there’s structural change (lessons added/removed); for content edits the existing zip fetches the latest from Hawkings on each load.

Self-hosted runtime

If you don’t want the runtime to call api.hawkings.education, point the package at your self-hosted instance:
await hk.scorm.create({
  cohort_id: cohortId,
  runtime_base_url: "https://api.your-school.example",
});
$hk->scorm->create([
    'cohort_id' => $cohortId,
    'runtime_base_url' => 'https://api.your-school.example',
]);

Limitations

  • The package needs network to load lessons. Offline mode is on the roadmap — talk to us if you need it sooner.
  • AI tutor chat works inside the SCORM iframe with the same token flow as the native app.
  • Some LMSs cap zip size at 100MB; if your course exceeds that, lessons with heavy media are streamed instead of bundled.