Skip to content
BuildBase

Enterprise provisioning

Let a customer company's directory create, update and remove its people in your app, through WorkOS Directory Sync in your own WorkOS account.

Provisioning lets a company you sell to manage its people in your app from its own directory: Okta, Microsoft Entra ID, Google Workspace, an HR system, or anything that speaks SCIM. When their IT team adds someone to the app in the directory, that person gets an account in your app, joins the right workspaces with the right role, and can sign in with Continue with SSO. When the IT team removes them, they are blocked here, signed out everywhere, and taken out of the workspaces the directory put them in.

This builds on Enterprise SSO for the company: set that up first, if you have not.

It is part of enterprise SSO and runs through the same WorkOS account.

Provisioning uses WorkOS Directory Sync, which WorkOS bills to your WorkOS account separately from single sign-on. See WorkOS pricing.

Before you start

An enterprise with a verified domain and enterprise sign-in turned on (see enterprise SSO), a plan that includes Directory Sync, and an owner or admin role in the BuildBase console.

1. Turn it on for the enterprise

Open the enterprise in Authentication, Enterprise, and turn on Provision people from the directory. The console asks first and shows the cost notice above.

Turning it on does three things: it starts following the enterprise's directory events from now (not the months before), finds a directory the IT admin may have connected already, and loads that directory's people once.

2. Hand the directory to the customer's IT admin

Set up provisioning opens the WorkOS setup portal on the provisioning step, in a new tab, for five minutes. You can also send the invitation by email with Provisioning ticked, or the company's own workspace admins can open it from your app (below). The IT admin picks their directory, and the portal gives them a SCIM endpoint and a bearer token to paste into it. See the page for IT admins.

Once the directory is connected, the enterprise shows it as Linked, with when it last synced.

The console showsWhat it means
LinkedThe directory is connected and its changes reach your app
ValidatingWorkOS is still checking the connection
Invalid credentialsThe directory refused WorkOS's credentials; the IT admin reconnects it in the portal
UnlinkedNothing has arrived from the directory yet, or it was disconnected. A new directory stays unlinked until the IT admin finishes connecting it and it sends its first change
DeletingThe directory is being removed

3. Choose where people land

Provisioned people join the enterprise's bound workspaces (its Workspaces section, as for SSO), once, as members the enterprise manages.

Roles from directory groups decides their role. Pick a directory group, then the role it gives in the app or in one workspace:

  • A person in several mapped groups gets the highest role any of them gives: admin over user in the app, admin over editor over viewer in a workspace. A custom role needs a rank to compare it with those (user 10 and admin 20 in the app; viewer 10, editor 20 and admin 30 in a workspace).
  • A group never makes anyone an owner.
  • Leaving the group takes the role back: to the enterprise's role for new accounts, or the bound workspace's role.
  • A role you change by hand stands until the groups next disagree with it. The person shows Role changed by hand in the enterprise's People, and the next sync puts the mapped role back.
  • A mapping gives a role in a workspace the person is a member of; it does not add them to one. Binding does that.

Saving the mappings applies them to everyone already provisioned within a minute.

The same section maps roles and groups the identity provider sends at each single sign-on, for a company that does not provision: see roles from single sign-on. A person who has both gets the highest role either gives.

Roles from WorkOS

A SCIM or Google Workspace directory can also have its roles worked out by WorkOS: the company's IT admin assigns their directory groups to roles in the Admin Portal, and WorkOS puts the result on each directory record. BuildBase keeps that role on the person at every directory change and every daily read, and a WorkOS role mapping gives it a role here, the same way a directory group mapping does. Once a mapping, a company group or a published role uses WorkOS roles, someone joining or leaving a group in the directory has their role read again with that change. Nothing needs turning on, and the directory group mappings above keep working beside it; an HR-system directory, where WorkOS assigns no roles, still needs them. How the two sources combine, and when the portal's changes arrive, is in roles from WorkOS.

Inside your app

Apps on @buildbase/sdk 1.0.0 or later add a Directory sync card to the workspace's Single sign-on screen while provisioning is on. The company's workspace admins see whether their directory is connected, the state in the table above in their own words, and when it last synced. Set up directory sync opens the same setup portal step, minted for that member, for members whose role includes workspace:settings:edit. Hide the card with ui.settings.enterprise.provisioning, or only its button with ui.settings.enterprise.setupProvisioning.

useEnterprise(workspaceId) returns the same facts as status.provisioning: { enabled, linked, state, lastSyncAt }.

What a provisioned person gets

  • An account with the email the directory lists, on one of the enterprise's verified domains. Someone on another domain is not created, and the enterprise says so. An existing account with that email is linked rather than duplicated.
  • No welcome email, and no other email about what the directory does to them: no "added to a workspace", "role changed", "blocked" or "unblocked". A company provisioning a thousand people would otherwise send a thousand of each.
  • They sign in with Continue with SSO. Provisioning creates the account; single sign-on is how they use it.
  • They count toward your plan's end-user ceiling. At the ceiling, new people are not created and the enterprise says so.

What happens when someone leaves

When the directory deletes, deactivates or suspends a person, they are:

  • Blocked, not deleted. Their account and its history stay, and the block reason says it was the directory.
  • Signed out everywhere, including any console session.
  • Taken out of the workspaces the enterprise added them to. A workspace someone added them to by hand keeps them.
  • Unlinked from SSO, so they cannot sign in through the enterprise.

If the directory brings them back, they are unblocked and rejoin the bound workspaces. A block you placed by hand is not lifted by the directory.

