xAPI + Captivate (Part 1)


From the moment I learned about it, I’ve been in love with the idea of what the Experience API (xAPI) could do. While adoption among some L&D departments (mostly at companies with large budgets and/or technical know-how) has been steadily gaining steam, the majority of us seem to be sitting around twiddling our thumbs, waiting for Adobe and Articulate to pull us into this new elearning world. Credit where credit is due, with a limited number of LMSs, you can actually use Adobe Captivate and Articulate Storyline to send a limited set of xAPI statements under a limited set of conditions (if you’re interested in reading more, Sean Putman has thoroughly documented how in the first xAPI Quarterly).

When it comes to rapid elearning development tools, stuff like this makes me want to pull my hair out (what little that hasn’t already gone). Why can’t I have Captivate send whatever statement I want to whatever LRS that I want?

So that’s the genesis behind this. I’ve found lots of information about xAPI at a high-level, and lots of information about xAPI at a very technical level, but very little information for instructional designers trying to bridge the gap between rapid development tools and writing code. Over the course of several blog posts (and hopefully on an ongoing basis as I continue to learn things) I’ll be documenting how I’m getting Captivate to bend to my will and play nice with xAPI, hopefully in a way that makes sense to instructional designers with little to no programming experience. For this first one, I’ll be demonstrating how to:

  • Retrieve user information to create an xAPI statement
  • Send that statement to an LRS when the user clicks a button

In order to send the statement, we need (at minimum) three pieces of information: an actor, a verb, and an object. Let’s start by creating a Captivate project and using our first slide to obtain information from the actor. In this case, we’ll be capturing the user’s name and email address using two text input boxes.


By default, Captivate assigns the generic variable Text_Entry_Box_1 to our input box, but it’ll make it a little clearer to assign it a more descriptive variable name. To do this, select the text entry box. In the Properties window, scroll down to Variable and click the [X] and create a new variable called user_name. For the email address box, set the variable to user_email.


On success, the action is set to Go to the next slide.

Create a second slide and add a button. This will be where the magic happens, but let’s get all our ducks in a row before we move any further.


Let’s publish our project and take a closer look at the files that Captivate outputs:


Each type of file has a specific role to play, and (in some cases) uses a different language to accomplish it. For those without web development experience, here’s what each file type means:

  • HTML (Hypertext Markup Language) – is the content itself. It’s useful to think of the HTML file as the “hub,” since it pulls in and links all of the other files.
  • CSS (Cascading Style Sheet) – styles the content in the HTML file. This file sets your colors, your fonts, your alignment, etc.
  • SWF (Flash) – is the meat of what Captivate outputs. All your slides, graphics, animations, and interface elements make up this file.
  • JS (Javascript) – are the dynamic elements. We’ll be doing most of our work today in Javascript.

To simplify our task, we can add a Javascript library to this folder to handle a lot of the behind the scenes communication between our project and the LRS. For this project, I’ll be using a library called xAPI Wrapper, which you can download from the ADL’s github. Download and copy the xapiwrapper.min.js file into your folder.

It’s not enough just to add the library file, however. We’ve got to create another Javascript file to tell it what to do! If you’re looking to go as simple as possible, you can do this in Notepad or TextEdit, but I prefer using a nice code editor such as Notepad++ (on Windows) or Brackets (on Mac). Both are free.

So let’s use our code editor to create a new file. Let’s call it setup.js and save it in the same folder. Start off by setting up our communication with the LRS.

