Concepts
SCIM deprovisioning behaviour
Offboarding is the reason directory sync gets bought. It is also where the semantics are least obvious: three different things a customer can do to a departing employee arrive as three different events, and two of them are frequently mistaken for the third. Getting this wrong means either leaving access open or deleting records somebody needed.
Three ways a person leaves
When somebody leaves a company, the IT team does one of three things, and which one depends on the company's own policy rather than on anything you control. All three should end access to your application immediately. Only one of them says anything about deleting data.
The distinction matters because the customer's expectations differ sharply. A suspended employee is often expected to return — parental leave, a secondment, a contractor between engagements — and their history should be intact when they do. A deleted employee is usually gone for good, though even then the customer's finance team may need to know who approved what last quarter.
Deciding this once, explicitly, is far easier than deciding it per support ticket. Write down what your application does in each case and put it in your own documentation, because a customer's security review will ask.
| What the customer did | How it reaches you | What it means |
|---|---|---|
| Deactivated the account | dsync.user.updated with state suspended | Access ends now. The person may come back. Keep everything. |
| Deleted the account | dsync.user.deleted | Access ends now. The identity is gone at the source; their work usually is not. |
| Removed from your application's scope | dsync.user.deleted, identically | Access ends now. The person still works there — they just should not use your product. |
| Removed from a group that granted access | dsync.group.user_removed | The specific access ends. The account remains. |
Rows two and three are indistinguishable from your side, because SCIM sends the same event for both. That is not a gap in the integration; it is how the protocol works. Treat a deletion as end all access, keep the data, and you are correct in both cases.
What your application should do
Separate the two decisions. Revoking access is urgent, unambiguous and identical in every case. Deciding what happens to the person's data is a product question with a different answer for a document, an audit log entry and an approval chain — and it is not a question your webhook handler should be answering at three in the morning.
- 1Revoke access immediately: end the membership, revoke sessions, and stop accepting the identity provider identifier for sign-in.
- 2Mark the local record inactive rather than deleting it. Reactivating in one step is worth far more than the storage you save.
- 3Reassign or park anything that would otherwise become unowned — scheduled reports, integrations, approval steps. An orphaned automation running under a departed employee's identity is a finding waiting to be written up.
- 4Emit your own audit log event recording the deprovisioning, with the source. When somebody asks in six months why access ended, the answer should be in the log rather than in a memory.
- 5Apply deletion of data on your own schedule and your own policy, driven by retention rules rather than by a webhook.
A handler that separates the two
The shape below keeps the urgent path short and synchronous, and defers everything that is a policy decision. It is also idempotent, which matters because a delivery can arrive twice and a second deprovisioning of an already-deprovisioned person should be a no-op rather than an error.
1export async function handle(event) {2 const idpId = event.data.idp_id;34 switch (event.event) {5 case "dsync.user.deleted":6 // Erisim hemen kesilir; veri silinmez.7 await access.revokeAll(idpId, { reason: "directory_deleted" });8 await retention.scheduleReview(idpId);9 return;1011 case "dsync.user.updated":12 if (event.data.state === "suspended") {13 await access.revokeAll(idpId, { reason: "directory_suspended" });14 } else if (event.data.state === "active") {15 await access.restore(idpId);16 }17 return;18 }19}
The person who comes back
Reactivation is the case that exposes whether deprovisioning was implemented as revoke or as delete. A person returning from leave is reactivated at the source, arrives as a user update with state active, and should get their access back without anybody filing a ticket. If your handler deleted their record, there is nothing to restore and somebody has to rebuild it by hand.
This is the practical argument for keeping records: not sentimentality about data, but that the reverse operation exists and customers use it. Key the local record on the identity provider identifier rather than on the email address and reactivation is a single state change, even for somebody who came back under a new surname.
How fast is fast enough
Customers rarely specify a number until their security review does, at which point they ask how long after deactivation somebody can still act in your product. The answer has two parts: how quickly the event reaches you, which is a function of how often their provider pushes, and how quickly your own sessions stop working, which is the access token lifetime you chose.
Neither part is hidden, and both are worth measuring rather than estimating. A directory whose provider pushes on a schedule rather than on change can take longer than anyone expects, and that latency belongs in the answer you give rather than in a footnote.