Skip to content

How-To: New Employee Onboarding Workflow

A step-by-step guide to building a workflow that lets a manager (or HR admin) request a new employee account. The workflow collects the candidate's name, manager, and personal email; verifies that the candidate actually controls that personal address with an OTP; then provisions the candidate in Floh, Active Directory, and Authifi before sending them a welcome email with a one-time password-setup link.

What you'll build

Submitter (HR / hiring manager) opens the request catalog
Catalog form collects firstName, lastName, preferredName?, manager, personalEmail
Start
Verify Personal Email  (verify_contact, mode: link_and_code)
    ├── success ─┐
    │            ▼
    │      Derive Account Details  (transform — username, display name)
    │            │
    │            ▼
    │      Create Floh Profile  (user_create, setAsSubject)
    │            │
    │            ▼
    │      Set Manager  (profile_update, managerId)
    │            │
    │            ▼
    │      Grant "New Employee" Role  (role_grant — provisions AD + Authifi via entitlements)
    │            │
    │            ▼
    │      Send Welcome Email + First Password Link  (first_password_invitation)
    │            │
    │            ├── success ─┐
    │            │            ▼
    │            │      Swap Primary Email  (profile_update, email)
    │            │            │
    │            │            ▼
    │            │           End
    │            │
    │            └── expired ─► Notify IT — Password Link Expired ─► End
    ├── expired ─► Notify Submitter — Verification Expired ─► End
    └── exhausted ► Notify Submitter — Too Many Attempts ─► End

Prerequisites

  1. Permissions on the submitter's account.
  2. workflow:manage to author and publish the workflow.
  3. workflow:provision_users for any human who will start the workflow. The user_create step gates on this permission on the run initiator, not on the workflow author. Without it, the run fails at step Create Floh Profile with denied_missing_permission.
  4. Feature flags on. Set FEATURE_VERIFY_CONTACT=true and FEATURE_FIRST_PASSWORD=true in the environment (both default to true in dev/test, false in production). Without FEATURE_FIRST_PASSWORD the first_password_invitation step fails immediately at runtime. Confirm at /api/health or with an admin who can check the env.
  5. Authifi connector configured and passing Test Connection on the Connectors page. Note its connector name — you'll reference it in the Authifi entitlement definition.
  6. Active Directory connector configured. Floh ships only the simulated test-activedirectory connector out of the box; for a real deployment you need a custom AD connector that exposes createAccount, setPassword, and disableAccount. This guide uses test-activedirectory so the walkthrough is reproducible on a fresh dev install — the entitlement config for a real connector is structurally identical, just substitute the connector name.
  7. Authifi group named new-employee (or whatever your organization uses). Create it from the Authifi admin console and note its UUID — you'll paste it into the Authifi entitlement's provision config.
  8. Entitlement definitions for AD and Authifi. Create these from the Entitlements page before building the workflow:

"Corporate AD Account" entitlement:

Field Value
Name Corporate AD Account
Connector test-activedirectory
Provision Config { "command": "createAccount" } (username is derived from userEmail by the test connector)
Deprovision Config { "command": "disableAccount" }
Reconciliation Config { "command": "checkAccountExists" }

"New Employee Authifi Group" entitlement:

Field Value
Name New Employee Authifi Group
Connector authifi
Provision Config { "command": "addToGroup", "groupId": "<new-employee-group-uuid>", "role": "member", "createIfMissing": true }
Deprovision Config { "command": "removeFromGroup", "groupId": "<new-employee-group-uuid>" }
Reconciliation Config { "command": "checkGroupMembership", "groupId": "<new-employee-group-uuid>" }

Replace <new-employee-group-uuid> with the actual Authifi group UUID.

  1. "New Employee" role definition linking both entitlements above. Navigate to Role DefinitionsNew Role, name it New Employee, save, then click Link Entitlement to attach both Corporate AD Account and New Employee Authifi Group. Note the role definition UUID — you'll reference it in the role_grant step.
  2. Email template (optional). A welcome_employee email template in Templates → Email Templates lets you author the welcome email body once and reuse it across runs. This guide uses an inline messageTemplate on first_password_invitation to keep the walkthrough self-contained.

