> ## Documentation Index
> Fetch the complete documentation index at: https://docs.fluz.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Enrollment Widget

> The ENROLLMENT widget type: send a user through account setup right after OAuth, choosing which steps they see — identity verification, funding sources, and PIN.

The `ENROLLMENT` widget type walks a user through account setup immediately after they authorize your application. It has the same shape as the other widget types — the user is redirected straight into a task after authorizing — with one difference: **you choose which setup steps the user sees.**

Unlike the payout and pay-in types, this widget moves no money. There is no amount to confirm and no transaction to approve.

<Note>
  `ENROLLMENT` is currently available in **staging only**. Production support is coming; until then, a production token with `transactionType: "ENROLLMENT"` will be rejected.
</Note>

<Info>
  **Prerequisites**

  * A widget application with OAuth settings in place. See [Configure App Widget](/developers/configure-app-widget). Any application type can run this widget, including a permissions-only `OAuth Integration` app.
  * The user must have granted your application's scopes. Scopes are checked for the application as a whole, not per step. If any are missing, the widget sends the user through the OAuth consent screen before the first step, the same as the other widget types. No step is skipped or failed because of a scope.
  * An OAuth user and an access token, same as the other widget types.
</Info>

## Choosing the steps

Pass an ordered `steps` array in the pre-approved transaction token alongside `transactionType: "ENROLLMENT"`:

```javascript theme={null}
import jwt from 'jsonwebtoken';

const generatedToken = jwt.sign(
  {
    apiKey: '', // Your apiKey
    transactionType: 'ENROLLMENT',
    externalId: '', // Your unique identifier for the user
    steps: ['KYC', 'FUNDING_SOURCE', 'PIN'],
    jti: uuidv4(),
  },
  secret, // Your apiSecret
  { expiresIn: '1 day' }
);
```

`amount` is not required for this widget type. Everything else about the token is unchanged — see [Set Up Your Server](/developers/setting-up-your-server).

### Supported steps

| Step             | What the user does                                                                                                                       |
| :--------------- | :--------------------------------------------------------------------------------------------------------------------------------------- |
| `KYC`            | Completes personal identity verification, escalating to document capture if needed. See [Verify by Widget](/verify-customers-by-widget). |
| `FUNDING_SOURCE` | Links one or more funding sources — a bank account via Plaid, a bank card, or PayPal.                                                    |
| `PIN`            | Sets their Fluz transaction PIN.                                                                                                         |

<Note>
  This widget runs **personal** identity verification only. Business verification (KYB) is not one of its steps — use the dedicated KYB widget type for that. See [Business Registration](/docs/business-registration).
</Note>

### Order

Steps run in the order you list them. Fluz does not reorder them, and there are no invalid combinations — none of the three steps is a prerequisite for another, so `["PIN", "FUNDING_SOURCE", "KYC"]` is as valid as `["KYC", "FUNDING_SOURCE", "PIN"]`.

List the steps in the order that makes sense for your product.

## Skip behavior

A step the user has already satisfied is skipped silently — they never see its screen.

| Step             | Skipped when                                      |
| :--------------- | :------------------------------------------------ |
| `KYC`            | The user has already passed identity verification |
| `PIN`            | The user already has a PIN set                    |
| `FUNDING_SOURCE` | **Never**                                         |

The funding-source step is never skipped, because a user who already has one source may want to add another. It always renders, and the user decides when to move on.

If every step you requested is already satisfied, the widget completes immediately without rendering a step screen.

<Info>
  Because `FUNDING_SOURCE` never auto-skips, a `steps` list that includes it always shows at least one screen. If you want the "nothing to do, complete immediately" outcome to be reachable, request only `KYC` and `PIN`.
</Info>

## The funding source step

This step is deliberately open-ended. The user can add as many funding sources as they like in one visit — the screen lists what they have added so far and carries an explicit **Continue** control, so advancing is their decision rather than something that happens automatically after the first successful link.

A user who does not want to add anything can move on without adding a source.

Funding-source changes do not emit a webhook yet. A dedicated event is in progress. Until it ships, read the user's funding sources with the `getWallet` query after the flow completes. See [Funding sources](/features/funding-sources).

## Completion

When the last step resolves, the widget shows a completion screen. If you supplied a `callbackUrl`, it carries a button back to your application; otherwise it offers a **Done** control that closes the widget.

<Warning>
  Do not infer success from the widget closing — a user can dismiss it at any point. Rely on the `onSuccess` and `onError` callbacks, and on webhooks for the individual steps: identity verification events fire independently of the widget. See [Webhooks](/fluz-dashboard/webhooks).
</Warning>

## Rejected tokens

The `steps` claim is validated when the session opens. These all reject the session rather than degrading to a partial flow:

| Problem                                                                | Result                                                                              |
| :--------------------------------------------------------------------- | :---------------------------------------------------------------------------------- |
| `steps` missing entirely                                               | Missing-parameter error                                                             |
| `steps` is not an array, or is empty                                   | Invalid-parameter error                                                             |
| `steps` contains a value that is not `KYC`, `FUNDING_SOURCE`, or `PIN` | Invalid-parameter error naming the unsupported value and listing the supported ones |
| `steps` lists the same step twice                                      | Invalid-parameter error naming the duplicate                                        |

The user sees a generic "invalid widget link" screen — the specific reason is not shown to them, since a malformed token is an integration problem rather than something they can act on. Check your token generator, and see the error detail delivered to your `onError` callback.

## Next steps

<CardGroup cols={2}>
  <Card title="Set up your server" icon="server" href="/developers/setting-up-your-server">
    Generate the pre-approved transaction token in your language.
  </Card>

  <Card title="Embed the widget" icon="code" href="/developers/adding-the-js-widget-to-your-page">
    Script tag, init call, callbacks.
  </Card>

  <Card title="Verify by widget" icon="circle-check-big" href="/verify-customers-by-widget">
    What the user experiences during the KYC step.
  </Card>

  <Card title="Funding sources" icon="coins" href="/features/funding-sources">
    What you can do with a source once it is linked.
  </Card>
</CardGroup>
