marzipano-to-xapi Connecting Marzipano to xAPI - Part One

Having written a couple of articles on Marzipano that continue to gain attention in Google searches including Marzipano 360 how to change hotspot properties and stylesMarking up 360 images making WHS more engaging and How to use 360 images with Marzipano and Insta360 I wanted to take things to the next level in eLearning and find out what it is the user is doing.

I’m currently involved in the xAPIChort and had also read an article Mel Milloway on incorporating xAPI into 360 images. This got me thinking as to how it can be setup in Marzipano and feed xAPI Statements back to an LRS.

Prior warning here, you’ll need to know a little bit about JavaScript as you need to modify the resulting JavaScript file that is made from the package, but if you follow this tutorial, you may work it out!

I used the Marzipano tool to generate a multi page interactive VR of the MONA Museum located in Hobart Tasmania back in 2016. I’m going to use this small project as a base for incorporating xAPI.

This is part one, introducing you to the concepts and capturing an Info Hotspot. The follow up parts will look at capturing more info and passing to the Learning Record Store (LRS).

1. Setting Up the Project Files

The first thing you need to do is expand the Marzipano package to a folder, you’ll end up with a folder structure similar to the image below:

marzipano_file_structure Connecting Marzipano to xAPI - Part One

You will also need to download an xAPI wrapper. This is the library that will do the heavy lifting for you. I’ve used the tincan.js file which you can download from github.

Once you’ve downloaded the file, add it to your root Marzipano directory. You will need to reference the file in the index.html file so you can use it. Make sure it’s located BEFORE the index.js file in the index.html file. Otherwise the modifications you make to the index.js and the use of the xAPI wrapper won’t work.

<!--Add the TinCan xAPI Wrapper-->

<script src="js/tincan-min.js"></script>

<script src="js/data.js"></script>
<script src="js/index.js"></script>

2. Understanding the index.js file

The index.js file can seem a little daunting at first, but it need not be. You’ll find it’s formatted well and easy to follow. In order to capture the event of a hotspot being fired, an eventlistner needs to be to added to fire off a function.
At the top of the index.js where the declarations are made, define the verb object and the object of the statement that we will use later on. We will set the defaults to experienced and the object ID to our URL. It’s important that you use a unique ID here, so having your own URL is an advantage.

//Add the xAPI TheVerb and TheObject objects!
 var TheVerb = {id:"", display:{ "en-US": "experienced" }};
 var TheObject = {id:"", "definition": {"name": { "en-US": "" }},"description":""};

Scroll to the bottom of the index.js file and before the closing class brackets (around line 370), add a new function that accepts two parameters, the actual event and a verb, both objects.

function sendxAPI(TheeEvent,TheVerb,TheObject){}

Now we need to add an event listener to part of the code where the hotspots are created. Look for the following code:

// Create info hotspots.
 sceneData.infoHotspots.forEach(function(hotspot) {
 var element = createInfoHotspotElement(hotspot);
 marzipanoScene.hotspotContainer().createHotspot(element, { yaw: hotspot.yaw, pitch: hotspot.pitch });

And add the event listener, firing off the function with the code below. This will send the hotspot event data and verb data to the sendxAPI function to build the statement:

// Create info hotspots.
 sceneData.infoHotspots.forEach(function(hotspot) {
 var element = createInfoHotspotElement(hotspot);
 marzipanoScene.hotspotContainer().createHotspot(element, { yaw: hotspot.yaw, pitch: hotspot.pitch });
   element.addEventListener('click', function() { = 'Hotspot - Click. '+ hotspot.title;
  TheObject.description =  hotspot.text;

The above code builds the Object data we need and we pull this data from the hotspot object, which will give us more information. A word of caution in my experience to date, you can add HTML to the text of the Hotspot when developing your project (or edit in the data.js file), but when passed to your LRS, it’s a bit messy.

3. Capturing a HotSpot link

So now we have the code setup, each time a hotspot is clicked on and expanded, our data is sent to the function. We now need to build out function to do the work for us.

Within the newly created function, add the following code:


var statement = '';
 statement = new TinCan.Statement(
 actor: {
 name:'Test User',
 mbox: ""
 verb: {
 "display": { "en-US": TheVerb.display["en-US"] }
 definition: {
 name: { "en-US": },
 "description": {
 "en-US": TheObject.description,
 target: {
 id: ""


It looks like a lot, but let’s work through it.

Actor: These are the details of the person logged in. This data can come from anywhere, but for the purpose of the exercise, I’m going to set the name and mbox to just a test user but added a random number after the username to get some statements from what appears as different users. Read more at

Verb: This will be populated from the TheVerb object we created has the name and definition already defined at the top of the page. This one is telling us that the user experienced the event. You could reset this where the listening event is to any of the other predefined verbs, or make up your own. That’s the power and flexibility of xAPI! Read more about the verbs you can use at and find out more about the Verb statement at

Object: This will set up what happened, this could be defined as the Activity. This is where we get the details from the  HotSpot object. Read more about the Object at

4. Sending to the LRS

As you’ve added the tincan.js file BEFORE the index.js file in the index.html (wow- so many indexes!), you already have a reference to the TinCan object.

The top of the function we created, add the following code:

var lrs;

try {
 lrs = new TinCan.LRS(
 endpoint: "",
 username: "123456789",
 password: "987654321",
 allowFail: false
catch (ex) {
 console.log("Failed to setup LRS object: ", ex);
 // TODO: do something with error, can't communicate with LRS

This sets up the LRS object of WHERE the data will be sent. There are heaps of of LRS’s you can use for free / development. I recently came across Yet Analytics, and for the purpose of development, it’s great! Check it out at

The next step is to save and send the statement. This is very easy as we’re using the TinCan Wrapper. add the following code AFTER the statement generation:

callback: function (err, xhr) {
if (err !== null) {
if (xhr !== null) {
console.log("Failed to save statement: " + xhr.responseText + " (" + xhr.status + ")");

console.log("Failed to save statement: " + err);

// TODO: do something with success (possibly ignore)


All going well, this will send the statement to the LRS and you’re done!

If you just want to view the statement in the browser console, simply comment out or replace the lrs.saveStatement part of the code with:



The outputs will be:

For writing to the console (or visit and have a look for yourself!)

xapi-browser-console-1024x648 Connecting Marzipano to xAPI - Part One


For sending to the LRS:

xapi-lrs-1024x523 Connecting Marzipano to xAPI - Part One

Leave a Reply

Your email address will not be published. Required fields are marked *

© The Digital Learning Guy |
ABN 364 4183 4283