The problem this guide solves is specific: you’ve signed a reseller agreement, you have a tenant provisioned on your vendor’s SaaS LMS, and now you need to make it indistinguishable from a product your company built. That means custom domains, per-client SSO federation, SCORM/xAPI data isolation, and white-labeled transactional email – all without surfacing the vendor’s infrastructure. Most guides cover branding UI panels. This one covers the architecture underneath them. Specifications referenced throughout are current as of xAPI 2.0 (IEEE 9274.1.1-2023), SCORM 2004 4th Edition, LTI Advantage 1.3, and SAML 2.0 (OASIS, March 2005).
Multi-Tenant Architecture: What Your Vendor Is Actually Running
Before configuring anything, understand the isolation model your vendor uses. There are three dominant patterns, and your white-label surface area differs significantly across them:
1. Shared Schema, Row-Level Isolation All clients share a single database schema. A tenant_id column partitions data. Branding, domain routing, and SSO configuration are stored as tenant metadata. This is the most common SaaS LMS model (TalentLMS, Docebo, LearnUpon). Risk: a misconfigured query can leak cross-tenant data. Verify your vendor’s SOC 2 Type II report covers tenant isolation controls.
2. Schema-per-Tenant Each client gets a dedicated database schema within a shared cluster. More isolation, slower provisioning. Used by some Moodle hosting providers (Moodle Workplace) and Totara.
3. Instance-per-Tenant (Pod Isolation) Each client runs a containerized application instance. Maximum isolation, highest infrastructure cost. Rare in pure SaaS; more common when resellers are provisioning on-premise or hybrid deployments.
Your DNS, SSL, and SSO configuration steps are structurally identical regardless of which model your vendor uses – but the blast radius of a misconfiguration is not.
Custom Domain Configuration: DNS, CNAME, and SSL
This is where most reseller implementations break first. The goal is to route learn.yourclient.com to your vendor’s infrastructure without exposing the vendor’s hostname in the browser address bar or TLS certificate.
DNS Configuration
Your vendor will give you a CNAME target, typically something like:
# Vendor-provided CNAME target (example)
your-reseller-id.lms-vendor.io
At your client’s DNS registrar (or your DNS zone if you own the domain), create:
# CNAME record
learn.yourclient.com. CNAME your-reseller-id.lms-vendor.io.
TTL: 300 (during cutover), then 3600
Critical: Some LMS vendors require an A record instead of a CNAME at the apex domain (yourclient.com). CNAMEs at the zone apex violate RFC 1034. Use ALIAS (Route 53), ANAME (Cloudflare CNAME flattening), or a subdomain (learn.) exclusively.
SSL/TLS Certificate Provisioning
Vendors handle this one of two ways:
| Method | How It Works | Your Action Required |
|---|---|---|
| Vendor-managed ACME (Let’s Encrypt) | Vendor auto-provisions cert after CNAME propagation is confirmed | Point CNAME, wait 24–48h, verify in vendor admin |
| Bring Your Own Certificate (BYOC) | You generate a CSR, obtain cert from CA, upload to vendor portal | Upload PEM-encoded cert + private key + chain |
| Wildcard Certificate | Vendor holds *.lms-vendor.io; you must BYO cert for custom domains | BYOC required |
For resellers managing 10+ clients, automate cert provisioning using Let’s Encrypt with DNS-01 challenges via certbot or acme.sh. This avoids HTTP-01 challenge failures that occur before DNS propagation completes.
# DNS-01 challenge via acme.sh (Cloudflare DNS provider)
acme.sh –issue \
–dns dns_cf \
-d learn.yourclient.com \
–keylength 2048
Email Domain (SMTP) White-Labeling
Every transactional email your LMS sends (enrollment confirmations, password resets, certificates) must originate from your client’s domain – not noreply@lms-vendor.io. Configure:
SPF: TXT “v=spf1 include:_spf.lms-vendor.io ~all”
DKIM: TXT key published at selector._domainkey.yourclient.com
DMARC: TXT “v=DMARC1; p=quarantine; rua=mailto:dmarc@yourclient.com“
Vendors supporting custom SMTP will either let you supply your own SMTP credentials (preferred for deliverability control) or provision DKIM signing keys per tenant domain.
SSO Federation Architecture for Multi-Tenant Resellers
This is the deepest technical surface area in any white-label deployment. Each of your clients has its own Identity Provider (IdP) – Okta, Microsoft Entra ID, Google Workspace, Ping Identity – and your LMS must act as a SAML 2.0 or OIDC Service Provider (SP) scoped per tenant.
SAML 2.0 Tenant Mapping
The LMS must distinguish which tenant’s IdP to redirect a given user to. This is resolved via one of two mechanisms:
Domain-hint routing: User lands on learn.yourclient.com; the LMS resolves tenant from hostname and redirects to the registered IdP metadata URL.
Email domain routing: User enters email on shared login page; LMS extracts domain (@yourclient.com) and maps to tenant’s IdP.
A valid SAML 2.0 AuthnRequest your LMS (as SP) generates will look like:
<samlp:AuthnRequest
xmlns:samlp=”urn:oasis:names:tc:SAML:2.0:protocol”
xmlns:saml=”urn:oasis:names:tc:SAML:2.0:assertion”
ID=”_a1b2c3d4e5f6″
Version=”2.0″
IssueInstant=”2026-04-02T10:00:00Z”
Destination=”https://login.microsoftonline.com/{tenant-id}/saml2″
AssertionConsumerServiceURL=”https://learn.yourclient.com/saml/acs”
ProtocolBinding=”urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST”>
<saml:Issuer>https://learn.yourclient.com/saml/metadata</saml:Issuer>
<samlp:NameIDPolicy
Format=”urn:oasis:names:tc:SAML:2.0:nameid-format:emailAddress”
AllowCreate=”true”/>
</samlp:AuthnRequest>
Key fields for reseller configuration:
- AssertionConsumerServiceURL must match the registered ACS URL in the client’s IdP exactly – including trailing slashes
- Issuer is the SP Entity ID; per-tenant custom domains require per-tenant Entity IDs
- Destination is the client’s SSO endpoint, retrieved from their IdP metadata XML
OIDC/OAuth 2.0 (Modern Alternative)
For clients running Okta, Google Workspace, or Azure Entra with OpenID Connect, the LMS registers as an OIDC Relying Party. Per-tenant configuration requires:
{
“client_id”: “your-lms-client-id”,
“client_secret”: “stored-in-vault”,
“redirect_uri”: “https://learn.yourclient.com/auth/callback“,
“authorization_endpoint”: “https://login.microsoftonline.com/{tenant}/oauth2/v2.0/authorize“,
“token_endpoint”: “https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token“,
“scopes”: [“openid”, “email”, “profile”]
}
Use Authorization Code + PKCE flow for browser-based LMS clients (RFC 7636). Avoid Implicit flow – deprecated per OAuth 2.0 Security Best Current Practice (RFC 9700).
JIT Provisioning and Attribute Mapping
Just-In-Time (JIT) user provisioning creates learner accounts on first SSO login from IdP attributes. Map these SAML attributes to LMS user fields in your vendor’s admin panel:
| SAML Attribute | LMS Field | Notes |
|---|---|---|
| NameID (email format) | username / email | Primary unique identifier |
| http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname | first_name | Microsoft Entra format |
| http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname | last_name | |
| department | group or custom field | For auto-enrollment rules |
| jobTitle | custom field | Optional |
SCIM 2.0 (RFC 7644) is the preferred alternative to JIT for enterprises needing pre-provisioning, deprovisioning, and group sync. If your LMS vendor supports SCIM, configure the SCIM endpoint and bearer token in your client’s Okta/Entra provisioning tab.
eLearning Standards Compatibility Matrix for White-Label Resellers
This matrix covers the practical behavior of core standards under white-label conditions – specifically the cross-domain implications that surface when your branded domain differs from your vendor’s content CDN.
| Standard | Version | Runtime Communication Method | Cross-Domain Risk | White-Label Mitigation |
|---|---|---|---|---|
| SCORM 1.2 | ADL 1.2 (2001) | JS window.parent.API object | High – same-origin required | Reverse proxy or vendor CDN aliased to custom domain |
| SCORM 2004 | 4th Edition (2009) | JS window.parent.API_1484_11 | High – same-origin required | Same as SCORM 1.2 |
| xAPI 1.0.3 / 2.0 | IEEE 9274.1.1-2023 | HTTP POST to LRS endpoint | Low – CORS-enabled by design | Verify LRS Access-Control-Allow-Origin header |
| cmi5 | ADL cmi5 Spec (2016) | xAPI + launch parameters | Low | AU must send sessionId from launch URL |
| LTI 1.3 / Advantage | IMS Global 1.3 (2019) | Signed JWT, server-to-server | None – no JS bridge | Configure platform OIDC registration per tool |
| AICC | AICC HACP (1998) | HTTP POST (HACP protocol) | Medium | Ensure HACP URL uses custom domain |
SCORM Cross-Domain: The Reseller-Specific Problem
SCORM’s runtime relies on JavaScript traversing the window.parent chain to find the LMS API object (API for 1.2, API_1484_11 for 2004). Browsers enforce the Same-Origin Policy (SOP), which blocks this traversal when content and LMS are on different origins.
In white-label deployments, this manifests when:
- Content is hosted on the vendor’s CDN (cdn.lms-vendor.io) but the LMS runs on learn.yourclient.com
- SCORM launches in a new window with the wrong origin
The correct architectural fix is a reverse proxy, not CORS headers. CORS cannot solve SCORM’s JS parent-window traversal because it’s not an XHR/fetch request.
# Nginx reverse proxy – content served under client’s domain
server {
listen 443 ssl;
server_name learn.yourclient.com;
location /content/ {
proxy_pass https://cdn.lms-vendor.io/tenants/your-tenant-id/;
proxy_set_header Host $host;
proxy_hide_header X-Powered-By;
# Ensures browser sees same origin for SCORM parent traversal
}
}
If you cannot control infrastructure, use xAPI (cmi5) instead of SCORM for any new content. xAPI sends statements to an LRS endpoint via authenticated HTTP – cross-domain by design, with no JS parent-window traversal required.
xAPI LRS Isolation in Multi-Tenant Deployments
Each client’s xAPI data must be stored in an isolated LRS partition. Verify your vendor’s LRS architecture:
- Tenant-scoped LRS: All tenants share one LRS URL but data is partitioned by registration GUID and tenant context. Lower isolation.
- Per-tenant LRS endpoint: Each tenant has a unique LRS base URL (e.g., https://lrs.lms-vendor.io/tenants/{tenant-id}/). Data isolation enforced at the HTTP layer. Preferred.
An xAPI statement structure for a white-labeled deployment should include the context.platform field set to your branded LMS name, not the vendor’s:
{
“actor”: { “mbox”: “mailto:learner@yourclient.com“, “objectType”: “Agent” },
“verb”: { “id”: “http://adlnet.gov/expapi/verbs/completed“, “display”: {“en-US”: “completed”} },
“object”: { “id”: “https://learn.yourclient.com/courses/safety-101“, “objectType”: “Activity” },
“context”: {
“platform”: “YourBrand LMS”,
“registration”: “f47ac10b-58cc-4372-a567-0e02b2c3d479”,
“extensions”: {
“https://learn.yourclient.com/ext/tenant“: “yourclient”
}
}
}
Implementation Steps for Reseller White-Label Deployment
Phase 1: Tenant Provisioning and Domain Routing (Days 1–3)
- Submit tenant provisioning request to vendor with: client name, primary admin email, expected user count, content standards required (SCORM 1.2 / 2004 / xAPI), geographic data residency requirement (EU, US, APAC).
- Receive vendor-assigned tenant ID and CNAME target.
- Create DNS CNAME record at client’s registrar. TTL: 300.
- Trigger SSL provisioning in vendor admin portal (or upload BYO cert if required).
- Verify domain resolution: dig learn.yourclient.com CNAME → should return vendor CNAME target.
- Verify SSL: openssl s_client -connect learn.yourclient.com:443 -servername learn.yourclient.com → cert CN must match learn.yourclient.com.
Phase 2: Branding Layer Configuration (Days 3–5)
Most enterprise LMS platforms expose branding through CSS custom properties. Inject client-specific variables via the vendor’s custom CSS panel:
/* Example: custom CSS panel in vendor admin */
:root {
–brand-primary: #1A3C6E;
–brand-secondary: #F4A300;
–brand-font: ‘Client Brand Font’, sans-serif;
–logo-url: url(‘https://learn.yourclient.com/assets/logo.svg’);
}
.lms-header__logo { background-image: var(–logo-url); }
.btn-primary { background-color: var(–brand-primary); }
Configure: platform name, favicon, login page background, email sender name and domain (SMTP or DKIM), and footer copyright text. Suppress all vendor attribution in footer, email templates, and course player chrome.
Phase 3: SSO Configuration (Days 5–10)
- Obtain IdP metadata XML from client’s IT team (Okta, Entra, Ping, etc.).
- Register SP metadata with client’s IdP: upload your LMS SP metadata XML containing Entity ID and ACS URL for the client’s custom domain.
- Configure attribute mapping in LMS SSO settings (see attribute table above).
- Enable JIT provisioning; set default role for new SSO users (typically Learner).
- Test with a sandbox account: SP-initiated flow (user visits learn.yourclient.com) and IdP-initiated flow (user clicks tile in Okta/Entra). Validate both paths.
Phase 4: Content Standards and LRS Configuration (Days 7–14)
- Upload a test SCORM 1.2 package and verify completion tracking on custom domain.
- Upload a test SCORM 2004 4th Edition package; confirm API_1484_11 is found by JS runtime (check browser console for “Unable to acquire SCORM API” errors).
- If cross-domain content hosting is required, configure reverse proxy (see nginx config above) or confirm vendor CDN aliasing.
- For xAPI content: configure LRS endpoint URL, key, and secret in authoring tool (Articulate 360, Adobe Captivate, iSpring). Verify statement delivery with browser Network tab – look for HTTP 200 responses to /statements endpoint.
- Confirm LRS data isolation: tenant A’s learner statements must not appear in tenant B’s reporting dashboard.
Phase 5: Reseller Admin Hierarchy and Sub-Admin Provisioning
White-label reseller programs typically provide a super-admin or partner admin role that sits above client-level admins. Configure:
- Reseller admin: Full access across all client tenants; can create/delete client portals; sees aggregated billing and usage.
- Client admin: Scoped to single tenant; manages users, content, and reports within their portal only.
- Learner: No admin access; enrolled into courses by client admin or via SSO group mapping.
Common Issues and Fixes
1. Unable to Acquire LMS API / SCORM Tracking Not Recording
Cause: SCORM content is served from a different origin than the LMS host (Same-Origin Policy violation). The JavaScript parent-window traversal fails silently or throws a SecurityError. Fix: Verify content and LMS are on the same origin. Use a reverse proxy to alias the vendor CDN under the client’s custom domain. Test in SCORM Cloud first – if it works there but not on the custom domain, the issue is origin isolation, not the package itself. (Source: Rustici Software Knowledge Base)
2. SSO Loop – Redirecting Indefinitely Between LMS and IdP
Cause: ACS URL registered in the IdP does not match the AssertionConsumerServiceURL in the SAML AuthnRequest (common after a custom domain cutover where the old vendor domain was still registered). Fix: Update the ACS URL in the client’s IdP application (Okta → Sign On → SAML Settings; Entra → Enterprise Applications → Single Sign-On → Basic SAML Configuration). Entity ID and ACS URL must both reflect the new custom domain.
3. SSL Certificate Warning on Custom Domain
Cause: DNS CNAME propagation completed before the vendor provisioned the TLS certificate, or a cached HTTP response is serving on port 443 with the vendor’s wildcard cert. Fix: Check certificate CN with openssl s_client (see Phase 1, Step 6). If the cert shows the vendor domain, the vendor’s ACME provisioning hasn’t completed. Wait 24–48 hours, or manually trigger re-provisioning in the vendor admin portal. Never force users to the custom domain before SSL is confirmed.
4. Emails Sent From Vendor Domain Despite SMTP Configuration
Cause: Custom SMTP settings in the vendor portal are overridden by a platform-level email relay. Some vendors only support custom FROM addresses at higher plan tiers. Fix: Verify your reseller plan tier includes custom SMTP or DKIM signing. Check vendor documentation for per-tenant email configuration scope. If custom SMTP isn’t supported, configure a DKIM record so the vendor’s relay signs emails on behalf of yourclient.com, which resolves DMARC failures even without a custom SMTP server.
5. xAPI Statements Not Delivered to LRS (CORS Error)
Cause: The LRS endpoint is not returning Access-Control-Allow-Origin headers, or the authoring tool is sending xAPI to the vendor’s default LRS URL rather than the tenant-scoped LRS endpoint. Fix: Inspect Network tab for OPTIONS preflight requests to the LRS endpoint. The LRS must return:
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: X-Experience-API-Version, Authorization, Content-Type
X-Experience-API-Version: 2.0
Reconfigure the authoring tool to point to the correct tenant-scoped LRS URL.
6. Sub-Admin Cannot Access Newly Created Client Portal
Cause: Sub-admin role permissions were copied from the reseller’s default template, which lacks the new portal in its scope. Some LMS platforms require explicit tenant-to-admin assignment rather than inheriting from a role. Fix: Navigate to reseller admin panel → User Management → select sub-admin → Portal Access → explicitly assign the new client portal. Role-level tenant scoping and user-level tenant assignment are independent controls in most platforms.
TIP FROM THE FIELD - For LMS Administrators:
When managing 20+ client portals as a reseller, the single highest-impact time-saver is building a standardized provisioning checklist as a runbook – not a document, an actual scripted checklist integrated into your onboarding workflow. Specifically: track SSL provisioning status, SSO test completion, SCORM smoke test result, and email deliverability check (DKIM/SPF pass) as gated steps before handing any portal to a client. The most common support escalation from newly onboarded clients is SCORM content not tracking – which is almost always a domain/SSL issue that was never verified before go-live. SCORM Cloud (cloud.scorm.com) is your canonical diagnostic baseline: if a package tracks correctly in SCORM Cloud but not on your branded domain, the problem is infrastructure (origin, proxy, SSL), not the content package.