LMS Integration
Building a robust integration with SCORM Cloud as an LMS provider can be a daunting task at first. This document is a guide in accomplishing it.
We recommend at least skimming Concepts before diving into this document.
Here’s the summary of all the below sections. Click the section name to jump straight to that section.
- IDs are yours
- Your system chooses the IDs for registrations, courses, and learners.
- Keeping System State
- Your system should store records of courses and registrations that are imported/created in SCORM Cloud.
- Importing Courses
- Import courses using importCourseAsync and getAsyncImportResult
- Creating Registrations
- Use a transaction around createRegistration to prevent data consistency problems.
- Create registrations in SCORM Cloud either at course assignment time or right before the learner launches the course.
- Use the registration postback (web hook) feature to receive registration state updates from SCORM Cloud.
- Build a way to manually call getRegistrationResult to sync registration state.
- Launching Registrations
- Don’t embed the launch API link directly in pages; redirect to it instead.
- Never rely on learners actually returning to the redirecturl specified in the launch request.
IDs Are Yours
In SCORM Cloud, importing a course requires providing a course ID. Creating a registration requires providing a registration ID and a learner ID.
It’s your system’s job to create and manage these IDs. In SCORM Cloud, they’re stored as strings up to 255 characters long. Feel free to use integers or GUIDs or any other format you’d like. Many customers use their own database primary keys as the IDs in SCORM Cloud.
The course IDs and registration IDs are used in other API calls to affect those resources, so keeping track of them is important.
The learner ID tied to a registration is mostly opaque to SCORM Cloud, but it is used in some contexts in SCORM itself, so it’s important that it’s unique to a learner. The learner first name and last name are similarly opaque, but are also exposed via the SCORM API: courses may use the learner’s name to generate certificates of completion, for example, so it also should be accurate if possible.
Keeping System State
We strongly recommend keeping records of your registrations and courses in your own database tables as well as in SCORM Cloud. This enables you to track and report on data in your own system instead of pulling data from SCORM Cloud every time it’s needed – which will make your system faster and more robust against system failures.
It also enables your system to potentially override registration state. For example, SCORM Cloud does not have any way to forcefully mark a registration as complete or passed or set its score. LMS administrators typically expect this functionality, however, so it can be implemented by manually overriding the registration state in your own reporting table(s).
Importing Courses
We recommend using importCourseAsync to import courses. The synchronous version, importCourse, is not reliable: SCORM Cloud lives behind AWS CloudFront, which will fail requests that take more than 60 seconds. As above, your system is responsible for creating and tracking the course ID used in the importCourseAsync call.
importCourseAsync will return a job ID. When an import is pending, your
system should periodically call
getAsyncImportResult
using that ID and check the status
element. As per the
documentation, if the status element has error
, then the course failed
to import for some reason – perhaps a user uploaded a file that
wasn’t a ZIP file (i.e. not a learning package) by accident, for
example. Otherwise, if it is finished
(not still running
), then the
course import completed successfully.
On a successful import, you can display the course’s title (extracted from the uploaded package) to the user, as it’s returned by getAsyncImportResult. getAsyncImportResult also returns a list of parser warnings, which might not be useful to show the user, depending on the audience; the parser warnings are mostly only interesting to content creators who are making their own packages.
Calling the import API
Getting a learning package from your end-user to SCORM Cloud requires a little thinking. SCORM packages, for example, can be quite large, sometimes several gigabytes – although around 50 megabytes is common. We recommend one of two approaches.
In both approaches, the user should be presented with something like a form input to upload their package. The difference lies in where the form will upload to.
importCourseAsync supports a redirecturl parameter that will redirect the browser after a successful POST to a page of your choosing. In principle, your user could POST directly to the importCourseAsync API call URL, and after the POST request is complete, the redirecturl would redirect the user to a “pending import” page on your site that periodically polls the getAsyncImportResult endpoint for the import status. However, API call links, once generated, are only valid for approximately 15 minutes. Therefore, embedding an importCourseAsync API call URL directly in the rendered HTML of a webpage can cause problems if the user waits more than 15 minutes before submitting the form. This isn’t just a hypothetical scenario: we get questions about this somewhat frequently from new system implementers who run into this problem.
This leads to the two solutions:
- your app could intercept the form submission, then call out to its own endpoint to generate the importCourseAsync URL and make the form upload to that instead, or
- your app can upload the course to your own server, which could then stream the course to the SCORM Cloud API. (Streaming here is important – since courses can be large, keeping the entire upload in memory or even on disk may not be desirable in your system.)
Note
importCourseAsync has the correct CORS headers to enable POSTing directly from client-side JavaScript. Keep in mind, however, that building the API link requires knowledge of your app ID’s secret key, so it’s best to build the link server-side and give it to the client if taking that approach.
The second solution is probably easiest, but it also makes the course files pass through your system on their way to SCORM Cloud. This may or may not be desirable.
In either case, it’s important for your system to follow up with getAsyncImportResult to check the import status.
Creating Registrations
For a learner to take a course, a registration must be created against that course for that learner. Usually, this is done via the createRegistration API call. As above, the registration ID is your system’s responsibility to generate, and the learner ID, first name, and last name are all opaque to Cloud – only used for reporting and debugging.
When creating a registration, if the registration creation fails, your system should take care to remove the record of that registration from your database. This can be accomplished using database transactions if your database supports them (and if that makes sense in your integration).
Registration Creation Time
When your system should create a registration in SCORM Cloud can vary.
If your system has a notion of “assigning” a course to a learner, then when the course is assigned, a registration could be created. This matches the mental model of registrations – the assignment of a course to a user.
Alternatively, some customers have written their integration such that registrations are not created in SCORM Cloud until the learner actually launches the course. Their workflow goes something like:
if we have an existing registration for that user:
launch that registration
else:
create a registration in SCORM Cloud
launch that registration
This has the advantage of possibly costing a little less if the learner never launches the registration.
Either way is fine.
Getting Registration Results
As discussed above, your system should store its own version of the registration state to display to the user (and possibly to modify on its own if necessary). The question is then: how does one synchronize the registration state in SCORM Cloud with an application?
We recommend implementing a way to store the results of
getRegistrationResult
in your system for a given registration. Most systems can
keep the default resultsformat parameter course
,
which will return four key elements of the registration state:
complete
– whether the registration has been completedsuccess
– whether the registration has been passed or failedscore
– the score, from 0 - 100totaltime
– the total time tracked by our content player in seconds: that is, how long the learner had the course open
Note
complete, success, and score are reported by the actual course content. For example, SCORM content will set these values via the SCORM API. As a result, the “meaning” of these values will depend on the course that the registration was made against.
These four items (“The Big Four” as we sometimes call them) are the
most important elements in displaying a registration state. For many
purposes, they suffice; if not, by changing the
resultsformat
parameter, your system can retrieve more
detailed information, such as individual activity statuses or
interaction (question/answer) data.
An ideal, robust integration uses this mechanism of storing the registration state in two different situations.
First, we very strongly recommend implementing an endpoint to receive
registration postback (web hook) data from our system, and setting that
up appropriately when calling
createRegistration
(see the postbackurl
parameter documentation).
When configured, SCORM Cloud will send the exact same data as
getRegistrationResult whenever SCORM Cloud gets an update to that
registration. In this way, your system receives updates about the
registration state as soon as they’re made. This creates a very robust
implementation, since the registration state in SCORM Cloud is always
POSTed directly to your system.
A detailed postback guide can be found at Registration Postbacks.
Second, we recommend building a way to force your system to call getRegistrationResult to refresh a given registration’s state in your database. Although postbacks are designed to be highly reliable, this manual refresh process could be useful. (Plus, since implementing postbacks requires writing code to parse getRegistrationResult, the same code can be used for the refresh process.)
Launching Registrations
Once courses are imported and your system has a way to store registration results, it’s time to launch those registrations.
The registration launch API call is special in that it’s meant to be “called” (loaded) directly from the learner’s browser. That API call URL will then respond with a 302 Redirect that sends the learner into the content player (and eventually will load the content).
Because the launch API call redirects the learner, it supports a
redirecturl
parameter that tells SCORM Cloud where to
redirect the learner back to when they’ve exited the registration.
Therefore, your system should redirect the user to the launch API call
URL with an appropriate redirecturl. Once the user’s finished,
depending on how they exit the course (more on this below), they will be
sent to the redirecturl, which points back at your website – maybe a
page that displays their registration results. (This is a good place to
call
getRegistrationResult,
by the way, supplementing registration postbacks.)
There are just two wrinkles.
Expiring Links
As discussed above in the course import section, API call URLs are only valid for approximately 15 minutes after they’re generated. Thus, embedding the built launch API call URL directly in a webpage is not a good idea – if the learner waits 15 minutes or more before pressing launch, they’ll get an error saying the timestamp is out of bounds.
Instead, your system should have its own launch page. That launch page can build the SCORM Cloud launch API call URL and send a redirect to the learner. Alternately, your system could generate the launch API call URL server-side and have client-side code to redirect to it. Either way, your system should generate the launch API call URL once the learner’s actually launching, not before.
Unreliable redirecturl
The learner may never make it back to the redirecturl
specified in the launch request. As an example, once the learner is
finished with their registration, they might just… close their
browser. In this case, our content player will use its onbeforeunload
handler to fire off a final state save, but the browser closing means
that the learner won’t ever hit the redirecturl.
As a result, your integration cannot rely on learners making it back to the redirecturl page. This limitation is the primary reason that we stress using the registration postback feature; there’s simply no other way to get (near-real-time) registration results reliably.