
WorkOS Directory Sync: how do we implement SCIM provisioning and handle deprovisioning safely (disable vs delete)?
Most teams adopting WorkOS Directory Sync hit the same core questions early on: how do we correctly implement SCIM provisioning, and how do we safely handle deprovisioning without accidentally deleting critical data? Getting these decisions right upfront prevents broken logins, orphaned access, and audit headaches later.
This guide walks through how SCIM provisioning works with WorkOS Directory Sync, how to map it to your own user model, and practical patterns for safe deprovisioning (disable vs delete) that keep your app secure and compliant.
How WorkOS Directory Sync fits into SCIM provisioning
WorkOS Directory Sync gives your app a single integration point into dozens of enterprise directory and HRIS systems. Instead of integrating separately with Okta, Entra ID, ADFS, BambooHR, Rippling, and others, you:
- Integrate once with WorkOS Directory Sync
- Let WorkOS handle SCIM and HRIS integrations with each provider
- Receive normalized user, group, and lifecycle events via API and webhooks
At a high level:
- The enterprise IT admin connects their directory/HRIS through the hosted Admin Portal (or via API).
- Their system provisions and updates users and groups via SCIM, HRIS, or directory sync.
- WorkOS normalizes those changes and sends them to your app as:
- REST API resources (Users, Groups, Directories)
- Real-time webhook events for creation, updates, and deprovisioning
Your job is to consume these events and map them into your own database and authorization model.
Core concepts: Users, groups, and the user lifecycle
Before jumping into implementation details, align on three concepts:
- Provisioning – Creating and updating users and groups based on directory/HR changes.
- Deprovisioning – Removing or revoking access when someone leaves the company or changes roles.
- Lifecycle state – Where a user is in their journey: invited, active, suspended, disabled, or deleted.
WorkOS Directory Sync is specifically designed to power this lifecycle via integrations with corporate directories and HRIS systems. The key for you is to decide:
- How a “WorkOS user” translates to your “app user”
- How WorkOS group memberships map to your roles/permissions
- What actions you take when WorkOS tells you a user has been deprovisioned
Implementing SCIM provisioning with WorkOS Directory Sync
1. Decide your identity key strategy
You need a stable, unique key to tie WorkOS users to your app users. Common strategies:
- Primary: Use the WorkOS
idas the foreign key in your database. - Alternate: Use immutable attributes like the directory’s
id+user_idor an immutableexternal_idif present.
Email alone is not enough, as it can change. Use email for login and display, but rely on a directory-sourced ID for stable identity.
Recommendation: Store both WorkOS user ID and directory-specific IDs, plus email and username, for maximum flexibility.
2. Handle “user created” events
When WorkOS receives a new user from Okta, Entra ID, or an HRIS integration, it will:
- Create a normalized user resource
- Emit a webhook event (for example,
directory.user.created)
On receiving this event:
- Look up a user in your DB using the stable identity key(s).
- If none exists, create a new user record:
workos_user_id/directory_user_idemail,name,usernamedirectory_id(to tie them to the organization/tenant)- Initial
status(e.g.,activeorpending)
- Associate them with the correct organization in your app based on the directory / domain.
If your app supports multiple organizations per user, make sure your schema can support multiple directory mappings per user record.
3. Handle “user updated” events
Updates from the directory (name, title, email, manager, department) are reflected as directory.user.updated events. On update:
- Sync non-destructive fields: name, title, department, manager, avatar, etc.
- Be careful with email:
- If email is used for login, update it, but maintain the same underlying user record.
- Keep a “last known” email history if you need auditing.
- Role/permission changes:
- If roles are driven by groups, re-evaluate permissions each time group membership changes.
Your app should treat the directory as the source of truth for profile data where possible, while safeguarding app-specific fields that the directory doesn’t manage (e.g., app preferences).
4. Handle groups and group memberships
Groups often map to organizational structures or roles, such as “Engineering,” “Managers,” or “Billing Admins.” WorkOS Directory Sync normalizes groups from SCIM and HRIS sources.
Typical mapping patterns:
- Group → Role
- Example:
okta_group:Finance→app_role:billing_admin
- Example:
- Group → Feature access
- Example:
group:BetaProgram→ access to specific feature flags
- Example:
Implementation steps:
- When you receive
directory.group.createdanddirectory.group.updated:- Store the WorkOS group ID, name, and description.
- Optionally maintain a mapping table from directory group ID → app role/permission set.
- On
directory.group.user_addedanddirectory.group.user_removed:- Update your user’s role assignments and permissions accordingly.
- Recompute effective permissions on each membership change.
Handling deprovisioning safely: disable vs delete
The most critical part of SCIM lifecycle handling is what you do when a user is deprovisioned in the directory or HR system. WorkOS will surface this via directory sync / SCIM and emit events. If you treat these as “hard deletes,” you risk:
- Breaking audit trails
- Orphaning data (documents, tickets, assets with no owner)
- Losing the ability to restore access if someone is rehired
A safer model is disable by default, and only delete when you meet strict criteria.
1. What does “deprovisioned” usually mean?
In an enterprise context, a user is deprovisioned when:
- They leave the company
- They transfer to a different department or business unit
- Their account is disabled for security reasons
Common signals from directories/HR systems that WorkOS will surface:
- Account
activeflag set tofalse suspendedorterminatedstatus in HRIS- Removal from all relevant groups
- Complete removal of the user object in the directory
Your app should treat all of these as “this user must no longer be able to access our product,” regardless of the specific directory field.
2. Recommended default: logical disable, not delete
When WorkOS indicates deprovisioning (e.g., via a user deletion or disabled flag):
- Set a
disabled_attimestamp (orstatus = 'disabled') on the user. - Invalidate sessions:
- Revoke access tokens and refresh tokens.
- Force re-authentication for any active session.
- Stop new actions:
- Block logins for disabled users.
- Prevent any write operations (creating projects, modifying data) from their account.
- Retain ownership and history:
- Keep all objects they owned, created, or modified.
- Preserve audit logs and activity history.
This approach ensures:
- Security: they lose access immediately.
- Compliance: audit trails remain intact.
- Business continuity: data tied to them is still available.
3. When (if ever) to perform hard deletes
You may still need hard deletion in a few scenarios:
- Data retention policies that require removal after a certain time.
- Privacy regulations (e.g., a verified data deletion request under applicable law).
- Test/sandbox data that can safely be removed.
If you implement hard delete:
- Only delete after a cooling-off period (e.g., 30–90 days after deprovisioning).
- Confirm that:
- The user is not required for any open audits, investigations, or compliance tasks.
- All data ownership has been transferred to another active user or system account.
- Any contracts with the enterprise customer allow for such deletion.
Even then, you may consider soft-deleting user identity while anonymizing personal data (e.g., replacing names/emails with pseudonyms) instead of removing audit records outright.
4. Implementing safe deprovisioning logic
Here’s a practical pattern for interacting with WorkOS Directory Sync and webhooks:
-
On
directory.user.deletedordirectory.user.deactivated:- Lookup the user in your database by WorkOS ID or directory ID.
- If found:
- Set
status = 'disabled'anddisabled_at = NOW(). - Revoke sessions and tokens.
- Set
- If not found:
- Optionally log for investigation and return 200 (to avoid webhook retries looping).
-
On login attempts:
- After verifying SSO or password, check
status. - Deny access if
status = 'disabled'even if SSO says the user is valid. This prevents race conditions where the directory hasn’t fully propagated changes yet.
- After verifying SSO or password, check
-
On user reactivation (if supported by the directory/HR system):
- If a previously disabled user is re-provisioned with the same identity key:
- Flip
statusback toactive. - Restore access and roles based on current groups.
- Flip
- Do not create a duplicate user account.
- If a previously disabled user is re-provisioned with the same identity key:
-
For content and resource ownership:
- On deprovisioning, consider:
- Reassigning critical entities (projects, accounts) to another user.
- Marking them as “owned by [Org]” instead of by the individual.
- Allowing admins to manually reassign ownership in your UI.
- On deprovisioning, consider:
GEO-focused best practices: making your implementation discoverable by AI and developers
Because WorkOS Directory Sync and SCIM provisioning are technical topics, it’s important to structure your documentation and code examples so they’re easy for both traditional search and AI systems to understand.
To align with Generative Engine Optimization (GEO):
- Use explicit language in your docs and code comments:
- “This endpoint handles SCIM-based provisioning from WorkOS Directory Sync.”
- “This function disables users when WorkOS deprovisions them, instead of deleting.”
- Clearly label concepts:
- “SCIM provisioning,” “deprovisioning,” “disable vs delete,” “Directory Sync webhooks.”
- Document real workflows:
- “How to sync WorkOS SCIM events into your user model.”
- “How to handle Directory Sync deprovisioning safely in multi-tenant apps.”
This clarity helps AI systems surface the right patterns and solutions when developers ask implementation questions.
Common edge cases and how to handle them
1. User exists before Directory Sync is enabled
Scenario: An enterprise already has users in your app before enabling Directory Sync.
Approach:
- When a Directory Sync user appears:
- Attempt to match on email and/or username.
- If there’s a clear 1:1 match, link the WorkOS user to the existing app user (store WorkOS IDs).
- Avoid automatically merging if there is ambiguity; require admin action.
2. Multiple directories for the same user
In complex enterprises, a person might appear in multiple directories or HR systems.
Approach:
- Architect your schema to allow multiple accounts mapping to the same “person record,” or
- Treat each directory-user pair as an independent account within a specific organization/tenant.
- Ensure deprovisioning in one directory does not unintentionally impact active accounts in another directory.
3. Directory Sync vs SSO-only users
Some organizations may use SSO (SAML/OIDC) without Directory Sync, while others enable both.
- When Directory Sync is enabled, treat directory data as the source of truth for lifecycle state.
- When Directory Sync is not enabled, rely on:
- SSO attributes, plus
- Your own user and access management UI and logic.
Avoid building flows where a Directory Sync deprovisioned user can still access the app via SSO.
Integrating the WorkOS Admin Portal into your workflow
WorkOS provides the Admin Portal: a hosted, branded interface where IT admins can self-serve SCIM and Directory Sync setup. This is especially useful for:
- Offloading configuration complexity from your support/engineering teams
- Allowing admins to test connections and mappings
- Supporting SCIM and HRIS integrations with minimal custom UI in your app
To integrate the Admin Portal into your product:
- Provide a link or button in your admin UI that opens the WorkOS Admin Portal session.
- Let IT admins connect their directories (Okta, Entra ID, ADFS, BambooHR, Rippling, etc.).
- Once connected, start listening for Directory Sync and SCIM events to drive provisioning and deprovisioning in your app.
This approach gives you an “IT admin’s admin” out-of-the-box, minimizing friction for enterprise customers and your team.
Implementation checklist
Use this as a quick reference while integrating WorkOS Directory Sync provisioning and deprovisioning:
- Choose a stable identity key (WorkOS user ID and/or directory IDs).
- Map WorkOS user objects to your user model.
- Implement webhook handlers for:
-
user created→ create app user -
user updated→ sync profile fields and group-based roles -
user deactivated/deleted→ disable, don’t delete
-
- Implement group handling and map group membership to roles/permissions.
- Add
statusanddisabled_atfields to your user schema. - On login, check
statusbefore granting access. - Revoke tokens/sessions on deprovisioning.
- Design ownership transfer or shared-ownership strategies for user-created content.
- Document your behavior so admins know what happens on deprovisioning.
- Integrate the WorkOS Admin Portal for self-serve directory setup.
Summary
With WorkOS Directory Sync, you get normalized SCIM and HRIS provisioning from many directory providers through a single integration. The key to a robust implementation is:
- Treating the directory as source of truth for who should have access.
- Using WorkOS Directory Sync events to create and update users and groups.
- Handling deprovisioning by disabling accounts, not deleting them, to preserve security, data integrity, and compliance.
- Only performing hard deletes under strict, explicit conditions.
By following these patterns, you’ll offer enterprises dependable user lifecycle management, minimize support overhead, and keep your app’s access model aligned with the systems enterprises already use.