Why this guide exists
When a learner launches a course from their LMS, you need to answer:- Who is this user?
- Which end-customer do they belong to?
- Are they authorized to view this specific course?
Recommended architecture
We recommend a hub-and-spoke model where both the LMS and your platform are SAML Service Providers trusting your end-customer’s enterprise Identity Provider (Okta, Entra ID, Google Workspace, ADFS, etc.). The launch flow: The learner sees a brief redirect and lands in the content. No login screen.Kombo is not in the authentication path. The seamless experience comes
from the IdP’s existing session, not from anything Kombo does at launch time.
Your platform’s uptime during course launch depends only on your
infrastructure and your end-customer’s IdP – not on ours.
Why the hub-and-spoke model (and not LMS-as-IdP)
Some LMSs (e.g. SAP SuccessFactors via its OCN framework) can act as SAML Identity Providers themselves. In that model, your platform would configure a SAML trust relationship with each end-customer’s LMS instance instead of with their enterprise IdP. We recommend the enterprise IdP path as the default:- Enterprise customers already use their IdP for every other SaaS app. Using it for your platform too is consistent with how they manage identity everywhere else.
- The enterprise IdP is more stable than the LMS. LMS instances change, get migrated, or get replaced; the IdP tends to outlive all of them.
- Some LMSs (notably Workday Learning) can’t act as an IdP at all, so you’ll need the enterprise IdP path for those customers regardless.
Identity matching: the actual Kombo-specific part
Authentication alone isn’t enough. Once the learner is authenticated via SSO, your platform has some identity for them – usually an email, maybe a name. But to call Kombo’s API on their behalf (checking enrollments, writing completions), you need to map this authenticated user to a Kombo user. What you need is a shared identifier that:- Exists in both the SAML assertion and in Kombo’s synced user data
- Is unique and stable
- Comes through reliably at every launch
remote_id field on each user returned from GET /lms/users. If your platform receives the same identifier in the SAML assertion, you can:
- Look the user up via
GET /lms/users?remote_ids=E12345and use the Komboiddownstream - Or, on action endpoints (e.g. enroll, complete), use the
remote:prefix (e.g.user_id: "remote:E12345") to skip the lookup entirely
remote_id actually holds, per LMS:
- SAP SuccessFactors – the Learning
studentID(as displayed in Learning admin). Many SF tenants are configured so this equals the Employee Central user ID, but that’s a per-tenant choice, not a guarantee – confirm with the end-customer’s IT team which identifier their IdP should pass. - Workday Learning – the Workday WID, pulled from
Worker_Reference. This is the master identifier Workday uses for the worker across its systems, so customers who already surface a Workday identifier to their IdP are likely passing the WID already. Note that it’s not the human-readable Employee ID – if the end-customer’s IdP only has the Employee ID, that won’t match, and you’ll need to either have the WID surfaced as an attribute or fall back to email. - Cornerstone OnDemand – the system’s internal user ID.
For the LMSs above,
remote_id is a single clean primary key. Elsewhere in
Kombo it can occasionally be a composite when the remote system has no single
stable key – if you ever see that for an LMS user, parse out the relevant
component and match on it, or fall back to email if
that isn’t possible. We’re happy to help figure out the specific
case.Where the shared identifier comes from
This varies based on how your end-customer has set up their identity architecture. The three common cases: Case A – Enterprise IdP with HRIS as source of truth. Most common for corporate customers. The identifier (typically an employee identifier – see the per-LMS notes above for which specific field matches) is synced from the HR system into the IdP via SCIM or directory sync. To get it into the SAML assertion to your platform, the end-customer’s IT team configures the SAML app for your platform to include the identifier as a claim. Case B – Enterprise IdP with the LMS maintaining its own user database. The LMS user ID may not exist in the IdP at all. Either the end-customer has configured a sync between their LMS and their IdP (so the LMS ID is available as an attribute), or they haven’t – in which case you’ll need to fall back to a weaker identifier like email. Case C – LMS itself is the IdP. The SAML assertion comes directly from the LMS. The LMS user ID is naturally available in the assertion, but you need to know where to look for it (theNameID element vs. a specific custom attribute).
The guide below applies to all three cases – what changes is the conversation your team has with the end-customer’s IT team during onboarding.
Three tiers of identity configuration
In order of preference, there are three ways to get the shared identifier into the SAML assertion. Recommend theNameID approach during onboarding, fall back to a custom SAML attribute or email matching as needed.
Tier 1: NameID (recommended)
The end-customer configures their IdP so the SAMLNameID element carries the LMS user ID.
NameID from the assertion and use it directly as the user’s remote_id when calling Kombo’s API. No additional configuration on your platform.
This is the cleanest setup, but it’s often not available – NameID is frequently already mapped to email for other SAML apps your end-customer uses, and changing it would break those.
Tier 2: Custom SAML attribute (alternative)
IfNameID is taken, the end-customer configures a custom SAML attribute with the LMS user ID. A conventional name is lms_user_id, but it can be whatever fits their naming scheme.
Tier 3: Email fallback
If neither theNameID nor custom-attribute approach is possible (e.g. the end-customer’s IdP doesn’t have the LMS user ID available at all, or their IT policy won’t allow custom attributes), you’ll have to fall back to matching on email.
Call Kombo’s users endpoint with the work_emails filter:
id from the response and use it for subsequent API calls. Email matching is case-insensitive.
What your engineering team needs to implement
Your platform already accepts SAML assertions from enterprise IdPs – we’re not going to document that here. The Kombo-specific pieces are:- Extract the shared identifier from
NameID, the configured custom SAML attribute, or the email – whichever your end-customer’s IdP provides. - Use the identifier with Kombo’s API.
- When you have the LMS-side user ID (from
NameIDor a custom attribute), pass it directly on action endpoints like enroll using theremote:prefix (e.g.user_id: "remote:E12345"); the same prefix works forcourse_revision_id. OnGETfilter endpoints, resolve the Kombo user once viaGET /lms/users?remote_ids=E12345and reuse the returnedidon subsequent calls. - When you only have the email, resolve via
work_emailsfirst.
- When you have the LMS-side user ID (from
Conceptual example: custom SAML attribute setup
A concrete scenario to ground the tiers: Setup:- End-customer uses an enterprise IdP (e.g. Entra ID, Okta).
- Their LMS is an enterprise system where the user has a stable LMS-side ID (e.g. SuccessFactors
studentID, Workday WID). - That ID is available in the IdP as a user attribute (synced from their HR system or LMS).
NameIDis already mapped to email for other SAML apps, so they expose the LMS user ID as a custom attribute instead.
- Add your platform as an Enterprise Application / SAML app in their IdP.
- Configure the SAML SSO settings (your entity ID, ACS URL, signing certificate).
- In the attributes/claims section, add a custom claim named
lms_user_idmapped to the user attribute containing the LMS user ID. - Assign the relevant users or groups to the application.
- On the enroll endpoint, pass
user_id: "remote:E12345"directly. For complete, use thecourse_progression_idreturned by enroll. - On
GETendpoints with filters, resolve first:GET /lms/users?remote_ids=E12345→ use the returned Komboid.
The specific attribute names, UI paths, and setup steps vary by IdP and by how
the end-customer has provisioned their users. The end-customer’s IT team will
know their setup best – the guide above is the template, not a prescriptive
script.
Authorization: identity doesn’t solve everything
With everything above in place, you know who the learner is and can make API calls on their behalf. What you still don’t know is whether they’re actually authorized to view the specific course they clicked. There are two patterns we see content providers use, depending on how their end-customers work:Pattern 1: Learning library (SSO is the gate)
The LMS admin publishes content broadly and any authenticated employee can view it. SSO handles authentication and, by extension, authorization – if you’re in the IdP, you’re an employee, and you can view the library. This is the simplest pattern and works well for self-serve content. No enrollment checks needed on your side.Pattern 2: Assigned content (enrollment is the gate)
For compliance training, onboarding, or role-specific courses, the LMS admin assigns specific courses to specific users. Your platform should check that the learner was actually assigned the course before serving it. Use Kombo’s course progressions endpoint, filtering by user and course. A few things to know about this endpoint:- Filter by Kombo IDs.
user_idsandcourse_idsonGET /lms/course-progressionstake Kombo IDs. Since the SAML assertion gives you an LMS-side user ID, resolve it once withGET /lms/users?remote_ids=E12345and reuse the returnedidon subsequent calls. - Progressions live at the revision level. Each progression row has a
course_revision_id(a specific version of a course), not acourse_iddirectly. Thecourse_idsfilter takes the parent course ID and internally matches progressions on all of that course’s revisions – which is what you want 99% of the time. - Store the Kombo course ID at upsert time. Bulk upsert is an async endpoint:
POST /lms/courses/bulkreturns atask_id, and you pollGET /lms/courses/bulk/{task_id}until completion to get each course’s Komboidalongside theorigin_idyou supplied. Persist that mapping alongside your own course data. Subsequent enrollment checks can then resolve your internal course ID to the Kombo course ID directly from your own store. If the mapping is ever lost,GET /lms/coursesreturnsorigin_idin the payload. So in practice, the check looks like:
user_id and course_revision_id directly (use the remote: prefix to pass LMS-side IDs without a separate lookup). Note that enroll is keyed on course_revision_id, not the parent course_id used in the filter above – grab the revision id from the course response or from a progressions row. Complete takes a course_progression_id path parameter; reuse the Kombo ID returned by enroll, or resolve it via GET /lms/course-progressions.
Grant access when the progression status is ENROLLED, IN_PROGRESS, or COMPLETED. Anything else – or no progression at all – should usually mean no access.
The sync delay caveat. Because Kombo syncs enrollment data periodically
rather than fetching it in real time, there’s a gap between when an admin
assigns a course and when Kombo has the data. The exact gap depends on the LMS
and on the sync frequency configured for your integration. Expect it to be on
the order of minutes to a few hours for most LMSs.
- Treat the enrollment check as the authoritative gate.
- If the check fails (no progression found), show the learner an error state that explains the likely cause – for example: “This course isn’t available to you yet. If your administrator just assigned it, please check back in a short while.”
- Log these cases so you can tell the difference between genuine “unauthorized” attempts and sync-delay misses.
FAQ / gotchas
What if the end-customer’s IdP can’t pass custom attributes? Very rare in practice – all major enterprise IdPs support this. If you hit a genuine blocker, fall back to email matching. What about users who exist in the LMS but not in the IdP (e.g. contractors, external trainees)? These users won’t be able to launch courses via SSO at all. Depending on your product, you may want to offer a separate login flow for these users, or work with the end-customer to ensure all learners are in the IdP. My end-customer passes the Workday Employee ID in SAML, butremote_id on /lms/users doesn’t match. Why? Workday Learning keys its user records on the WID (an internal Workday ID from Worker_Reference), not the human-readable Employee ID. Have the end-customer surface the WID as an additional IdP attribute so it reaches your platform in the SAML assertion – no change needed on your side beyond reading the new attribute.
Does the SAML trust relationship need to be set up per end-customer? Yes. Each end-customer has their own IdP and their own SAML metadata. Your platform needs a tenant-aware SAML setup where each end-customer has their own SP configuration. Most enterprise SaaS products already have this; if yours doesn’t, consider a service like WorkOS or Auth0 that federates many IdPs behind a single integration.
Can I avoid the per-end-customer SAML setup? Not really – identity is inherently per-customer. The configuration work moves to whoever owns the trust relationship (your platform, WorkOS, etc.), but it doesn’t disappear.
What if my end-customer can’t set up SSO at all (or no viable identifier comes through)? You have two fallback paths. First, you can show your own login screen after the redirect – not ideal for the first launch, but if you keep session lifetimes generous (weeks or months), repeat launches are seamless. Some content providers run this way in production and report it’s acceptable in practice; the friction is concentrated on the first click, not every visit. Second, depending on your product, you might offer a one-time account claim flow (learner enters email, receives verification link, creates credentials) which bridges the first login a bit more smoothly.
Next steps
Content providers guide
The broader integration flow: syncing users, pushing courses, and writing
completions back.
LMS API reference
Detailed endpoint documentation for users, courses, and course progressions.