Tracking Registration Results

Platform← click to see code and instructions specific to each platform

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 summary
  • includeRuntime: includes the runtime data of the activities
  • includeInteractionsAndObjectives: 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

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.

results matching ""

    No results matching ""