How-To: Employee Offboarding Workflow¶
A step-by-step guide to building a workflow that lets an HR admin (or the
departing employee's manager) revoke all provisioned access for a user who
is leaving the organization. The workflow gates deprovisioning behind a
manager approval, uses role_revoke to automatically deprovision all
entitlement-managed accounts, and surfaces any unmanaged external
identities that need manual cleanup.
What you'll build¶
Submitter (HR / manager) opens the request catalog
│
▼
Catalog form collects departingUser, offboardingReason, approver
│
▼
Start
│
▼
Manager/HR Approval (approval, approvers: [{{approver.id}}])
│
├── success ─┐
│ ▼
│ Revoke "New Employee" Role (role_revoke)
│ [entitlement system calls disableAccount + removeFromGroup]
│ │
│ ▼
│ Identify Unmanaged Accounts (transform)
│ │
│ ▼
│ Notify Manager — Unmanaged Accounts (notification, conditional)
│ │
│ ▼
│ Notify Departing User — Access Revoked (notification)
│ │
│ ▼
│ Notify Submitter — Offboarding Complete (notification)
│ │
│ ▼
│ End
│
└── error (rejected) ─► Notify Submitter — Offboarding Denied ─► End
Prerequisites¶
- The "New Employee" role and its entitlements exist. This workflow
is the complement to the Employee Onboarding
guide, which provisions access via
role_grant. The same role definition and entitlement definitions (AD account + Authifi group) must already be configured — see the onboarding guide's Prerequisites for creation steps. - Permissions on the submitter's account.
workflow:manageto author and publish the workflow.workflow:start(or catalog submission group membership) to submit offboarding requests.- The departing user has an active role assignment.
role_revokecompletes successfully even if no active assignments exist (it returnsrevokedCount: 0), but the entitlement deprovisioning only fires when there are active entitlement instances to revoke. - Departing user has a manager assigned. Step 4.4 sends an
"unmanaged accounts" notification to
{{departingUser.manager.id}}. If no manager is set on the user, the recipient resolves toundefinedand the notification step errors. Note that this failure occurs afterrole_revokehas already completed — access is already revoked, but neither the departing user nor the submitter receives any notification (the run stops at step 4.4). Ensure departing users have a manager before submitting offboarding — or replace the recipient with{{submitter.id}}if your process does not require manager involvement. - Connectors passing. The
test-activedirectoryandauthificonnectors must be configured and passingTest Connection— the entitlement deprovision commands execute against them at revocation time.
Step 1 — Create the workflow shell¶
- In the admin UI, navigate to Workflows and click New Workflow.
- Fill in the General tab:
| Field | Value |
|---|---|
| Name | Employee Offboarding |
| Description | Revoke provisioned access and notify about unmanaged accounts on separation. |
| Category | user |
| Subject Variable | departingUser |
| Error Strategy | stop |
| Trigger | manual |
Why
usercategory? The departing employee already has a Floh user row. By usinguserwith a subject variable, the engine resolvesdepartingUserto the full user object at run start — giving us{{departingUser.id}},{{departingUser.email}},{{departingUser.displayName}},{{departingUser.manager}}, and{{departingUser.externalIdentities}}for free.
- Save the draft. The designer drops you on the Variables tab.
Step 2 — Define context variables¶
Open the Variables tab and add the following rows:
| Name | Type | Required | Default | Description |
|---|---|---|---|---|
departingUser |
User |
yes | — | The employee being offboarded (workflow subject). |
offboardingReason |
String |
yes | — | Reason for offboarding (resignation, termination, transfer, etc.). |
approver |
User |
yes | {{submitter}} |
The person who must approve deprovisioning. |
Notes:
departingUseris the subject variable declared in Step 1. The admin picks the departing employee from the User typeahead on the catalog form.approverdefaults to the submitter for walkthrough simplicity. For production, enforce separation of duties — the submitter and approver should be different people (e.g. HR submits, the departing user's manager approves). Remove the default or set it to{{departingUser.manager}}so the form forces an explicit approver selection.- Output variables from workflow steps (
revokedCount,unmanagedAccounts,hasUnmanagedAccounts,unmanagedSummary) do not need to be declared — they appear in the variable bag automatically.
Step 3 — Customize the catalog form (Input Form tab)¶
Open the Input Form tab. A minimal layout for offboarding:
<div class="grid">
<div class="col-12">
<h3 class="mt-0">Employee separation</h3>
</div>
<div class="col-12 md:col-6">
{{departingUser.label}}
{{departingUser}}
</div>
<div class="col-12 md:col-6">
{{approver.label}}
<small class="text-color-secondary mb-2 block">
Who should approve the deprovisioning? Leave blank to approve it yourself.
</small>
{{approver}}
</div>
<div class="col-12 mt-3">
{{offboardingReason.label}}
{{offboardingReason}}
</div>
</div>
Step 4 — Add the workflow steps¶
4.1 — Manager/HR Approval (approval)¶
| Config Field | Value |
|---|---|
| Step ID | offboarding-approval |
| Step Name | Manager/HR Approval |
| Type | approval |
| Approvers | {{approver.id}} |
| Due Date | (optional — set a 48h SLA if your org requires it) |
The run pauses at waiting_approval. The approver sees a task in
My Tasks → Approvals with the departing user's name and the
offboarding reason.
4.2 — Revoke "New Employee" Role (role_revoke)¶
| Config Field | Value |
|---|---|
| Step ID | revoke-new-employee-role |
| Step Name | Revoke "New Employee" Role |
| Type | role_revoke |
| Role Definition ID | (UUID of the "New Employee" role — paste or pick from autocomplete) |
| User ID | {{departingUser.id}} |
| Reason | Employee offboarding: {{offboardingReason}} |
This step:
- Finds all active role assignments for
(departingUser.id, roleDefinitionId). - For each linked entitlement, calls its
deprovision_config: - AD entitlement →
test-activedirectory.disableAccount— disables the AD account. - Authifi entitlement →
authifi.removeFromGroup— removes the user from thenew-employeegroup. - Marks the role assignment as
revoked. - Writes
revokedCountto the variable bag.
If no active assignments exist (e.g. already revoked, or user never had
the role), the step still completes with revokedCount: 0.
4.3 — Identify Unmanaged Accounts (transform)¶
| Config Field | Value |
|---|---|
| Step ID | identify-unmanaged-accounts |
| Step Name | Identify Unmanaged Accounts |
| Type | transform |
| Outputs | unmanagedAccounts, hasUnmanagedAccounts, unmanagedSummary |
| Script | see below |
const user = floh.variables.departingUser;
const identities = user.externalIdentities || [];
// Connectors whose deprovisioning is handled by the entitlement system.
// These were already cleaned up by role_revoke in step 4.2.
const managedConnectors = ["test-activedirectory", "authifi"];
const unmanaged = identities.filter(
(identity) => !managedConnectors.includes(identity.connectorName),
);
return {
unmanagedAccounts: unmanaged,
hasUnmanagedAccounts: unmanaged.length > 0,
unmanagedSummary:
unmanaged.length > 0
? unmanaged
.map(
(identity) =>
identity.connectorName + ": " + (identity.externalEmail || identity.externalId),
)
.join("\n")
: "None — all external accounts were deprovisioned via entitlements.",
};
Replace managedConnectors with the connector names whose
deprovisioning is handled by your entitlement definitions. Any connector
identity NOT in this list is treated as unmanaged and surfaced to the
manager.
4.4 — Notify Manager — Unmanaged Accounts (notification)¶
| Config Field | Value |
|---|---|
| Step ID | notify-unmanaged-accounts |
| Step Name | Notify Manager — Unmanaged Accounts |
| Type | notification |
| Recipient Type | internal |
| Recipient User | {{departingUser.manager.id}} |
| Subject Override | Action required — unmanaged accounts for {{departingUser.displayName}} |
| Custom Body | The following external accounts for {{departingUser.displayName}} are not managed by Floh entitlements and need manual deprovisioning:\n\n{{unmanagedSummary}}\n\nPlease disable or remove these accounts in their respective systems. |
| Require Acceptance | unchecked |
Conditional execution. Notification steps do not have a built-in "skip if" toggle. As written, this notification fires on every run — when no unmanaged accounts exist, the body reads "None — all external accounts were deprovisioned via entitlements." which serves as a confirmation. To suppress the notification entirely when there are no unmanaged accounts, insert a
conditionstep between 4.3 and 4.4 that checkshasUnmanagedAccounts == trueand routes to 4.4 on thetrueedge and directly to 4.5 on thefalseedge. If you add this condition step, update the wiring table in Step 5 accordingly.
4.5 — Notify Departing User — Access Revoked (notification)¶
| Config Field | Value |
|---|---|
| Step ID | notify-departing-user |
| Step Name | Notify Departing User — Access Revoked |
| Type | notification |
| Recipient Type | internal |
| Recipient User | {{departingUser.id}} |
| Subject Override | Your access has been revoked |
| Custom Body | Hi {{departingUser.displayName}},\n\nYour organizational access has been revoked as part of the offboarding process. If you believe this is an error, contact your manager or HR.\n\nReason: {{offboardingReason}} |
| Require Acceptance | unchecked |
Sensitivity note. The
{{offboardingReason}}is included verbatim in the notification sent to the departing employee. For involuntary terminations, ensure the reason text entered at submission time is appropriate for the employee to read — or remove theReason:line from the body and limit it to the submitter/manager notifications.
4.6 — Notify Submitter — Offboarding Complete (notification)¶
| Config Field | Value |
|---|---|
| Step ID | notify-offboarding-complete |
| Step Name | Notify Submitter — Offboarding Complete |
| Type | notification |
| Recipient Type | internal |
| Recipient User | {{submitter.id}} |
| Subject Override | Offboarding complete — {{departingUser.displayName}} |
| Custom Body | Offboarding for {{departingUser.displayName}} is complete.\n\nRoles revoked: {{revokedCount}}\nUnmanaged accounts: {{unmanagedSummary}}\n\nReason: {{offboardingReason}} |
| Require Acceptance | unchecked |
4.7 — Notify Submitter — Offboarding Denied (notification)¶
The rejection branch when the approver declines the request.
| Config Field | Value |
|---|---|
| Step ID | notify-offboarding-denied |
| Step Name | Notify Submitter — Offboarding Denied |
| Type | notification |
| Recipient Type | internal |
| Recipient User | {{submitter.id}} |
| Subject Override | Offboarding denied — {{departingUser.displayName}} |
| Custom Body | The offboarding request for {{departingUser.displayName}} was denied by the approver. No access has been revoked. Contact the approver for details. |
| Require Acceptance | unchecked |
4.8 — End¶
| Config Field | Value |
|---|---|
| Step ID | end |
| Step Name | End |
| Type | end |
Step 5 — Wire the graph¶
| # | From | Edge label | To |
|---|---|---|---|
| 1 | Start | success |
Manager/HR Approval |
| 2 | Manager/HR Approval | success |
Revoke "New Employee" Role |
| 3 | Manager/HR Approval | error |
Notify Submitter — Offboarding Denied |
| 4 | Revoke "New Employee" Role | success |
Identify Unmanaged Accounts |
| 5 | Identify Unmanaged Accounts | success |
Notify Manager — Unmanaged Accounts |
| 6 | Notify Manager — Unmanaged Accounts | success |
Notify Departing User — Access Revoked |
| 7 | Notify Departing User — Access Revoked | success |
Notify Submitter — Offboarding Complete |
| 8 | Notify Submitter — Offboarding Complete | success |
End |
| 9 | Notify Submitter — Offboarding Denied | success |
End |
Note on the
erroredge. In Floh, theapprovalstep routes to itson: "error"transition both when the approver intentionally rejects the request and when a technical failure occurs. There is no separaterejectededge label. If you need to distinguish rejection from transient failure in the notification body, inspect the task'srejectedReasonfield in the step payload.Required for production: add an
on: "error"edge from therole_revokestep (4.2) to a dedicated "Deprovisioning Failed — Admin Attention Required" notification step. Ifrole_revokefails mid-execution (e.g. one connector times out while another succeeds), deprovisioning is partially complete — some access may still be active — and all downstream notification steps silently never fire. Without the error edge, the run stops viaonError: "stop"and the submitter has no indication that manual cleanup is needed. This is omitted from the walkthrough for simplicity but must be added before deploying to production.
Step 6 — Save and validate¶
Click Save Draft. The designer validates:
- Graph structure — no unreachable nodes, all transitions target valid step IDs.
- Role reference — the
roleDefinitionIdon therole_revokestep resolves to an existing role definition. - Lint warning — if you skipped the recommended
on: "error"edge onrole_revoke, you'll see a yellowPROVISIONING_CHAIN_NO_ERROR_EDGElint banner.
Step 7 — Test Run before publishing¶
- From the workflow detail page, click Test Run.
- Pick a test user who was previously onboarded via the Employee Onboarding workflow (they should have an active "New Employee" role assignment).
- Set yourself as the
approver. - Click Start Run.
- The run pauses at
Manager/HR Approval. Go to My Tasks → Approvals and approve. - The
role_revokestep executes — confirm: - The Role Assignments page shows the user's "New Employee"
assignment as
revoked. - The
test-activedirectoryconnector: runfindUser— the account should showenabled: false. - The
authificonnector: runcheckGroupMembership— the user should no longer be in thenew-employeegroup. - The transform identifies any unmanaged accounts.
- Notifications fire. Check the submitter's inbox for the completion summary.
Step 8 — Publish and add to the catalog¶
- Click Publish on the workflow detail page.
- Open the Catalog tab and configure:
| Field | Value |
|---|---|
| Published | checked |
| Icon | person_remove |
| Description | Revoke access and deprovision accounts for a departing employee. |
| Tags | hr, offboarding, deprovisioning |
| Submission Groups | restrict to hr-admins and engineering-managers |
Runtime walkthrough¶
- HR admin opens the catalog and picks Employee Offboarding.
- HR fills in the form — picks the departing user from the typeahead, selects the manager (or themselves) as approver, and enters the reason. They click Submit.
- Approval task appears in the approver's My Tasks inbox. The task shows the departing user's name and the offboarding reason.
- Approver clicks Approve. The run resumes.
role_revokeexecutes:- Finds the active "New Employee" role assignment.
- Calls AD entitlement deprovision →
disableAccountdisables the AD account. - Calls Authifi entitlement deprovision →
removeFromGroupremoves the user from thenew-employeegroup. - Marks the assignment as
revoked. - Transform inspects
externalIdentities. If the user has a Google Workspace link (from connector sync) or other non-entitlement-managed identities, they appear inunmanagedSummary. - Manager receives a notification listing unmanaged accounts that need manual cleanup (or a confirmation that all accounts were handled automatically).
- Departing user receives notification that their access has been revoked.
- Submitter receives notification with a summary: roles revoked, unmanaged accounts listed.
- Run completes.
Known limitations¶
-
No Floh user deactivation step. There is no built-in
user_deactivatestep type today. After the workflow completes, the Floh user row remainsactive. Deactivate manually from the admin UI (Users → user detail → Deactivate) or callDELETE /api/users/:id(soft-delete) from an external automation. A dedicateduser_deactivatestep type is not currently available. -
Single role targeted. The
role_revokestep targets oneroleDefinitionId. If the user holds multiple roles (e.g. "New Employee" + "Genome Access Participant" from the Provisioning example), each needs its ownrole_revokestep — or atransform→ loop pattern that isn't natively supported today. For users with many roles, the Role Assignments admin page offers a "Revoke All" bulk action. -
Unmanaged account notification is informational only. The workflow notifies the manager but cannot deprovision accounts in connectors that lack entitlement definitions. Manual cleanup is required for those.
-
Test-connector simplification. This guide uses
test-activedirectory, which simulates AD in-memory. A production deployment would use a real AD connector whosedisableAccountcommand targets the actual directory. The workflow structure is identical — only the connector name changes.
What to do next¶
- Pair this with the Employee Onboarding
workflow — onboarding provisions via
role_grant, offboarding deprovisions viarole_revoke. - Add a scheduled trigger variant that runs automatically on the employee's last day (pulled from an HR system integration).
- Add
on: "error"edges on therole_revokestep to handle connector failures gracefully — see the Partial Provisioning Runbook. - Create entitlement definitions for additional connectors (Google Workspace, SCIM providers) so their deprovisioning is also automatic.
References¶
- Employee Onboarding workflow — the
companion provisioning guide using
role_grant. - Provisioning workflow with role lifecycle — document-driven provisioning with automatic expiry revocation.
- Roles & Entitlements — role definitions, entitlement definitions, and the deprovisioning model.
- External Identities — how connector identity links work and how the transform step reads them.
- Partial Provisioning Runbook — cleanup steps when deprovisioning partially fails.