A blocked person still counts toward your plan's end-user ceiling. To stop counting the people a directory removed, turn off Count them under People the directory removed on the WorkOS provider card (it needs Directory Sync on your plan). Then bringing one back counts them again, so at the ceiling they stay blocked and the enterprise says so until there is room.

A disconnected directory is not proof that anyone left: its people are marked inactive and lose their managed workspaces and mapped roles, but they are not blocked. Provisioning turns itself off for that enterprise until you turn it back on.

How changes arrive

Your server reads each enterprise's directory changes from WorkOS every five minutes, with your WorkOS key. Nothing has to reach your server from outside, so this works on a self-hosted install behind a firewall too.

  • Sync now on the enterprise asks for the changes at once (once a minute), and reads the directory's state from WorkOS on the spot. For a SCIM directory, whose provider pushes each change to WorkOS as it happens, the changes show within a minute. A Google Workspace directory is read by WorkOS itself, about every 30 minutes, so Sync now also asks WorkOS to read it at once (below).
  • Once a day, your server reads each directory whole and corrects anything an event missed, including its state: WorkOS sends no event when a directory's credentials are refused or it is unlinked on the identity provider's side.
  • The webhook is optional. With it, WorkOS tells your server about a change as it happens and the five-minute wait drops to seconds. Register webhook creates it in your WorkOS account and keeps its signing secret; if you created one in the WorkOS dashboard, paste its whsec_ secret instead. The webhook only wakes your server, which then reads the changes the usual way.
  • The same endpoint serves single sign-on. Register webhook subscribes it to the directory events and also to the connection events (connection.activated, connection.deactivated, connection.deleted, connection.saml_certificate_renewal_required, connection.saml_certificate_renewed) and the domain events (organization_domain.created, .updated, .deleted, .verified, .verification_failed), so a certificate warning, a connection's change or a domain WorkOS verified lands in seconds rather than at the next six-hourly read. An endpoint registered before it carried them gets the directory events only: choose Register again. WorkOS makes a new endpoint with a new secret, and your server asks WorkOS to delete the older endpoint at the same address, whose deliveries would no longer match the saved secret. An endpoint you made in the dashboard needs those events ticked there.

WorkOS only delivers webhooks to a public HTTPS address, so a self-hosted install behind a firewall cannot register one. It does not need one: changes arrive within five minutes.

Sync now and Google Workspace

Google does not push a Google Workspace directory's changes: WorkOS reads it about every 30 minutes, so your server's own pull finds nothing new in between. For a Google Workspace directory that WorkOS lists as linked or validating, Sync now also asks WorkOS to read it now. WorkOS allows one such request every five minutes per directory, shared with your WorkOS dashboard and the IT admin's Sync manually in the Admin Portal. What WorkOS then reads reaches your app like any change: within five minutes of WorkOS finishing, or at once with the webhook. The enterprise's provisioning card says so under its buttons for a Google Workspace directory: "WorkOS reads Google Workspace about every 30 minutes. Sync now also asks it to read at once, at most every five minutes." A SCIM directory's card keeps "Changes arrive within five minutes, or within seconds with the webhook below." The card knows the directory's type from WorkOS's last read of it, by Sync now, the directory's activation or the daily read. Your own pull is queued whatever WorkOS answers, and the console says what it did:

provider.outcomeWhat happenedThe console says
queuedWorkOS took the requestWorkOS is reading the Google Workspace directory now
already_runningWorkOS is reading it already (its 409)WorkOS is already reading it
cooldownWorkOS read it less than five minutes ago (its 429). retryAfterSeconds carries WorkOS's waitHow many minutes are left
pausedWorkOS has paused directory syncing for now (its 503)WorkOS reads the directory again on its own
not_googleNot asked: the directory is not Google Workspace, so there is nothing to read earlyChanges appear within a minute, as before
not_readyNot asked: a Google Workspace directory that is not linked or validatingThe same
failedWorkOS could not be asked, for any other reason; your server logs WorkOS's codeWorkOS could not be asked, and changes appear within a minute

None of these is an error: the route answers 202 either way, with requestedAt, when WorkOS last took a request for the directory. Nothing is added to the trail, as before. Your app and the company API have no Sync now: the company's IT admin uses WorkOS's own Sync manually in the Admin Portal (the page for IT admins).

# Console route: an org API key with the enterprises resource
curl -X POST -H "Authorization: Bearer $API_KEY" \
  "https://<your server>/api/auth/enterprise/enterprises/<enterprise id>/directory/sync"
{
  "queued": true,
  "state": "active",
  "provider": {
    "outcome": "cooldown",
    "retryAfterSeconds": 240,
    "requestedAt": "2026-10-18T09:58:02.000Z"
  }
}

Events

EventWhen
enterprise.user_provisionedA person was created from the directory (instead of user.registered)
enterprise.user_deprovisionedA person was blocked because the directory removed them
enterprise.directory_syncedOne per enterprise per sync, with counts: provisioned, updated, deprovisioned, skipped

Joins, removals and role changes the directory causes also fire the usual workspace events, marked with source: "directory_sync". A join single sign-on makes, when someone first signs in through the company and a binding adds them to its workspaces, is marked source: "sso", and an invitation your team revokes in the console source: "console". See webhooks.

Turning it off

Turning provisioning off stops changes from the directory reaching your app. Nobody is removed or blocked, and turning it back on within 90 days picks up where it left off. An enterprise cannot be deleted while provisioning is on.