Why HR System Integrations Are Still a Dumpster Fire in 2026
I've built integrations with ADP, Workday, Okta, and Entra ID for the past decade. The good news: SSO works. The bad news: everything else is held together with duct tape and prayer.
🧠 The Promise vs. The Reality
The pitch is simple: connect your identity provider to your HR system, and user provisioning becomes automatic. New hire? They get access instantly. Termination? Access revoked across all systems in minutes.
The reality? You're manually mapping custom fields, writing transformation logic for every edge case, and debugging why Sarah from Accounting lost access to Salesforce when she changed departments.
As an Integration Engineer, I've seen this pattern repeat across enterprises: SSO is solved tech, but identity lifecycle management is still a mess.
⚙️ The SCIM Lie
SCIM (System for Cross-domain Identity Management) was supposed to fix this. It's a standard protocol for user provisioning. Everyone implements it. Nobody implements it the same way.
Example: Okta to Workday
# Okta SCIM payload
{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
"userName": "jdoe@example.com",
"name": {
"givenName": "John",
"familyName": "Doe"
},
"emails": [{"value": "jdoe@example.com", "primary": true}],
"active": true
}
# What Workday actually expects
{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
"userName": "jdoe@example.com",
"name": {
"givenName": "John",
"familyName": "Doe",
"formatted": "Doe, John" # Required but not in spec
},
"emails": [{"value": "jdoe@example.com", "type": "work"}],
"active": true,
"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User": {
"employeeNumber": "12345", # Not sent by default
"department": "Engineering",
"manager": {"value": "manager-uuid"} # Must exist first!
}
}See the problem? Both claim SCIM compliance. Neither works out of the box. You end up building middleware that transforms payloads, handles retry logic, and manages dependency ordering (create manager account before employee).
🚀 What Actually Works
After years of building these integrations at CTM and my employer, here's what I've learned:
1. Build for eventual consistency
HR systems are slow. Workday takes 15 minutes to provision a user. ADP can take hours. Don't build synchronous flows—you'll hit timeouts and confuse everyone.
Instead: queue the request, return a job ID, poll for completion. Store state in a database, not memory.
# Anti-pattern: synchronous provisioning
def create_user(user_data):
okta_user = okta.create(user_data) # 2 seconds
workday_user = workday.create(user_data) # 15+ minutes (timeout!)
return okta_user, workday_user
# Pattern: async with job tracking
def create_user(user_data):
job_id = str(uuid4())
db.save_job(job_id, status='pending', data=user_data)
queue.enqueue('provision_user', job_id)
return {'job_id': job_id, 'status_url': f'/jobs/{job_id}'}
def provision_user(job_id):
job = db.get_job(job_id)
try:
okta_user = okta.create(job.data)
db.update_job(job_id, okta_id=okta_user.id, status='okta_complete')
workday_user = workday.create_with_retry(job.data, max_attempts=5)
db.update_job(job_id, workday_id=workday_user.id, status='complete')
except Exception as e:
db.update_job(job_id, status='failed', error=str(e))2. Treat every API as unreliable
ADP goes down during payroll runs. Workday throttles aggressively. Okta rate-limits without warning. Your integration needs:
- Exponential backoff with jitter
- Circuit breakers (stop hammering failing services)
- Idempotency (safe to retry the same request)
- Dead letter queues (quarantine poison messages)
3. Observability from day one
You can't debug what you can't see. Every integration needs:
- Request/response logging (sanitized—no passwords!)
- Metrics (success rate, latency, queue depth)
- Distributed tracing (follow a user creation across 5 systems)
- Alerting (when job queue exceeds 100, page someone)
I run Grafana + Loki for all CTM integrations. When Workday's API started returning 500s last month, I knew within 90 seconds and had logs to prove it wasn't our code.
💡 The Authentik Advantage
For my homelab and CTM's internal tools, I switched to Authentik SSO. It's open source, self-hosted, and actually implements SCIM correctly.
Why it works:
- Full control: I can patch the code when vendors do weird things
- Free: No per-user licensing (Okta would charge me $10k/year for my setup)
- Flexible: Python expressions for attribute mapping, not GUI hell
- Private: User data stays on my infrastructure
I run it in Docker on ctmprod, behind Traefik with automatic Let's Encrypt certs. 48 services authenticate through it—Grafana, n8n, Windmill, Ghost, you name it. Single sign-on for everything.
📊 The Economics of Integration
Here's what enterprises don't tell you: HR integrations are never 'done.' They require constant maintenance:
- Workday updates their API quarterly (breaking changes monthly)
- ADP deprecates endpoints without notice
- Entra ID changes field requirements based on tenant config
- Every M&A event requires re-mapping org structures
At my employer, we budget 20% of integration engineering time to 'keeping the lights on.' That's the tax you pay for having 15 SaaS systems talk to each other.
🧠 Lessons Learned
- SCIM is a guideline, not a spec. Every vendor interprets it differently. Budget time for transformation logic.
- Build for async from day one. Synchronous provisioning will bite you at scale.
- Observability is non-negotiable. You'll spend more time debugging than coding.
- Self-hosted SSO is underrated. Authentik saved me $10k/year and gave me more control.
- Test in production. Sandbox environments lie. Workday's test instance behaves nothing like prod.
- HR teams are your allies. They know where the bodies are buried. Work with them early.
Identity integration is unsexy infrastructure work. But when it works, new hires are productive on day one, and terminated employees lose access instantly. That's the difference between compliance theater and actual security.