Step 1 — Create the workflow shell

  1. In the admin UI, navigate to Workflows and click New Workflow.
  2. Fill in the General tab:
Field Value
Name New Employee Onboarding
Description Provision a new hire in Floh, AD, and Authifi after personal-email verification.
Category general
Subject Variable (disabled — the general category does not allow one)
Error Strategy stop
Trigger manual

Why general and not user_self_service? The submitter (a manager or HR admin) is not the person being provisioned, so user_self_service (which auto-binds the workflow target to the submitter) is wrong. user works only if the new employee already has a Floh user row — but here we want to verify their personal email before creating the Floh row, so user_create mid-run is the right tool. The user_create step then opts into setAsSubject: true so the run still gets a subject_id once the Floh user exists — the new hire shows up under GET /api/users/:id/workflow-activity from that point onwards.

  1. 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. Click Add Variable for each one and fill in the fields shown:

Name Type Required Default Description
firstName String yes Legal first name (used to derive AD username and welcome email salutation).
lastName String yes Legal last name.
preferredName String no Optional preferred / chosen name. Used in greetings if set.
personalEmail String yes The candidate's personal email — the only address we know is reachable before AD provisioning. Verified by step 4.1.
manager User yes {{submitter.id}} The new employee's manager. Defaults to the submitter (since 90% of the time HR submits on behalf of the manager — change in the form if not).

Notes:

  • The table above lists only variables that need to be declared up front because they're submitter-supplied (the form fields and manager). Other workflow variables — personalEmailVerified (from step 4.1), adUsername, displayName, and onboardingId (all from step 4.4), flohUser and friends (from step 4.5), roleAssignmentId and roleProvisioned (from step 4.7) — appear in the run variable bag automatically as their producing step's outputVariable / outputKey. You don't pre-declare them, and they're available to every step that runs after the producer. Note that {{run.id}} is not a usable template token: the workflow run id is not exposed in the variable bag, so a transform that needs a stable per-run identifier (e.g. for the initialAttributes.onboardingId custom attribute below) generates one with floh.uuid() instead.
  • manager of type User lets the submitter pick a teammate from a typeahead picker on the catalog form. The picker is scope-restricted: non-admin submitters only see users they share at least one effective group membership with, plus themselves. Admins (user:read permission) see the full directory.
  • The default {{submitter.id}} on manager only resolves at run start (not in the catalog form preview). Submitters who want a different manager open the picker and pick the right teammate.

Step 3 — Customize the catalog form (Input Form tab)

Open the Input Form tab. The designer seeds a Handlebars scaffold that lists every variable in declaration order. That layout is acceptable, but for HR/onboarding it reads better grouped:

<div class="grid">
  <div class="col-12">
    <h3 class="mt-0">Personal information</h3>
  </div>
  <div class="col-12 md:col-6">{{firstName.label}} {{firstName}}</div>
  <div class="col-12 md:col-6">{{lastName.label}} {{lastName}}</div>
  <div class="col-12 md:col-6">{{preferredName.label}} {{preferredName}}</div>
  <div class="col-12 md:col-6">{{personalEmail.label}} {{personalEmail}}</div>

  <div class="col-12 mt-4">
    <h3 class="mt-0">Reporting line</h3>
  </div>
  <div class="col-12">
    <small class="text-color-secondary mb-2 block">
      Pick the new hire's manager from the directory. Leave blank to default to your own account.
    </small>
    {{manager.label}}
    {{manager}}
  </div>
</div>

The renderer mounts a real input where each {{name}} token sits and suppresses the built-in label/description because we render {{name.label}} in the same block. The right-hand pane previews the form live as you type.

If you'd rather skip the custom layout, leave the editor scaffold alone — you'll just get a flat field list in declaration order.

Step 4 — Add the workflow steps

In the Steps tab, click Add Step for each row below. The order in this list matches the natural execution order; transitions are wired in Step 5.

4.1 — Verify Personal Email (verify_contact)

