scimdeprovisioningseat churnrevenue leakage

Okta Deactivates a User in Seconds. It Almost Never Sends the DELETE Your Billing Logic Is Waiting For.

SCIM offboarding is usually a PATCH setting active to false, not a DELETE. Seat-release logic built for DELETE never fires, and keeps billing ghost seats.

XY
9 November 2026 · 8 min read

Ask a SaaS vendor why a customer is still being billed for an employee who left two months ago, and the honest answer is almost never "we don't support SCIM." Most do. The honest answer is that their identity provider sent a message their billing code wasn't listening for. Okta told the directory the user was gone the moment IT clicked deactivate. It did not send the message most seat-release code is written to expect.

Key stat
48%
of former employees still had active access to at least one company application months after their last day
Source: CloudEagle, 2026 IGA Report (survey of 1,000 enterprise CIOs and CISOs, June 2026)

That number gets reported as a security finding, and it is one. What it misses is the mirror-image problem sitting on the billing side of the exact same gap. If the systems meant to cut off access can't reliably tell when someone's gone, the systems that bill per seat can't either — and unlike a security gap, nobody files an incident report over a seat you're quietly still charging for.

What SCIM actually sends when someone's offboarded

SCIM 2.0's governing spec, RFC 7643, treats deactivation as the standard way to remove a user's access — the resource stays, the active attribute flips to false, and a receiving app is expected to revoke access immediately while keeping the record for audit and rehire purposes. A hard delete is a separate, less common action.

Okta and Microsoft Entra ID both follow that pattern on a routine offboarding. Okta sends a PATCH setting active to false when a user is deactivated or unassigned from an app, and does not send a DELETE on that path. Entra behaves the same way during its 30-day soft-delete window, pushing the same downstream deactivation signal and reserving DELETE for after that window closes or for an admin-forced hard delete. Across both platforms — which between them sit behind most mid-market and enterprise SSO deployments — the message your app receives on a normal Tuesday-afternoon offboarding is a deactivation, not a deletion.

Identity providerStandard offboarding signalWhen a DELETE actually fires
OktaPATCH, active: falseExplicit hard delete, not standard removal
Microsoft Entra IDPATCH, active: false (30-day soft-delete window)After the recycle-bin window closes, or a forced hard delete
Most SCIM 2.0 providers (per RFC 7643)Deactivation is the documented default behaviorOnly on an explicit delete action, not routine offboarding

Downstream of the IdP, the event name changes shape again depending on how your identity layer surfaces it. Clerk represents a SCIM deactivation as a user.updated event with the user flagged inactive, not user.deleted. WorkOS fires dsync.user.updated with an inactive state for the same soft-delete path. Auth0's SCIM bridge sets blocked: true on the user rather than removing the record. None of these is a deletion event. If your seat-counting logic is pattern-matching on "deleted," "removed," or a literal DELETE verb, it will not fire on any of them — which means it won't fire on the vast majority of real offboardings at all.

Why that turns into a billing leak, not just an access gap

WorkOS's own developer documentation states the consequence plainly: without SCIM wired correctly to seat logic, "their seat wasn't deprovisioned in your tool, so the customer is still paying for them." The access problem and the billing problem share one root cause — a receiving system built around the wrong trigger — but they get discovered completely differently. A locked-out employee notices immediately and complains to IT. A ghost seat notices nobody, because the person it was billed to can no longer log in to see it, and the account admin who pays the invoice usually isn't cross-checking headcount against seat count line by line every month.

It usually surfaces the way seat mismatches tend to surface for any B2B account: at a license audit, a renewal review, or when a new finance hire runs a spend report for the first time. By then the gap has had months to compound.

What happens to access after an employee's last day
At least one account kept working79%
Could still reach company data (HR, financial, customer records)64%
Access survived even after the device was returned82%
Access lasted more than a month30%

Source: Sagiss, 2026 Employee Offboarding Survey (self-reported, 1,000 US former employees, September 2026)

Sagiss's survey asked former employees, not IT teams, and got answers that track with the CloudEagle finding from the vendor side: offboarding is porous in both directions. The part that doesn't make it into a security writeup is that a billing system watching the same deactivation event has exactly the same odds of missing it as the app the employee is still logged into.

This is a third category of seat mismatch, not a repeat of the other two

It's worth being precise here, because we've written about two adjacent problems before and the fix for each one is different. Seat-based churn is a team paying for more seats than people actually use — the seats are technically assigned and accessible, nobody's using them, and the fix is a usage-visibility and right-sizing conversation before renewal. Account sharing is the inverse: more people using the product than seats paid for. This is neither. The person is fully, correctly locked out. Nobody is using the seat. It just never got released, because the billing system was waiting for a message the identity provider wasn't sending.

