Creating Your Own xAPI Wrapper: A Beginner-Friendly Tutorial

If you’re working with xAPI for the first time, you’ve probably noticed that sending and receiving statements directly through raw HTTP requests can feel intimidating. That’s where an

What is an xAPI Wrapper?

An xAPI wrapper is a reusable piece of code (usually written in JavaScript for web-based learning content) that simplifies interaction with a Learning Record Store (LRS). Instead of writing full fetch or XMLHttpRequest calls every time, you can call a simple function like:

sendStatement("John Doe", "completed", "Module 1: Introduction to xAPI");

The wrapper handles authentication, formatting, and sending the xAPI statement in the background.

 

Why Do You Need a Wrapper?

  • Saves time: No need to rewrite API calls every time you send a statement.
  • Reduces errors: Handles JSON formatting and required fields consistently.
  • Makes code cleaner: Developers can use simple function calls instead of complex xAPI syntax.
  • Reusable: Once built, you can plug it into multiple projects or authoring tool exports.

Anatomy of an xAPI Statement

{
  "actor": { "mbox": "mailto:john@example.com", "name": "John Doe" },
  "verb": { "id": "http://adlnet.gov/expapi/verbs/completed", "display": { "en-US": "completed" } },
  "object": { "id": "http://example.com/course/module1", "definition": { "name": { "en-US": "Module 1: Introduction to xAPI" } } }
}

Step 1: Set Up LRS Connection Variables

const LRS_ENDPOINT = "https://lrs.example.com/xapi/";
const LRS_USERNAME = "basic_key";
const LRS_PASSWORD = "basic_secret";

Step 2: Build a Function to Send Statements

function sendStatement(actorName, verb, objectName) {
    const statement = {
        actor: {
            name: actorName,
            mbox: "mailto:" + actorName.replace(" ", ".").toLowerCase() + "@example.com"
        },
        verb: {
            id: "http://adlnet.gov/expapi/verbs/" + verb,
            display: { "en-US": verb }
        },
        object: {
            id: "http://example.com/activities/" + objectName.replace(" ", "-").toLowerCase(),
            definition: {
                name: { "en-US": objectName }
            }
        }
    };

    fetch(LRS_ENDPOINT + "statements", {
        method: "POST",
        headers: {
            "Authorization": "Basic " + btoa(LRS_USERNAME + ":" + LRS_PASSWORD),
            "Content-Type": "application/json"
        },
        body: JSON.stringify(statement)
    })
    .then(response => {
        if (response.ok) {
            console.log("xAPI Statement sent successfully!");
        } else {
            console.error("Error sending statement:", response.statusText);
        }
    })
    .catch(err => console.error("Network error:", err));
}

Step 3: Test Your Wrapper

sendStatement("John Doe", "completed", "Module 1: Introduction to xAPI");

Step 4: Extend Your Wrapper (Optional)

Once the basics work, you can expand your wrapper to handle more complex scenarios:

  • Question/Answer tracking
  • Video tracking (play, pause, complete)
  • Score reporting
  • Session data (context, result, extensions)

Example of tracking a quiz score:

function sendScore(actorName, objectName, score, maxScore) {
    const statement = {
        actor: {
            name: actorName,
            mbox: "mailto:" + actorName.replace(" ", ".").toLowerCase() + "@example.com"
        },
        verb: {
            id: "http://adlnet.gov/expapi/verbs/scored",
            display: { "en-US": "scored" }
        },
        object: {
            id: "http://example.com/quizzes/" + objectName.replace(" ", "-").toLowerCase(),
            definition: { name: { "en-US": objectName } }
        },
        result: {
            score: {
                raw: score,
                max: maxScore
            }
        }
    };

    // Reuse fetch logic here
    sendToLRS(statement);
}

Best Practices for Your Wrapper

  • Keep it modular – separate authentication, statement building, and sending logic.
  • Validate data – make sure actor, verb, and object are always present.
  • Handle errors gracefully – log failed statements or retry sending later.
  • Secure your credentials – don’t expose LRS keys in public-facing code (use a proxy if needed).

Final Thoughts

Creating your own xAPI wrapper gives you flexibility and control over how learning data is tracked. While existing libraries (like ADL’s xAPI Wrapper) are powerful, building your own lightweight version helps you understand the mechanics of xAPI and adapt it to your organization’s specific needs.

Start small with simple verbs like completed and experienced, then expand your wrapper to capture richer learning interactions.

The more data you capture, the better insights your Learning Record Store (LRS) can provide.

Next Step: Try extending your wrapper to track video interactions or quiz questions, and compare the statements in your LRS!

Comments

Popular posts from this blog

What is xAPI? A Complete Guide for eLearning Developers

Choosing the Right LRS: A Technical Evaluation for Developers

Creating Custom xAPI Statements: A Step-by-Step Developer Guide