Most L&D professionals treat the LMS as a container. You push content in, learners pull it out, completion gets tracked, done. I wanted to test something different: could a course running inside an enterprise LMS fetch the learner's identity at runtime, send it to an external AI voice API, and play back a personalised audio greeting — all without any backend, any server-side code, or any IT middleware?

The answer is yes. But the path to yes looked nothing like I expected.

What I was trying to build

The goal was straightforward on paper: when the course launches, greet the learner by their first name — spoken aloud by an AI voice, generated in real time. No pre-recorded audio. No manually authored variations. The name is pulled from the LMS, sent to the voice API, and played back within seconds of the course opening.

The entire mechanism had to live inside a JavaScript trigger in Storyline 360. No server. No middleware. One script, firing on slide load.

The architecture

Three moving parts:

Here's the core logic:

var userName = "Learner"; // Default fallback

function getSCORMLearnerName() {
  var api = null;
  function findAPI(win) {
    if (win.API) return win.API;              // SCORM 1.2
    if (win.API_1484_11) return win.API_1484_11; // SCORM 2004
    if (win.parent && win.parent !== win) return findAPI(win.parent);
    return null;
  }
  api = findAPI(window);
  if (api) {
    var name = (api.LMSGetValue)
      ? api.LMSGetValue("cmi.core.student_name")
      : api.GetValue("cmi.learner_name");
    if (name) {
      console.log("Raw SCORM Name: " + name);
      return name.split(", ").reverse()[0]; // Extract first name
    }
  }
  console.warn("SCORM API not found. Using default.");
  return "Learner";
}

userName = getSCORMLearnerName();
var message = "Hello " + userName + ", welcome to this eLearning course!";

function fetchSpeech() {
  fetch("https://[voice-api-endpoint]/v1/speech/generate", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "api-key": "YOUR_API_KEY_HERE"
    },
    body: JSON.stringify({
      voiceId: "en-UK-[voice]",
      style: "Conversational",
      text: message,
      format: "MP3"
    })
  })
  .then(r => r.json())
  .then(data => {
    if (data && data.audioFile) {
      new Audio(data.audioFile).play();
    }
  })
  .catch(err => console.error("API Error:", err));
}

fetchSpeech();

The walls I hit

1. CORS — and a week of looking in the wrong place

The first time I ran this in a real LMS environment, the fetch call failed silently. Browser console showed a CORS error: the AI voice API was blocking requests from the LMS domain.

My first assumption: something in our network configuration was blocking the outbound call. I spent almost a week checking proxy settings, testing from different environments, and ruling out internal firewall rules. None of it was the problem.

The actual fix lived on the API provider's side. Their endpoint needed to explicitly whitelist the LMS domain — the specific URL from which the course was being served. One configuration change on their end, after a week of looking on ours.

In enterprise environments, CORS errors are almost always a cross-team problem. The course developer can diagnose them but rarely resolve them alone. You need the API provider and potentially your IT/network team in the same conversation.

2. The name came back as Last, First

Once the API call started working, the greeting sounded wrong. The LMS was returning the learner's name in Lastname, Firstname format — a legacy data structure common in enterprise SCORM implementations.

The fix is one line of JavaScript, but only once you know what to look for:

name.split(", ").reverse()[0]

Split on the comma-space, reverse the array, take the first element. This only surfaced with a real learner account in the actual LMS — local previews and SCORM Cloud don't replicate how an enterprise LMS structures that field.

3. The API key sitting in plain JavaScript

There's no clean solution to this one in a purely client-side architecture. The API key lives in the JavaScript, which means anyone who opens DevTools during course runtime can read it.

The risk calculus depends on context. For a course deployed to internal employees behind SSO, the exposure surface is limited. For external audiences or public-facing deployments, it's a genuine vulnerability that would need a backend proxy to solve properly. In this deployment, the internal nature of the audience made it an acceptable tradeoff — but it's worth being explicit about that decision rather than ignoring it.

4. Names the AI can't pronounce

AI voice models are trained primarily on common Western names. Unusual names, non-Latin-script names transliterated into English, or names with unexpected stress patterns will be mispronounced. The workaround: most voice APIs support phonetic input — you pass a pronunciation hint alongside the name. For a global deployment, this requires a lookup table or override mechanism, which adds complexity.


What the learner actually experienced

On course launch, before any content appeared, the course played: "Hello [First Name], welcome to this eLearning course" — spoken in a natural AI voice, using the actual name pulled from the LMS in real time. No pre-recording. The audio was generated fresh on every launch, for every learner.

What this took that most tutorials won't tell you

The code itself is not the hard part. The environment is the hard part. That's true of almost all AI integration work in L&D — the gap between "this works in a demo" and "this works in your LMS, on your network, for your learners" is where the real engineering lives.


If you're working on something similar — or running into the same walls — connect on LinkedIn. This is the kind of problem worth talking about.