DimensionSeat wasteAccount sharingSCIM deprovisioning lag
Who still has accessThe assigned user, unusedMultiple people, one seatNobody — access is correctly revoked
Who notices firstThe buyer, via a license auditThe vendor, via session signalsUsually nobody, until a renewal review
Root causeOver-provisioning or stalled adoptionUnenforced per-user login policySeat-release logic wired to the wrong SCIM event
The fixUsage visibility, right-size at renewalSession detection, seat-upgrade pathTreat deactivation as a release trigger, not just deletion

That last column is also the one with the cleanest fix, because unlike seat waste or sharing, it isn't a behavior problem on either side of the relationship. Nobody has to change how they use the product. It's a wiring problem in your own integration.

What to actually build

  • Treat active: false as a seat-release event, full stop. Don't gate seat release behind a hard delete that, per Okta's and Entra's own documented behavior, will rarely arrive on a standard offboarding.
  • Handle the event names your identity layer actually fires — user.updated with an inactive flag, dsync.user.updated, or a provider-specific blocked/active field — instead of pattern-matching on a literal delete or removal string.
  • Reconcile on a schedule, not just on webhook deltas. Pull the live roster from the SCIM API periodically and diff it against your billed seat count. Webhooks get retried, deduplicated incorrectly, or dropped during an outage, and a missed event with no reconciliation behind it becomes permanent drift.
  • Decide your billing policy explicitly, then document it. Some vendors release a seat immediately on deactivation; others hold it for the remainder of the billing term and release it at renewal. Either is defensible. What isn't defensible is an undefined behavior that an angry customer discovers for you.
  • Surface the reconciliation to the account admin before they go looking for it. The same principle we've covered for forced security-driven cost increases applies here in reverse: whoever raises the discrepancy first controls how the conversation goes. "We noticed 4 deactivated users still on your invoice — here's a credit" lands very differently than a customer's finance team finding it first.

The trust cost here is bigger than the dollar figure usually suggests. A customer who finds three months of billing for five people who no longer work there doesn't typically ask for a prorated credit and move on — they ask what else in your billing they can't trust, and some share of them cancel outright rather than keep auditing you every quarter. That's the same dynamic behind the entitlement and feature-gating lag we've written about before: a billing system that's silently wrong in the customer's favor just means you leave money on the table; one that's silently wrong against them is how you lose the account. If you're already running a cancellation flow to catch why people leave, "found out I was overbilled for offboarded users" is exactly the kind of reason that should route to a credit and an apology before it ever routes to a save offer — the damage is already done by the time someone's on your cancel page over it. You can see what even a handful of unreleased seats are costing you in ARR with our churn calculator before you decide how urgently to fix the wiring.

Frequently asked questions

Does SCIM send a DELETE request when a user is offboarded?+

Usually not. RFC 7643 treats deactivation as the default offboarding action, and Okta and Microsoft Entra ID both confirm they send a PATCH setting active to false on a standard user removal, not a DELETE. A DELETE is reserved for a hard delete — a GDPR erasure request, an account merge, or an admin explicitly purging the record — which is a small minority of real-world offboardings.

Why does my SaaS still bill for an employee who was removed from Okta weeks ago?+

Almost always because your seat-release logic is listening for the wrong event. If your billing system only decrements a seat count on a SCIM DELETE, and the identity provider sent a PATCH with active: false instead, your system never got a signal it recognized as "this seat is free now." The user is correctly locked out of your product and still fully billed, because deactivation and seat release were never wired together.

What's the difference between SCIM deactivation and deletion?+

Deactivation (active: false) is a soft removal — the user record stays intact for audit history, rehire, and compliance, and access is revoked immediately. Deletion removes the resource outright. Every major identity provider treats deactivation as the normal offboarding path and deletion as the exception, which is the opposite of how a lot of seat-counting code is written.

How do I fix seat billing after a SCIM deprovisioning event?+

Treat a PATCH to active: false exactly like a seat-release trigger, the same as a DELETE. Then add a periodic reconciliation job that pulls the IdP's live user list through the SCIM API and compares it against your billed seat count, instead of trusting webhook deltas alone — webhooks get missed, retried, and silently dropped, and a one-time sync won't catch drift that accumulates over months.

Try CancelFlow

Stop losing subscribers today

One script tag. One function call. A live cancellation flow in under 10 minutes.

Start free trial →
← All postsHome