How-to
SCIM provisioning basics
Provisioning is easy to demonstrate and easy to get subtly wrong. A handler that creates users correctly and deactivates them unreliably will pass every demo and fail the first security review. This guide is about the decisions around the handler, not the handler itself.
Four decisions to make before any code
Each of these is cheap now and expensive later, because changing them means migrating records that customers can see.
- 1What is the primary key? The directory's own identifier, not the email address. Emails change; some are even reassigned.
- 2What does removal mean? Deactivation is right for most products, because deleting a user usually deletes their authored content with them.
- 3What is the source of truth for names and email? If the directory owns them, your profile editor should be read-only for those fields, or the next sync will silently overwrite what somebody typed.
- 4Do groups mean anything? If they map to roles, decide whether the mapping is per organization — it almost always has to be.
The states a directory user moves through
Providers differ in which events they send, but the underlying states are the same. Modelling them explicitly is what stops a suspended person from keeping a live session.
| Directory state | Event you receive | What your product should do |
|---|---|---|
| Created and assigned | dsync.user.created | Create the account, inactive until first sign-in if you prefer |
| Attribute changed | dsync.user.updated | Upsert the record from the full payload |
| Suspended | dsync.user.updated with state inactive | Block sign-in and revoke existing sessions |
| Unassigned from the app | dsync.user.deleted | Deactivate; keep the data |
| Deleted from the directory | dsync.user.deleted | Deactivate, and follow your retention policy |
| Added to a group | dsync.group.user_added | Re-evaluate role mapping |
Revoking sessions on suspension is the step most often missed. Without it, somebody suspended on Friday afternoon keeps working until their session expires, which may be days.
Backfilling an existing directory
Events describe changes from now on. When a customer connects a directory that already contains two thousand people, you need to read the current state once — and you need it to be safe to run twice, because it will be.
1import { Paycux } from "@paycux/node";23const paycux = new Paycux(process.env.PAYCUX_API_KEY);45export async function backfill(directoryId: string) {6 let after: string | undefined;7 let seen = 0;89 do {10 const page = await paycux.directorySync.listUsers({11 directory: directoryId,12 limit: 100,13 after,14 });1516 for (const user of page.data) {17 // upsert — ayni kaydi iki kez islemek guvenli olmali18 await upsertUser({19 directoryUserId: user.id,20 organizationId: user.organizationId,21 email: user.emails.find((e) => e.primary)?.value,22 firstName: user.firstName,23 lastName: user.lastName,24 active: user.state === "active",25 });26 seen++;27 }2829 after = page.listMetadata.after;30 } while (after);3132 return seen;33}
Mapping groups to roles
Never infer meaning from a group name. A group called Admins in one customer's directory is their IT department; in another it is every team lead. Guessing produces privilege escalation that nobody notices until an audit.
Build a small screen where an organization admin pairs each incoming group with one of your roles, store it against the organization, and re-evaluate on every membership event. Unmapped groups should grant nothing.
Log the mapping decisions as audit events. When somebody asks in six months why a person had a permission, the answer needs to be a record rather than an inference.
What to test
A provisioning integration is tested by breaking it. These five cases catch nearly everything that reaches production.
- 1Create a user, then create the same user again. The second attempt must not produce a duplicate.
- 2Suspend a user with an open session and confirm the session stops working, not just the next sign-in.
- 3Rename a user in the directory and confirm your records follow the identifier, not the email.
- 4Return a 500 from your endpoint, then let the retry succeed. Confirm nothing was applied twice — no duplicate welcome email, no doubled group membership.
- 5Bulk-import fifty users at once and confirm your handler keeps up rather than timing out and triggering a retry storm.
What SCIM will not tell you
SCIM carries identity and membership. It does not carry job titles reliably, it does not carry manager relationships in a standard way, and it does not say when somebody's last day is — only that they have gone.
If your product needs employment context rather than directory context, the HR system is the source you want. That is a different integration with a different shape, and it is worth knowing before you promise a customer that SCIM will deliver it.