Fixing iSpring + SQL-LRS: a small xAPI handshake problem

Fixing iSpring + SQL-LRS: a small xAPI handshake problem

Some problems don’t look big at first.

The LRS is running.
Statements are coming in.
The content launches fine.

And then:

  • resume stops working
  • state writes start failing
  • “Introduce yourself” shows up again
  • the network tab fills with 401 and 409

This post is about one of those problems, and a small shim that eventually fixed it.

The context

I was integrating iSpring xAPI content into a broader learning platform backed by SQL-LRS (Yet Analytics), with Moodle sitting around it.

On paper, this should be straightforward:

  • iSpring supports xAPI
  • SQL-LRS is spec-correct
  • Moodle just launches the content

In practice, the behavior was inconsistent and hard to reason about.


The symptom

The pattern looked like this:

  • First launch sometimes worked
  • Subsequent launches didn’t
  • PUT /activities/state returned 409 Conflict
  • Sometimes it degraded into 401 Unauthorized
  • Resume never became reliable

Nothing else in the system seemed broken.


What was actually happening

SQL-LRS enforces xAPI state concurrency correctly.

That means:

  • State updates require If-Match with a valid ETag
  • Or If-None-Match: * when creating state for the first time

Some xAPI clients (including iSpring) don’t fully implement this negotiation.
They perform blind PUT requests and assume the server will accept them.

SQL-LRS doesn’t — and shouldn’t.

So this wasn’t really a “bug” on either side.
It was a mismatch in expectations.


Why Moodle couldn’t fix this

The failure happens inside the browser, between the content player and the LRS.

Moodle never sees these requests.
It can’t inject headers or fix concurrency logic after the fact.

This had to be solved at the protocol boundary.


The idea: a small compatibility shim

Instead of relaxing SQL-LRS, I added a very small proxy in front of it.

The shim:

  • Handles ETag negotiation
  • Performs a GET-before-PUT when needed
  • Uses its own trusted credentials to talk to the LRS
  • Leaves everything else untouched

No changes to:

  • iSpring packages
  • Moodle configuration
  • SQL-LRS settings

What changed after that

Once the shim was in place:

  • No more 401
  • No more 409
  • Resume started working
  • State persisted reliably
  • SQL-LRS remained strict and spec-correct

The system became boring again — which is usually a good sign.


Why I open-sourced it

This problem isn’t rare.
It’s just not documented very well.

If you’re combining:

  • iSpring
  • SQL-LRS
  • Moodle
  • or any strict xAPI LRS

you’ll likely run into the same edge case at some point.

So I made the shim public.


The project

https://github.com/sachinravindran/xapi-shim

It’s small, focused, and intentionally unexciting.
It exists to let different parts of a learning stack cooperate without weakening each other.


A closing thought

Most integration issues aren’t caused by bad software.

They’re caused by two correct systems making different assumptions.

Sometimes the right fix isn’t to change either one —
it’s to add a quiet handshake in between.

This shim is one such handshake.