Config Field Value
Step ID verify-personal-email
Step Name Verify Personal Email
Type verify_contact
Recipient Email {{personalEmail}}
Mode link_and_code
Link Expires (h) 24
OTP Length 6
OTP Expires (min) 10
OTP Max Attempts 5
Output Variable personalEmailVerified
Custom Subject Confirm your email to start onboarding at Acme
Message Template (leave default — the built-in template renders a styled "Verify" button using {{acceptUrl}} and the recipient's address)

The candidate gets an email containing a high-entropy verification link. Clicking it opens /verify on the public portal and a second email arrives with the 6-digit code; they type the code on the page to complete verification.

On success the variable bag now carries:

{
  "personalEmailVerified": {
    "verified": true,
    "verifiedEmail": "candidate@gmail.com",
    "verifiedAt": "2026-05-01T13:42:00.000Z",
    "mode": "link_and_code"
  }
}

4.2 — Notify Submitter — Verification Expired (notification)

This is the recovery branch for the expired outcome (link or code TTL elapsed). Wire it via Step 5 — this panel just defines the step.

Config Field Value
Step ID notify-otp-expired
Step Name Notify Submitter — Verification Expired
Type notification
Recipient Type Internal User
Recipient User {{submitter.id}}
Subject Override Onboarding paused — {{firstName}} {{lastName}} did not verify their email
Custom Body The verification email sent to {{personalEmail}} expired before {{firstName}} clicked the link or entered the code. Resubmit the request once you have a current address for them.
Require Acceptance unchecked

4.3 — Notify Submitter — Too Many Attempts (notification)

The exhausted recovery branch. Same shape as 4.2 but with a different message:

Config Field Value
Step ID notify-otp-exhausted
Step Name Notify Submitter — Too Many Attempts
Type notification
Recipient Type Internal User
Recipient User {{submitter.id}}
Subject Override Onboarding paused — {{firstName}} {{lastName}} entered an invalid code too many times
Custom Body {{firstName}} entered the wrong verification code five times. The current request has been closed; please resubmit and confirm the address with them out-of-band first.
Require Acceptance unchecked

4.4 — Derive Account Details (transform)

Generate a stable AD username (e.g. flast) and a tracking identifier from the verified inputs.

Config Field Value
Step ID derive-account-details
Step Name Derive Account Details
Type transform
Outputs adUsername, displayName, onboardingId
Script see below
var v = floh.variables;

// Username = first initial + last name, lowercased and ASCII-folded.
// Adjust to match your AD naming policy (e.g. "first.last").
//
// COLLISION RISK: this naive scheme produces the same username for
// "Jane Smith" and "James Smith" → both `jsmith`. Step 4.7's
// `role_grant` will fail on the second onboard with no recovery
// edge in the graph. For production, add a `connector` step BEFORE
// step 4.7 that calls `test-activedirectory.checkAccountExists` (or
// `findUser`) on the candidate username and, if taken, append a
// numeric suffix in a follow-up `transform` (`jsmith` → `jsmith2`).
// The walkthrough leaves this out so the happy-path graph stays
// compact — see the "Known limitations" section for the full story.
var first = String(v.firstName || "")
  .trim()
  .toLowerCase();
var last = String(v.lastName || "")
  .trim()
  .toLowerCase();
var ascii = function (s) {
  return s.replace(/[^a-z0-9]/g, "");
};
var adUsername = (ascii(first).charAt(0) || "x") + (ascii(last) || "user");

// Display name uses preferred name when present, legal name otherwise.
var displayName =
  v.preferredName && String(v.preferredName).trim()
    ? String(v.preferredName).trim() + " " + v.lastName
    : v.firstName + " " + v.lastName;

// `floh.uuid()` is documented for non-cryptographic unique-identifier
// use (see `docs/workflows/transform-step-guide.md`). We use it to
// stamp every onboarding event with a stable id that downstream
// systems (offboarding workflow, audit log queries) can key off
// without depending on the run.id template token, which is NOT
// available in the workflow's variable bag.
var onboardingId = floh.uuid();

return {
  adUsername: adUsername,
  displayName: displayName,
  onboardingId: onboardingId,
};

Click Test Script under the editor and paste sample variable values. Confirm the username and display-name shape, then save.

4.5 — Create Floh Profile (user_create)

Config Field Value
Step ID create-floh-user
Step Name Create Floh Profile
Type user_create
Email {{personalEmailVerified.verifiedEmail}}
Display Name {{displayName}}
If Exists fail (refuse to onboard the same person twice)
Set As Subject (general workflow only; binds the run's subject_id to the new user once it exists, so the new hire shows up under GET /api/users/:id/workflow-activity)
Initial Attributes see below
Output Key flohUser

Initial attributes (the JSON entry on the Initial Attributes field):

{
  "preferredName": "{{preferredName}}",
  "personalEmail": "{{personalEmailVerified.verifiedEmail}}",
  "onboardingId": "{{onboardingId}}"
}

preferredName empty-string behavior. preferredName is an optional variable; when the submitter leaves it blank, the Handlebars template above renders "preferredName": "" (an empty string, not a missing key). Downstream consumers that want "preferred name was not provided" semantics should test attribute.preferredName?.length > 0 (or Boolean(attribute.preferredName)) rather than attribute.preferredName !== undefined — the key is always present once user_create writes it. We accept this trade-off because the initialAttributes JSON field is treated as a single Handlebars template at runtime; a {{#if preferredName}}…{{/if}} guard around the key would either leave a trailing comma in the rendered JSON (depending on key order) or require committing to a specific key-ordering hack, both of which are more brittle than documenting the empty-string default.

On success the bag carries:

{
  "userCreated": true,
  "userCreateUserId": "f4a8…",
  "userCreateEmail": "candidate@gmail.com",
  "flohUser": {
    /* full payload */
  }
}

4.6 — Set Manager (profile_update)

Config Field Value
Step ID set-manager
Step Name Set Manager
Type profile_update
User ID {{userCreateUserId}}
Manager ID {{manager.id}}

This writes the user row's first-class manager_id column so that approver: "manager:{{user.id}}" resolves correctly on subsequent runs. Cycles (A → B → A) and self-management are rejected at the repository layer with a typed error; successful writes emit a user.manager_changed audit row.

4.7 — Grant "New Employee" Role (role_grant)

Config Field Value
Step ID grant-new-employee-role
Step Name Grant "New Employee" Role
Type role_grant
Role Definition ID (UUID of the "New Employee" role from prerequisite 7)
User ID {{userCreateUserId}}
Fail on Partial false (unchecked — allows partial provisioning so the run continues)
On Duplicate skip

This single step provisions both external accounts via the entitlement system:

  1. Creates a role_assignment record for "New Employee" linked to this run.
  2. For each entitlement on the role:
  3. Corporate AD Account → calls test-activedirectory.createAccount with the auto-injected user identity. Creates the AD account and establishes an external identity link in Floh.
  4. New Employee Authifi Group → calls authifi.addToGroup with the auto-injected user identity. Creates the Authifi user (via createIfMissing: true) and adds them to the new-employee group.
  5. Marks the assignment as active.

The step produces output variables: roleAssignmentId, roleProvisioned, provisionedCount, failedCount.

Why entitlements instead of direct connector steps? Entitlement definitions carry both a provision_config and a deprovision_config. When the Employee Offboarding workflow runs role_revoke, the entitlement system automatically calls each deprovision command (disableAccount, removeFromGroup). This means deprovisioning logic is defined once and reused — no need to maintain parallel connector steps in the offboarding workflow.

Test-connector note. The test-activedirectory connector requires an explicit username parameter on createAccount. The entitlement auto-injection provides userEmail and userDisplayName from the Floh user row. The test connector's ensureUsernameFromEmail hook auto-derives username from userEmail's local part at runtime when no explicit username is provided in the config. In production, a real AD connector derives the SAM account name from the provided email or UPN directly.

The first_password_invitation step replaces the old pattern of generating a temporary password in a transform step and interpolating it into a notification body. Instead, it issues a one-shot HMAC-peppered link the new hire clicks to choose their own password — the plaintext never traverses email or the run variable bag. The step's own messageTemplate doubles as the welcome email, so a separate notification step is not needed.

Config Field Value
Step ID first-password-invitation
Step Name Send Welcome Email + First Password Link
Type first_password_invitation
Connector ID test-activedirectory (or your real AD connector instance)
User Key {{adUsername}}
Recipient Email {{personalEmailVerified.verifiedEmail}}
Expires (h) 48
Custom Subject Welcome to Acme — set your password to get started
Message Template see below
Output Variable firstPasswordSet

Message template:

Hi {{#if preferredName}}{{preferredName}}{{else}}{{firstName}}{{/if}},

Welcome to Acme! Your accounts are ready. Set your password to
get started:

  Username:  {{adUsername}}
  Set your password: {{firstPasswordUrl}}

This link expires at {{firstPasswordExpiresAt}}. After setting
your password, sign in at https://login.corp.example.com.
Your manager ({{manager.displayName}}) will reach out about
team-specific onboarding.

Welcome aboard,
The Acme IT team

The step pauses at waiting_first_password_set. When the new hire clicks the link, the portal page at /first-password lets them choose a password; on submit the public route calls the AD connector's setPassword command. A connector_rejected (password too short, for example) does not fail the step — the invitation stays pending so the user can retry. On success the bag carries:

{
  "firstPasswordSet": {
    "passwordSet": true,
    "connectorId": "test-activedirectory",
    "userKey": "jdoe",
    "acceptedAt": "2026-05-02T14:30:00.000Z"
  }
}

See First Password Invitation for the full contract and security model.

4.9 — Swap Primary Email (profile_update)

After provisioning completes, rotate the Floh user's primary email from the personal address (used at sign-up) to the corporate AD address. This ensures {{user.email}} resolves to the corporate address in downstream workflows.

Config Field Value
Step ID swap-primary-email
Step Name Swap Primary Email
Type profile_update
User ID {{userCreateUserId}}
Email {{adUsername}}@corp.example.com (replace corp.example.com with your AD primary domain; adUsername is derived in step 4.4 using the same local-part logic the test connector uses internally)
Require Reconfirm false (set to true and add a follow-up verify_contact step if you need the corporate address re-verified before the user is fully confirmed)

The UNIQUE(iss, email, deleted_at) index is honoured — collisions surface a typed EMAIL_IN_USE step error. Successful writes emit a user.email_changed audit row.

If the new hire doesn't set their password before the invitation expires, this notification alerts IT so they can reissue the onboarding or resend the invitation manually.

Config Field Value
Step ID notify-password-link-expired
Step Name Notify IT — Password Link Expired
Type notification
Recipient Type Internal User (or a team mailbox, per your ops model)
Recipient User use the autocomplete to pick the IT on-call user or distribution group
Subject Override Onboarding paused — password setup link expired for {{firstName}} {{lastName}}
Custom Body The first-password invitation expired before {{firstName}} {{lastName}} completed it. Reissue the onboarding workflow or resend the invitation from the run detail page.
Require Acceptance unchecked

4.11 — End

Config Field Value
Step ID end
Step Name End
Type end

The start step is created automatically when the workflow is saved — you don't need to add it manually.

Step 5 — Wire the graph

Open the Graph tab and draw the following transitions. Each click in the editor goes from a step's output port (right side) to the next step's input port (left side). Multi-output steps (verify_contact here) prompt for a transition label after the second or later draw.

# From Edge label To
1 Start success Verify Personal Email
2 Verify Personal Email success Derive Account Details
3 Verify Personal Email expired Notify Submitter — Verification Expired
4 Verify Personal Email exhausted Notify Submitter — Too Many Attempts
5 Derive Account Details success Create Floh Profile
6 Create Floh Profile success Set Manager
7 Set Manager success Grant "New Employee" Role
8 Grant "New Employee" Role success Send Welcome Email + First Password Link
9 Send Welcome Email + First Password Link success Swap Primary Email
10 Send Welcome Email + First Password Link expired Notify IT — Password Link Expired
11 Swap Primary Email success End
12 Notify Submitter — Verification Expired success End
13 Notify Submitter — Too Many Attempts success End
14 Notify IT — Password Link Expired success End

Tip: if you don't wire the expired and exhausted edges explicitly, the engine still recovers — it falls through to on: "error" and then to the workflow's global onError policy (stop in our case). The run won't hang, but the submitter never hears why; the explicit notification edges in 3 and 4 are worth the two extra steps.

Recommended (not shown above): add an on: "error" edge from the role_grant step (4.7) and the email-swap step (4.9, which can surface EMAIL_IN_USE) to a dedicated "Provisioning Failed — admin attention required" notification → End. The default onError: stop strategy stops the run silently otherwise, leaving partial state across Floh / AD / Authifi.

The Provisioning Chain — with failure routing template (Templates → Insert from Template → "Workflow Definition") gives you a working starter graph with these edges already wired. The workflow designer surfaces a yellow lint banner (PROVISIONING_CHAIN_NO_ERROR_EDGE) whenever a chain of connector or role_grant steps lacks explicit on: "error" edges. See the Partial Provisioning Runbook for cleanup steps when a partial-provisioning failure does happen.

Step 6 — Save and validate

Click Save Draft. The designer runs:

  1. Static validation — duplicate step IDs, unreachable nodes, missing required config fields.
  2. Role reference validation — the roleDefinitionId on the role_grant step resolves to an existing role definition.
  3. Lint warning — if you skipped the recommended on: "error" edge on role_grant, you'll see a yellow PROVISIONING_CHAIN_NO_ERROR_EDGE lint banner.

Step 7 — Test Run before publishing

  1. From the workflow detail page, click Test Run.
  2. Fill in the form. Use your own email for personalEmail so you can complete the OTP loop end-to-end. Pick the candidate's manager from the Manager typeahead (start typing a name, email, or username — the picker is scoped to users you can see) or leave the field blank to default to {{submitter.id}} (you).
  3. Click Start Run.
  4. The run pauses at Verify Personal Email with status waiting_verification. Check the email at the address you used, click the link, and enter the 6-digit code on /verify.
  5. After verification, the provisioning steps (Derive Account DetailsCreate Floh ProfileSet ManagerGrant "New Employee" Role) execute automatically. Watch the Runs tab — each should reach succeeded within seconds.
  6. The run pauses again at Send Welcome Email + First Password Link with status waiting_first_password_set. Check the email you used — it contains the welcome message and a one-time password-setup link. Click the link, set a password on the /first-password portal page, and submit.
  7. The run resumes. Swap Primary EmailEnd completes automatically.
  8. Confirm:
  9. Floh user — the new user appears under Users with the personal email and the custom attributes from initialAttributes. The manager column shows the selected manager.
  10. Role assignment — the Role Assignments page shows an active assignment for "New Employee" linked to this run.
  11. AD account — open the test-activedirectory connector's Commands tab and run findUser with the derived username. Run authenticate with the password you set to verify it took.
  12. Authifi membership — run checkGroupMembership on the Authifi connector with the new userId and the new-employee group.
  13. Primary email — the user's primary email should now be the corporate AD address (from step 4.9).

Step 8 — Publish and add to the catalog

  1. Click Publish on the workflow detail page.
  2. Open the Catalog tab and configure:
Field Value
Published checked
Icon person_add
Description Provision a new employee in Floh, AD, and Authifi after personal-email verification.
Tags hr, onboarding, provisioning
Submission Groups restrict to hr-admins and engineering-managers (don't leave open to all authenticated users — workflow:provision_users is a sensitive permission)

Catalog changes auto-save after a short debounce.

Runtime walkthrough

  1. Manager opens the catalog and picks New Employee Onboarding.
  2. Manager fills in the form — first/last/preferred name, personal email, and the candidate's manager via the Manager typeahead picker (or leaves the field blank to default to themselves via {{submitter.id}}). They click Submit.
  3. OTP email lands in the candidate's inbox. They click the link; /verify issues a code that lands in their inbox in a second email.
  4. Candidate enters the code on /verify. The run resumes.
  5. Engine derives adUsername (jdoe), displayName (Jane Doe), and onboardingId.
  6. Floh user row is created with the personal email as email and the custom attributes from initialAttributes.
  7. Manager is linked — the profile_update step writes manager_id on the Floh user row.
  8. "New Employee" role is granted — the role_grant step provisions both entitlements:
  9. AD entitlement → createAccount creates jdoe@corp.example.com.
  10. Authifi entitlement → addToGroup creates the user in the target tenant (via createIfMissing: true) and adds them to new-employee. Both entitlement instances are marked provisioned; the role assignment is marked active.
  11. Welcome email + password link lands at the personal address. The run pauses at waiting_first_password_set.
  12. Candidate sets their password by clicking the link and choosing a password on the /first-password portal page. The AD connector's setPassword command fires server-side. The run resumes.
  13. Primary email is swapped from the personal address to the corporate AD address via profile_update.
  14. Run completes. The submitter sees Completed on the run detail page and can drill into each step's variables.

Historical context

This walkthrough was originally drafted under epic LSA-8544 while the building blocks it exercises were still under construction. Eight gaps were tracked as individual stories (LSA-8652 through LSA-8659) covering subject linkage, in-portal user picker, manager linkage, test-AD password parity, first-password invitation, email rotation, failure-routing scaffold, and cluster-wide resend cooldown. All eight have shipped; the walkthrough above uses them directly with no workarounds.

Known limitations

These are deliberate scope cuts in this walkthrough rather than shortcomings in the underlying Floh building blocks. Each is fixable inside the workflow today by adding extra steps; the walkthrough omits them only so the happy-path graph stays compact.

  • No AD username collision handling. The step 4.4 transform produces <first-initial><last-name> and the entitlement system forwards it to createAccount. "Jane Smith" and "James Smith" both resolve to jsmith, and the second onboard fails at the role_grant step. To handle this in production, insert a connector step between 4.4 and 4.7 that calls test-activedirectory.checkAccountExists (or findUser) on the candidate username, route the exists: true edge into a follow-up transform that appends a numeric suffix (jsmithjsmith2jsmith3 …), and loop until the check returns exists: false. The happy path then continues to role_grant as written.
  • No on: "error" edges on provisioning / profile steps. The walkthrough wires only the verify_contact and first_password_invitation failure edges. The role_grant step (4.7) and the email-swap step (4.9, which can surface EMAIL_IN_USE) fall through to the workflow's onError: "stop" policy on failure, leaving the run in failed and the side-effects in a half-completed state. For production, add an on: "error" edge from each of these steps to a single Provisioning Failed — Admin Attention Required notification step that pages the IT operations group.
  • Test-connector simplification. The test-activedirectory connector normally requires an explicit username on createAccount, but its ensureUsernameFromEmail hook auto-derives it from the email local part, so the walkthrough succeeds without extra config. A production real-AD connector typically derives the SAM account name from the provided UPN directly — verify your connector's parameter contract before promoting to production.
  • Authifi provisioning timing. With the entitlement-based approach, addToGroup runs as part of role_grant (step 4.7), which executes before the user sets their first password (step 4.8). In the original direct-connector design, addToGroup ran after password setup. If your Authifi group grants immediate access to resources that require a password, the user briefly has group membership without a usable credential. In practice this is acceptable because the first_password_invitation step follows immediately, but note the ordering difference if your security model requires credentials before group assignment.

What to do next

  • Run the companion Employee Offboarding workflow to reverse provisioning — role_revoke automatically calls the entitlement deprovision commands (disableAccount, removeFromGroup) without any direct connector steps.
  • Subscribe the IT operations group to the workflow's runs feed (GET /api/runs?workflowId=...&status=failed) so partial provisioning surfaces fast.
  • Use the Provisioning Chain — with failure routing template (Templates → Insert from Template → "Workflow Definition") to scaffold on: "error" edges and a cleanup notification for the role_grant step.
  • Create entitlement definitions for additional connectors (Google Workspace, SCIM providers) so their provisioning and deprovisioning are also automatic via role grant/revoke.

References