Tracking Registration Results
Technically, Engine does all the tracking for you, but Engine doesn't have any idea what you intend to do with the data it captures. As discussed in the overview, your system should store its own version of the registration state to display to the user in your application and for other reporting purposes. We provide a couple of ways of keeping that data in sync with the registration state in Engine.
Automatic Update through Webhooks
Engine can be configured to send posts to an endpoint in your system (a webhook) whenever it receives an update to the status of one of your registrations. You can do this by subscribing to the RegistrationChanged
topic and providing us the url to which you'd like us to post the details.
For example, you might create a new subscription like the following which will send you the registration state whenever there is a significant change to it. "Significant" here just means we're not going to send a post for every update to the registration, if that update only involves datapoints that aren't generally used by customer applications (eg, the suspend data or a runtime objective).
{
"topic": "RegistrationChanged",
"enabled": true
"url": "https://example.com/regchanged",
"authId": "e2c2c6e2-7b39-4a16-9d0c-48d5b61f3172",
}
There are many other configuration options you can use when creating a subscription, so please see the Webhooks section for more details on those and how to configure authentication for the requests.
Once you have this configured, you will periodically receive posts to your endpoint that look something like this:
{
"payloadId": "f2d92452-2579-431b-b353-1b0541d2b553",
"subscriptionId": "1cd1b488-284b-4b08-bff3-6ceb1d8649ac",
"topic": "RegistrationChanged",
"subtopics": [
"CompletionChanged"
],
"tenantName": "apitests",
"timestamp": "2022-12-20T20:54:10.379Z",
"body": {
"id": "117a8053-f379-4d57-b87e-f698284c4239",
"instance": 0,
"xapiRegistrationId": "6f878647-3327-4735-a79c-8150430eff35",
"updated": "2022-12-20T20:54:10.375Z",
"registrationCompletion": "INCOMPLETE",
"registrationSuccess": "UNKNOWN",
"totalSecondsTracked": 0,
"lastAccessDate": "2022-12-20T20:54:10.375Z",
"createdDate": "2022-12-20T20:54:07Z",
"course": {
"id": "678fff8e-f558-44e4-a384-e04bd9b72911",
"title": "Introduction to Geology - Responsive Style",
"version": 0
},
"learner": {
"id": "117a8053-f379-4d57-b87e-f698284c4239",
"firstName": "Queen",
"lastName": "Bosco"
},
... <other properties> ...
},
"bodyVersion": "1.0",
"messageVersion": "1.0",
"resources": {
"course": {
"id": "678fff8e-f558-44e4-a384-e04bd9b72911",
"learningStandard": "cmi5",
"version": 0
},
"registration": {
"id": "117a8053-f379-4d57-b87e-f698284c4239",
"instance": 0,
"learner": {
"id": "117a8053-f379-4d57-b87e-f698284c4239",
"firstName": "Queen",
"lastName": "Bosco"
},
"isDispatch": false
}
}
}
Processing the Webhook Payload
The payload's body
property will contain the same RegistrationSchema
that the API returns when you call the GetRegistrationProgress
endpoint.
Keep in mind that it represents the state of the registration at the moment the notification message was generated. This means that it is important to track the payload's timestamp
property to ensure that you do not update the registration on your side with data from an older payload!
To make it easier to handle the postback coming from Engine, you can reference the API client's model classes and translate the postback into a RusticiSoftware.Core.api.client.v2.Model.EventMessage
object using your favorite JSON parser. Then, access its body
property and translate it into a RusticiSoftware.Core.api.client.v2.Model.RegistrationSchema
object.
On .NET, these are the namespaces you'd need to import to use the sample code.
using Newtonsoft.Json;
using RusticiSoftware.Core.api.client.v2.Model;
using System;
Provided that the string variable jsonString
contains the JSON from the postback, this code would deserialize it into the EventMessage
class and then its body
property into the RegistrationSchema
class:
EventMessage eventMessageSchema = JsonConvert.DeserializeObject<EventMessage>(jsonString);
if (eventMessageSchema.Topic.Equals("RegistrationChanged"))
{
string body = eventMessageSchema.Body.ToString();
RegistrationSchema registrationSchema = JsonConvert.DeserializeObject<RegistrationSchema>(body);
Console.WriteLine(registrationSchema.Id); //output = 'my_test_reg_id'
Console.WriteLine(registrationSchema.RegistrationCompletion); //output = 'COMPLETED'
Console.WriteLine(registrationSchema.Learner.FirstName); //output = 'Waylon'
}
On Java, these are the packages you'd need to import for the sample code.
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializer;
import com.google.gson.internal.LinkedTreeMap;
import RusticiSoftware.Core.api.client.v2.Models.RegistrationSchema;
import RusticiSoftware.Core.api.client.v2.Models.EventMessage;
Provided that the string variable jsonString
contains the JSON from the postback, this code would deserialize it into the EventMessage
class and then its body
property into the RegistrationSchema
class:
Gson gSonInstance = new GsonBuilder()
.registerTypeAdapter(OffsetDateTime.class, (JsonDeserializer<OffsetDateTime>)
(json, type, context) -> OffsetDateTime.parse(json.getAsString()))
.create();
EventMessage eventMessageSchema = gSonInstance.fromJson(jsonString, EventMessage.class);
if (eventMessageSchema.getTopic().equals("RegistrationChanged"))
{
LinkedTreeMap bodyMap = (LinkedTreeMap) eventMessageSchema.getBody();
RegistrationSchema registrationSchema = gSonInstance.fromJson(gSonInstance.toJson(bodyMap), RegistrationSchema.class);
System.out.println(registrationSchema.getId()); //output = 'my_test_reg_id'
System.out.println(registrationSchema.getRegistrationCompletion()); //output = 'COMPLETED'
System.out.println(registrationSchema.getLearner().getFirstName()); //output = 'Waylon'
}
Querying the API
At any time, you can retrieve the current state of a registration using the /registrations/{registrationId}
endpoint. This endpoint will return the same JSON schema that you receive in the registration postbacks. You might use this as a way to manually refresh a registration's state when needed.
Please remember that these resources are not intended to be ad hoc reporting mechanisms. Fetching all of the information about the registrations is not a small task, and repeatedly hitting these endpoints can result in negative effects on Engine's performance.
When calling the /registrations
endpoint, there are a few parameters that control how much detail about the registration is returned:
includeChildResults
: this includes details about each activity or learning object in the course, not just the top-level summaryincludeRuntime
: includes the runtime data of the activitiesincludeInteractionsAndObjectives
: this goes a step further and includes objective and runtime interaction details (questions/answers)
We disable these parameters by default, as each one adds additional processing time for creating the result. Many customers only care about the top-level details anyway, so this lets you include them only if actually needed.
API Results Limit and Paging
Although Engine and its REST API are not intended to be used for heavy reporting, some endpoints, like GET requests to /registrations
and /courses
, have the ability to return lengthy arrays of results. Consequently, Engine limits how many items Engine returns at once. Two settings, ApiResultLimit
and ApiResultLimitSinceNotSpecified
, can be used to adjust the number of items returned in one response. If the optional 'since' or 'until' range parameters are available but not used, then ApiResultLimitSinceNotSpecified
determines the results limit. Otherwise, if you do send 'since' or 'until' as query parameters, then ApiResultLimit
determines the results limit.
When the total number of results exceeds the limit, the response will contain an additional property called 'more':
"more":"Q5GITSQBAAAKAAAA"
If you use the original query parameters along with 'more' and its value on a subsequent GET request, you will receive the next page of results:
/api/v2/registrations?since=2020-11-02T19:52:00Z&more=Q5GITSQBAAAKAAAA
Client-side Player API
In many integrations with Engine, our player is loaded in the context of one of the customer's application pages. There are some situations where it is useful for the two pages to be able to communicate with each other, either to trigger an action in the player or for the player to notify the application of certain events happening.
This type of message passing uses the browser's postMessage
and message
event listener functionality to accomplish this. To enable this type of communication to and from a parent or opener window you must configure the PlayerPostMessageApiEnabled
setting to true
. Without this setting enabled, the player will not listen for or trigger any messages.
Basic Message Structure
All messages that the player can send or receive will be an object that contains at least the following 3 properties: topic
, version
, and rusticiEngine
. This basic message structure is shown below:
{
topic: "EventTopic",
version: 1,
rusticiEngine: true
}
Each property is described below:
topic
: a string indicating what this particular message is aboutversion
: an integer indicating which version of the Client-side Player API that this message usesrusticiEngine
: a boolean that indicates whether or not this message originated from Engine, to disambiguate it from other messages received via 'window.postMessage'
All specific messages defined by the API are listed below, grouped by whether they are sent or received by Engine. Some messages may include additional properties beyond the standard topic
, version
, and rusticiEngine
. If a message can do this, it is noted in its description, and the additional properties are outlined and described.
Additional messages, as well as additional properties to messages, may be added over time. This documentation will be updated to reflect those changes. However, some time may elapse between when you view this documentation and when the next update is made. For this reason, we advise you to not depend on only the messages described below existing, or on them only having the properties described below.
Messages that the Player can send
ContentLoaded
Indicates that the course content has been loaded. Applies to both content launched in a new window and content launched in an iframe.
Note that if the course content does not share the same domain as Engine, Engine is prevented from accessing information it can use to accurately determine when the content has loaded. This is due to browser same-origin policy. In this case, Engine will send this message when the media content's window or frame navigates to that domain, which may be before the time the content is able to fully load.
Also, there are certain error conditions where this message will not fire at all:
- for SCORM content, if the concurrent launch page is triggered
- if there is an error in loading the modern.html player page or the request it makes to validate the launch token and retrieve information about the registration to launch
Because of this possibility, if your application is depending on the ContentLoaded message to remove a loading overlay, you should have a fallback mechanism to remove the overlay after a specified number of seconds so that the page can be interacted with in these edge cases.
Example:
{
topic: "ContentLoaded",
version: 1,
rusticiEngine: true
}
PlayerExited
Indicates that the player is performing its exit routines and is about to redirect to the RedirectOnExitUrl or be unloaded.
Example:
{
topic: "PlayerExited",
version: 1,
rusticiEngine: true
}
RegistrationStatusChanged
This message notifies of updates to the registration's completion, completion amount, success, or score. That is, if Engine determines that the value of at least one of those attributes is different from before, this message will be sent, and will contain the now-updated values for all of them.
Note that this topic only fires for courses in the SCORM learning standard currently. This is due to the nature of how progress updates are communicated to Engine by SCORM through the player page. Since other learning standards communicate status directly to the Engine application, capturing progress updates in order to send client-side notifications will require some additional functionality to be implemented in a future version.
Also note that the values sent are those indicated by the SCORM Lookahead Sequencer, which allows us to calculate what they will be once we rollup from the runtime. For SCORM 2004, this lets us send what the final overall status will be even before it has officially rolled up to the top-level Registration status. This means that your client-side application could receive notification of the course being completed before you get a webhook notification from the Engine application indicating the same. This can be helpful for letting your application UI react to the completion without waiting for the exit of the course.
This message includes the following additional properties: sequenceNumber
, payload
.
Example:
{
topic: "RegistrationStatusChanged",
version: 1,
rusticiEngine: true,
sequenceNumber: 3,
payload: {
completion: "completed",
success: "failed",
score: 20,
completionAmount: "unknown"
}
}
Additional properties:
sequenceNumber
: an integer indicating the order of this registration status changed message relative to previous registration status changed messages (e.g. asequenceNumber
of 3 indicates that this registration status changed message was preceded by two others)payload
: an object containing information about the registration's updated status; it includes the following properties:completion
,success
,score
,completionAmount
Messages that the Player can receive
ExitPlayer
Notifies the player that it should exit. Upon receiving this message, the player will perform its exit routines and redirect to the configured RedirectOnExitUrl
.
Example:
{
topic: "ExitPlayer",
version: 1,
rusticiEngine: true
}
Resetting Learner Progress
There may be times when you need to reset a learner's registration state so that they basically start over fresh. This may be so the user can retake the course, or to clear out data during testing. You can trigger this clearing of progress data by doing a DELETE request to the /registrations/{registrationId}/progress
endpoint.