function setupConfig() {
 var endpoint = 'https://lrs.adlnet.gov/xapi/';
 var user = 'xapi-tools';
 var password = 'xapi-tools';

 var conf = {
  "endpoint" : endpoint,
  "auth" : "Basic " + toBase64(user + ":" + password),

In this example, I’m using the ADL’s Public LRS. Whichever LRS you sign up for should provide you with an endpoint, username, and password, so simply change these fields to suit your LRS. Most LRS providers (SCORM Cloud, Saltbox, etc.) have free options available for those just looking to tinker, and I heartily recommend signing up for an account if you’re looking to dive deeper.

Since this is a function, we’ll need to call it.


Our LRS setup complete, let’s actually go about actually crafting our statement. Let’s create a new statement and name it stmt.

var stmt = new ADL.XAPIStatement();

Since our actor information is going to be dynamically set from within Captivate based on the user’s input, we’ll only handle the verb and object here. Let’s create a statement that reads:

[actor] experienced Captivate button tutorial

Where experienced is the verb and Captivate button tutorial is the object. In addition to their plain English names, we’ll also need IDs for each.

You can select from a preset library of verbs in the Tin Can Registry or on ADL’s Controlled Vocabulary. I find in conversations with those who haven’t worked with xAPI, people are really interested in the idea of creating their own verbs. In some cases, it makes sense, but it’s the sort of thing that adds an unnecessary complication unless you really need to.

The verb that I’m using here is already defined, so there’s no point in reinventing the wheel. Let’s grab the information we need, the verb (experienced) and its ID (http://adlnet.gov/expapi/verbs/experienced) and add it to our statement:

stmt.verb = new ADL.XAPIStatement.Verb(‘http://adlnet.gov/expapi/verbs/experienced’, ‘experienced’);

Now we need to determine what our object is. Object types are standardized – in this case, I want it to be an Activity – but the rest of the object is entirely up to us to define, since we (as instructional designers) are usually the ones creating it. In this case, I’m giving it a name that’s logical and makes sense (Captivate button tutorial) and assigning it an ID that also happens to make sense – the URL for this blog post (https://willchinda.com/blog/2015/10/14/xapi-captivate-part-1/). There’s some debate about whether IDs can just be random codes (courses/tutorials/buttontutorial101) or actual URLs that open webpages. I tend to prefer the latter, since a URL can provide a great deal more context and information.

At any rate, let’s add this information to our statement:

stmt.object = new ADL.XAPIStatement.Activity(‘https://willchinda.com/blog/2015/10/14/xapi-captivate-part-1/’, ‘Captivate button tutorial’);

That’s it! Pretty simple, eh? Let’s get back to our Captivate file and add our actor to this statement.

We’ve already captured our actor information from user inputs – all we need to do is translate these Captivate variables (user_email and user_name) into Javascript ones. Thanks to a new Javascript framework available since Captivate 8, this is fairly simple. Then we can add them to our statement and send it. Thanks to our inclusion of the xAPI Wrapper library in the published folder, we can easily trigger all of these actions to occur when the user clicks the button.

Select the button on the second slide and open the Properties window. Under Actions, set the On Success action to Execute Javascript. Click the Script_Window button, and we can add Javascript code that will execute when the user clicks the button.


var userName = window.cpAPIInterface.getVariableValue(“user_name”);
var userEmail = ‘mailto:’ + window.cpAPIInterface.getVariableValue(“user_email”);

stmt.actor = new ADL.XAPIStatement.Agent(userEmail, userName);

var resp_obj = ADL.XAPIWrapper.sendStatement(stmt);

Here, all we’re doing is creating two new variables (userName and userEmail) to accept the variables entered in by the user in the first slide. Note that we’re also adding ‘mailto:’ to the user’s email (a necessary format for the actor’s ID). Then, much like we set the verb and object, we’re adding these elements to our statement. Finally, we send our statement.

Let’s republish our project and have another look at our published folder.


In addition to the four files that Captivate publishes, we should also have our xapiwrapper.min.js library as well as the setup.js file that we created. Now the question is, how do we bring those two files into our project? We can do that with two lines of code added to our HTML file:

<script src="xapiwrapper.min.js" type="text/javascript"></script>
<script src="setup.js" type="text/javascript"></script>

Open up the test.html file and add it anywhere in between the <head> tags. For consistency’s sake, I just like pasting them right underneath where the standard.js file is being added.


Note that if you decide to publish again, you’ll have to go back in and add these two lines to the html file.

That’s it! Want to try it out? I’ve got a live version of the project here sending statements to the ADL Public LRS. Click the button and see your statement show up in the LRS! Since it is sending to a Public LRS, don’t type anything into those input fields you don’t want public.

I’ve also posted the source files to github if you want to download and start playing around with them. Get busy making statements!

16 thoughts on “xAPI + Captivate (Part 1)

    1. Hi John. Yes, both those go in the same file. I tried to separate them just so I could explain them separately. You can download all the finished files from my github.

  1. Hi Again Will,
    OK, I played around with your app again today with the idea of adding one more slide with a button thereby sending one more statement to the LRS. I couldn’t do it.
    My question: I added one more slide in Captivate 9 and placed a button on it. What code would I have to modify for this button to send a statement when clicked on? Thank you very much.
    – John M.

    1. Hi John,

      Did you duplicate the slide? If it has the same javascript code it should send the statement no problem. Is it not sending a statement at all? Or are you trying to send a different statement? If you’re trying to do the latter, you’ll have to create a different statement from the “stmt” that’s defined in setup.js

      1. Hi Will,
        Thank you for the reply. I tried your suggestion of making another statement and it worked!! Thank you. Now on to creating more and varied statements!! Have a great week. John M.

  2. Hi Will,

    I have found several xAPI + Captivate tutorials, but no one has published their Captivate file as xAPI, even though that is an option. Is there a reason why you chose not to do that?

    1. I think that’s addressed in your other comment – it only works for certain LMS. What I found interesting was xAPI’s capability to de-couple the course from the LMS, so it could be hosted in other places, and that’s what I chose to explore.

  3. Hi William,

    I have another question. In this example the endpoint to the LRS and the LRS authorization data are hardwired into the Captivate project.

    If I have a project that will have a lot of difference users, how do I ensure the xAPI data is sent to each user’s individual LRS? In other words, how can I dynamically change the config file?

    One way would be to use additional Captivate input boxes and variables and have the user enter the LRS endpoint, username and password. Is there another way to retrieve this info without the manual entry?

    I guess this is more of a general xAPI question.

    1. This is almost certainly something you could do with Javascript, though I can see it getting quite complex. How you implement it would depend on where you would store that LRS endpoint info. You probably shouldn’t expect your end users to know their LRS info, unless your audience is just xAPI nerds 🙂

      1. Interesting! Ok, that sounds promising. I’ll keep an eye on your blog for additional info ; ) I dont know how the project would ever know what the user’s LRS info is, unless it was hosted on a LMS or something similar where they had already logged in.

  4. Hi Will
    Thanks so much for posting, looks a very concise and straight forward process.
    I noticed in the screenshots you have output as SWF.
    Does it matter if it is output as HTML5?
    I followed your steps to a “T” however I’m not getting any statements visible in the ADL LRS.
    Also, does it matter where the Captivate output file is placed? We have it on a random web server with quiz reporting turned off.
    Thank you

  5. It looks as though someone wanting to do these steps needs to know HTML; is this a correct assumption?

    1. Yes, some basic HTMl knowledge is needed, though you could probably muddle through by copying and pasting.

Leave a Reply

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