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

# Customer onboarding with KYC

> Build a multi-step customer onboarding process with identity verification, document generation, manual review routing, and email notifications.

In this tutorial, you build a **customer onboarding process** — a BPMN-driven app that collects personal data through multi-step forms, verifies the customer's identity via an external KYC API, generates a welcome letter, and routes failed verifications to a manual review queue.

**What you will build:**

* A **multi-step form** that collects personal details, address, employment, and document uploads
* A **KYC verification** integration that calls an external REST API via an integration workflow
* An **exclusive gateway** that routes the process based on verification results
* A **document generation** step that produces a personalized welcome letter
* A **manual review task** assigned to compliance officers when KYC fails
* An **email notification** sent upon successful onboarding
* A **timer boundary event** for SLA tracking on the review task

**Node types used:** User Task, Send Message Task, Receive Message Task, Exclusive Gateway, Timer Boundary Event

**Patterns demonstrated:** Multi-step form collection, REST API integration via workflows, conditional branching, document generation, task assignment, notification sending

***

## Architecture overview

The process follows a **collect-verify-decide** pattern. Customer data is gathered through a series of user task nodes, sent to a KYC service for verification, and then routed based on the result.

```mermaid theme={"system"}
flowchart TD
    A(("Start")) --> B["User Task:<br/>Personal Information"]
    B --> C["User Task:<br/>Address Details"]
    C --> D["User Task:<br/>Employment & Income"]
    D --> E["User Task:<br/>Document Upload"]
    E --> F["Send Message Task:<br/>Start KYC Check"]
    F -. "Workflow:<br/>kycVerification" .-> F
    F --> G["Receive Message Task:<br/>KYC Result"]
    G --> H{"Exclusive Gateway:<br/>KYC Decision"}
    H -- "PASSED" --> I["Generate Welcome Letter<br/>(Send/Receive Message)"]
    H -- "FAILED / REVIEW" --> J["User Task: Manual Review<br/>⏱ 48h Timer Boundary"]
    J -- "APPROVED" --> I
    J -- "REJECTED" --> K(("End<br/>(Rejected)"))
    I --> L["Send Email Notification<br/>(Send/Receive Message)"]
    L --> M(("End<br/>(Success)"))
```

The main process orchestrates four form steps, delegates KYC verification to an integration workflow, and uses an exclusive gateway to branch into success or manual review paths.

***

## Prerequisites

Before starting, make sure you have:

* Access to a FlowX Designer workspace
* The **Documents Plugin** deployed and configured (for document generation)
* The **Notifications Plugin** deployed and configured (for email sending)
* A document template named `welcomeLetter` created in **Document Templates** (HTML template with placeholders for customer name and date)
* Familiarity with creating processes, user tasks, and actions in FlowX

<Tip>
  If you do not have a real KYC service available, you can simulate one using a mock REST API endpoint (for example, a simple service that returns `PASSED` or `FAILED` based on predefined rules). The integration workflow calls this endpoint the same way it would call a production KYC provider.
</Tip>

***

## Data model

Define the following keys in your process data model. All keys use camelCase and are nested under logical groups.

| Key path                      | Type   | Description                                          |
| ----------------------------- | ------ | ---------------------------------------------------- |
| `customer.firstName`          | String | Customer first name                                  |
| `customer.lastName`           | String | Customer last name                                   |
| `customer.dateOfBirth`        | String | Date of birth (ISO format)                           |
| `customer.nationalId`         | String | National ID or passport number                       |
| `customer.email`              | String | Email address                                        |
| `customer.phone`              | String | Phone number                                         |
| `address.street`              | String | Street address                                       |
| `address.city`                | String | City                                                 |
| `address.postalCode`          | String | Postal code                                          |
| `address.country`             | String | Country (ISO 3166 code)                              |
| `employment.status`           | String | `EMPLOYED`, `SELF_EMPLOYED`, `UNEMPLOYED`, `RETIRED` |
| `employment.employer`         | String | Employer name (if employed)                          |
| `employment.monthlyIncome`    | Number | Monthly income in local currency                     |
| `employment.startDate`        | String | Employment start date                                |
| `documents.identityDoc`       | Object | Uploaded identity document reference                 |
| `documents.proofOfAddress`    | Object | Uploaded proof of address reference                  |
| `kycResult`                   | Object | KYC verification response from external API          |
| `kycResult.status`            | String | `PASSED`, `FAILED`, or `REVIEW`                      |
| `kycResult.score`             | Number | Risk score (0-100)                                   |
| `kycResult.reasons`           | Array  | List of reasons if not passed                        |
| `reviewDecision`              | String | Manual review outcome: `APPROVED` or `REJECTED`      |
| `reviewNotes`                 | String | Notes from the compliance reviewer                   |
| `generatedDocs.welcomeLetter` | Object | Generated welcome letter document reference          |
| `notificationResult`          | Object | Email notification delivery result                   |

***

## Step 1: Create the process definition

<Steps>
  <Step title="Create a new process">
    In FlowX Designer, navigate to your project and create a new process definition named `customerOnboarding`.

    Set the platform to **Web Only** (or **Omnichannel** if you plan to support mobile).
  </Step>

  <Step title="Add swimlanes">
    Create two swimlanes:

    * **Customer** — for the user-facing form steps
    * **BackOffice** — for the manual review task (assigned to a compliance role)

    This ensures the review task is routed to the right team while the customer interacts with the form steps.
  </Step>
</Steps>

<Info>
  For details on configuring swimlanes and role-based access, see the [Swimlanes](../../docs/platform-deep-dive/user-roles-management/swimlanes) documentation.
</Info>

***

## Step 2: Build the data collection forms

Create four **User Task** nodes in the **Customer** swimlane, connected in sequence. Each node represents one step of the onboarding form.

### User Task 1: Personal information

**Node name:** `personalInfo`

Open the UI Designer and add a **Card** with a **Form** containing these fields:

| Component  | Label         | Key                    | Validation             |
| ---------- | ------------- | ---------------------- | ---------------------- |
| Input      | First name    | `customer.firstName`   | Required               |
| Input      | Last name     | `customer.lastName`    | Required               |
| Datepicker | Date of birth | `customer.dateOfBirth` | Required, must be 18+  |
| Input      | National ID   | `customer.nationalId`  | Required               |
| Input      | Email         | `customer.email`       | Required, email format |
| Input      | Phone         | `customer.phone`       | Required               |

Add a **Button** labeled **Next** with an action of type **Save Data** to persist the form values.

### User Task 2: Address details

**Node name:** `addressInfo`

| Component | Label          | Key                  | Validation                           |
| --------- | -------------- | -------------------- | ------------------------------------ |
| Input     | Street address | `address.street`     | Required                             |
| Input     | City           | `address.city`       | Required                             |
| Input     | Postal code    | `address.postalCode` | Required                             |
| Select    | Country        | `address.country`    | Required (populate from enumeration) |

<Tip>
  Use an [enumeration](../../docs/platform-deep-dive/core-extensions/content-management/enumerations) for the country dropdown. Create an enumeration named `countries` with ISO 3166 country codes and labels.
</Tip>

Add **Back** and **Next** buttons. Set `canGoBack: true` on this node so the customer can return to the previous step.

### User Task 3: Employment and income

**Node name:** `employmentInfo`

| Component  | Label             | Key                        | Validation           |
| ---------- | ----------------- | -------------------------- | -------------------- |
| Radio      | Employment status | `employment.status`        | Required             |
| Input      | Employer name     | `employment.employer`      | Required if EMPLOYED |
| Input      | Monthly income    | `employment.monthlyIncome` | Required, numeric    |
| Datepicker | Start date        | `employment.startDate`     | Required if EMPLOYED |

<Info>
  Use a [conditional expression](../../docs/building-blocks/ui-designer/ui-component-types/form-elements/input-form-field) on the Employer and Start Date fields to show them only when `employment.status` is `EMPLOYED` or `SELF_EMPLOYED`.
</Info>

### User Task 4: Document upload

**Node name:** `documentUpload`

| Component   | Label                                             | Key                        |
| ----------- | ------------------------------------------------- | -------------------------- |
| File Upload | Identity document (passport or ID card)           | `documents.identityDoc`    |
| File Upload | Proof of address (utility bill or bank statement) | `documents.proofOfAddress` |

Add a **Submit** button that triggers the process to move to the KYC verification step.

<Warning>
  Uploaded files are stored via the Documents Plugin. Make sure the plugin is configured with an S3-compatible storage backend before testing this step.
</Warning>

***

## Step 3: Set up the KYC verification integration

The KYC check is handled by an integration workflow that calls an external REST API. This keeps the BPMN process clean and the integration logic reusable.

### Create the data source

<Steps>
  <Step title="Add a RESTful System data source">
    Navigate to **Integrations** > **Data Sources** and create a new data source:

    * **Type:** RESTful System
    * **Name:** KYC Provider
    * **Code:** `kycProvider`
    * **Base URL:** `https://api.{your-kyc-provider}.com/v1`
    * **Authorization:** Bearer Token (configure the token value using a [Configuration Parameter Override](../../docs/projects/runtime/configuration-parameters-overrides) so it differs per environment)
  </Step>

  <Step title="Define the verification endpoint">
    Under the data source, add an endpoint:

    * **Method:** POST
    * **Path:** `/verify`
    * **Body type:** JSON
    * **Request body example:**

    ```json theme={"system"}
    {
      "firstName": "Jane",
      "lastName": "Smith",
      "dateOfBirth": "1990-05-15",
      "nationalId": "AB1234567",
      "country": "GB"
    }
    ```

    * **Response example:**

    ```json theme={"system"}
    {
      "status": "PASSED",
      "score": 92,
      "reasons": [],
      "verificationId": "kyc-20260311-001"
    }
    ```
  </Step>
</Steps>

### Create the integration workflow

<Steps>
  <Step title="Create the workflow">
    Navigate to **Integrations** > **Workflows** and create a new workflow named `kycVerification`.
  </Step>

  <Step title="Add a REST Call node">
    Add a **REST API Call** node connected to the Start node. Configure it to use the `kycProvider` data source and the `/verify` endpoint.

    Map the input variables to the request body:

    ```json theme={"system"}
    {
      "firstName": "${customer.firstName}",
      "lastName": "${customer.lastName}",
      "dateOfBirth": "${customer.dateOfBirth}",
      "nationalId": "${customer.nationalId}",
      "country": "${address.country}"
    }
    ```
  </Step>

  <Step title="Add an End Flow node">
    Add an **End Flow** node after the REST Call. Set the body to pass the response back:

    ```json theme={"system"}
    {
      "output": ${kycResponse}
    }
    ```
  </Step>
</Steps>

<Info>
  For a detailed guide on building integration workflows, see the [Integration Designer](../../docs/platform-deep-dive/integrations/integration-designer) documentation.
</Info>

***

## Step 4: Trigger KYC from the BPMN process

Back in the `customerOnboarding` process, add a **Send Message Task** and a **Receive Message Task** after the `documentUpload` user task.

### Send Message Task: Start KYC check

**Node name:** `sendKycRequest`

Add an action:

* **Action type:** Start Integration Workflow
* **Workflow:** `kycVerification`
* **Input mapping:**

```json theme={"system"}
{
  "customer": "${customer}",
  "address": "${address}"
}
```

* **Trigger:** Automatic
* **Mandatory:** Yes

### Receive Message Task: Receive KYC result

**Node name:** `receiveKycResult`

* **Key Name:** `kycResult`

The workflow output is captured at this key. Subsequent nodes can access `kycResult.status`, `kycResult.score`, and `kycResult.reasons`.

<Info>
  The process waits at the Receive Message Task until the integration workflow completes and returns its output. For details on this pattern, see [Start Integration Workflow Action](../../docs/building-blocks/actions/start-integration-workflow).
</Info>

***

## Step 5: Add the KYC decision gateway

Add an **Exclusive Gateway** node after the `receiveKycResult` node.

**Node name:** `kycDecision`

Configure two outgoing branches:

| Branch          | Condition (JavaScript)          | Target                                   |
| --------------- | ------------------------------- | ---------------------------------------- |
| Passed          | `kycResult.status === "PASSED"` | Go to document generation (success path) |
| Failed / Review | Default (else)                  | Go to manual review task                 |

<Tip>
  Using the default branch for the failure path ensures that any unexpected status value (not just `FAILED`) is routed to human review rather than slipping through.
</Tip>

For details on configuring exclusive gateways, see the [Exclusive Gateway Node](../../docs/building-blocks/node/exclusive-gateway-node) documentation.

***

## Step 6: Build the success path

The success path generates a welcome letter and sends a confirmation email.

### Generate the welcome letter

Add a **Send Message Task** and **Receive Message Task** pair for document generation.

#### Send Message Task: Generate welcome letter

**Node name:** `sendGenerateWelcomeLetter`

Add a **Kafka Send Action** with:

* **Topic:** `ai.flowx.plugin.document.trigger.generate.html.v1`
* **Message body:**

```json theme={"system"}
{
  "documentList": [
    {
      "customId": "welcomeLetter",
      "templateName": "welcomeLetter",
      "language": "en",
      "data": {
        "customerName": "${customer.firstName} ${customer.lastName}",
        "currentDate": "${#now('yyyy-MM-dd')}",
        "accountReference": "${kycResult.verificationId}"
      },
      "includeBarcode": false
    }
  ]
}
```

<Info>
  The `templateName` must match a document template you created in **Document Templates**. Use the WYSIWYG editor to design the welcome letter with placeholders like `{{customerName}}` and `{{currentDate}}`. For details, see [Generating documents from templates](../../docs/platform-deep-dive/core-extensions/content-management/documents-plugin/generating-from-html-templates).
</Info>

#### Receive Message Task: Receive letter result

**Node name:** `receiveWelcomeLetter`

* **Data stream topic:** use the value from `KAFKA_TOPIC_DOCUMENT_GENERATE_HTML_OUT` (default: `ai.flowx.engine.receive.plugin.document.generate.html.results.v1`)
* **Key Name:** `generatedDocs.welcomeLetter`

### Send the confirmation email

Add another **Send Message Task** and **Receive Message Task** pair for notification.

#### Send Message Task: Send email

**Node name:** `sendEmailNotification`

Add a **Kafka Send Action** with:

* **Topic:** `ai.flowx.plugin.notification.trigger.send.notification.v1`
* **Message body:**

```json theme={"system"}
{
  "templateName": "onboardingComplete",
  "channel": "email",
  "receivers": [
    {
      "email": "${customer.email}"
    }
  ],
  "data": {
    "customerName": "${customer.firstName} ${customer.lastName}",
    "subject": "Welcome - Your account is ready"
  }
}
```

<Info>
  Create the `onboardingComplete` notification template in the Notifications Plugin before testing. For configuration details, see the [Notifications Plugin](../../docs/platform-deep-dive/core-extensions/content-management/notifications-plugin/notifications-plugin-overview) documentation.
</Info>

#### Receive Message Task: Receive email confirmation

**Node name:** `receiveEmailConfirmation`

* **Data stream topic:** use the value from `KAFKA_TOPIC_NOTIFICATION_INTERNAL_OUT` (default: `ai.flowx.engine.receive.plugin.notification.confirm.send.notification.v1`)
* **Key Name:** `notificationResult`

After this node, add an **End Event** node to close the success path.

***

## Step 7: Build the manual review path

When KYC fails or returns a `REVIEW` status, the process routes to a human reviewer.

### User Task: Manual review

**Node name:** `manualReview`

Place this node in the **BackOffice** swimlane.

Open the UI Designer and create a review interface:

| Component        | Label         | Key                                        | Notes                           |
| ---------------- | ------------- | ------------------------------------------ | ------------------------------- |
| Text             | Customer name | `customer.firstName` + `customer.lastName` | Read-only display               |
| Text             | National ID   | `customer.nationalId`                      | Read-only display               |
| Text             | KYC status    | `kycResult.status`                         | Read-only display               |
| Text             | Risk score    | `kycResult.score`                          | Read-only display               |
| Text             | Reasons       | `kycResult.reasons`                        | Read-only list                  |
| Select           | Decision      | `reviewDecision`                           | Options: `APPROVED`, `REJECTED` |
| Input (textarea) | Notes         | `reviewNotes`                              | Required                        |

Add two buttons:

* **Approve** — sets `reviewDecision` to `APPROVED` and advances
* **Reject** — sets `reviewDecision` to `REJECTED` and advances

<Tip>
  Enable **Update Task Management** on this node so the task appears in the Task Manager inbox for compliance officers. Assign the node to a swimlane with the appropriate role (for example, `COMPLIANCE_REVIEWER`).
</Tip>

### Timer boundary event: SLA escalation

Attach a **Timer Boundary Event** to the `manualReview` node to enforce a 48-hour SLA.

* **Timer type:** Duration
* **Duration:** `PT48H` (ISO 8601 format — 48 hours)
* **Interrupting:** No (non-interrupting — the review can still complete after escalation)

Connect the timer to a **Send Message Task** that sends an escalation notification:

```json theme={"system"}
{
  "templateName": "reviewEscalation",
  "channel": "email",
  "receivers": [
    {
      "email": "${escalation.managerEmail}"
    }
  ],
  "data": {
    "customerName": "${customer.firstName} ${customer.lastName}",
    "hoursOverdue": "48",
    "subject": "Overdue: KYC review pending for 48 hours"
  }
}
```

<Info>
  For details on timer configuration, see [Timer Boundary Event](../../docs/building-blocks/node/timer-events/timer-boundary-event) and [Timer Expressions](../../docs/building-blocks/node/timer-events/timer-expressions).
</Info>

### Review decision gateway

Add a second **Exclusive Gateway** after the `manualReview` node.

**Node name:** `reviewDecisionGateway`

| Branch   | Condition (JavaScript)          | Target                                        |
| -------- | ------------------------------- | --------------------------------------------- |
| Approved | `reviewDecision === "APPROVED"` | Go to document generation (same success path) |
| Rejected | Default (else)                  | Go to rejection End Event                     |

For the **Approved** branch, connect to the same document generation and email notification nodes used in the success path (or duplicate them if your process layout requires separate paths).

For the **Rejected** branch, add an **End Event** node labeled `endRejected`.

***

## Step 8: Add a business rule for risk scoring (optional)

You can add a **Business Rule Action** on the `receiveKycResult` node to enrich the KYC response with custom logic before the gateway evaluates it.

**Action type:** Business Rule
**Language:** JavaScript

```javascript theme={"system"}
// Normalize the KYC result for downstream processing
var status = input.kycResult.status;
var score = input.kycResult.score;

// Override status based on custom score thresholds
if (status === "REVIEW" && score >= 75) {
  output.put("kycResult.status", "PASSED");
} else if (status === "REVIEW" && score < 50) {
  output.put("kycResult.status", "FAILED");
}

// Flag high-risk countries for additional review
var highRiskCountries = ["XX", "YY", "ZZ"];
if (highRiskCountries.indexOf(input.address.country) !== -1 && status === "PASSED") {
  output.put("kycResult.status", "REVIEW");
  output.put("kycResult.reasons", ["High-risk jurisdiction flagged for manual review"]);
}
```

<Info>
  Business rules execute synchronously within the process engine. Use them for lightweight data transformations and decision logic. For details, see [Business Rule Action](../../docs/building-blocks/actions/business-rule-action/business-rule-action).
</Info>

***

## Step 9: Configure stages for Task Manager

Define [stages](../../docs/platform-deep-dive/core-extensions/task-management/using-stages) to track progress in the Task Manager:

| Stage name       | Assigned to nodes                                                 |
| ---------------- | ----------------------------------------------------------------- |
| Data Collection  | `personalInfo`, `addressInfo`, `employmentInfo`, `documentUpload` |
| KYC Verification | `sendKycRequest`, `receiveKycResult`                              |
| Manual Review    | `manualReview`                                                    |
| Completion       | `sendGenerateWelcomeLetter`, `sendEmailNotification`              |

Enable **Update Task Management** on the first user task node (`personalInfo`) and the manual review node (`manualReview`) so that process instances appear in the Task Manager with their current stage.

***

## Testing

<Steps>
  <Step title="Test the happy path (KYC passes)">
    Start a new process instance and fill in all four form steps with valid data. Use a national ID that your mock KYC service recognizes as valid.

    Verify:

    * The KYC integration workflow completes and returns `status: PASSED`
    * The welcome letter is generated (check the document storage)
    * The confirmation email is sent to the customer email address
    * The process ends at the success End Event

    ```json theme={"system"}
    {
      "customer": {
        "firstName": "Jane",
        "lastName": "Smith",
        "dateOfBirth": "1990-05-15",
        "nationalId": "AB1234567",
        "email": "jane.smith@example.com",
        "phone": "+44 7700 900000"
      },
      "address": {
        "street": "42 Oak Lane",
        "city": "London",
        "postalCode": "SW1A 1AA",
        "country": "GB"
      },
      "employment": {
        "status": "EMPLOYED",
        "employer": "Acme Corp",
        "monthlyIncome": 4500,
        "startDate": "2020-03-01"
      }
    }
    ```
  </Step>

  <Step title="Test the manual review path (KYC fails)">
    Start a new instance with data that triggers a `FAILED` or `REVIEW` KYC status.

    Verify:

    * The process routes to the `manualReview` user task
    * The task appears in the Task Manager for the BackOffice swimlane
    * Approving the review generates the welcome letter and sends the email
    * Rejecting the review ends the process at the rejection End Event
  </Step>

  <Step title="Test SLA escalation">
    Start a manual review scenario and let the 48-hour timer expire (for testing, temporarily change the duration to `PT1M` — one minute).

    Verify:

    * The escalation email is sent to the configured manager address
    * The review task remains active (non-interrupting timer)
    * The reviewer can still approve or reject after escalation
  </Step>

  <Step title="Test form navigation">
    Verify that the **Back** button on each form step returns the user to the previous step without losing entered data.

    Verify that required field validations prevent advancing to the next step when fields are empty.
  </Step>
</Steps>

***

## What you learned

In this tutorial, you built a complete customer onboarding process that demonstrates several core FlowX capabilities:

* **Multi-step forms** — using sequential User Task nodes with form components to collect structured data across multiple screens
* **Integration workflows** — delegating external API calls (KYC verification) to reusable integration workflows triggered via the Start Integration Workflow action
* **Conditional routing** — using Exclusive Gateway nodes with JavaScript conditions to branch process logic based on data values
* **Document generation** — using the Documents Plugin with Kafka Send/Receive Message Tasks to generate documents from HTML templates
* **Notifications** — using the Notifications Plugin to send email confirmations
* **Task assignment** — using swimlanes and the Task Management plugin to route manual review tasks to specific roles
* **SLA tracking** — using Timer Boundary Events to enforce and escalate overdue tasks
* **Business rules** — using JavaScript business rules to enrich and transform data within the process

***

## Next steps

<CardGroup cols={2}>
  <Card title="Process definitions" icon="diagram-project" href="../../docs/building-blocks/process/process-definition">
    Deep-dive into process configuration options
  </Card>

  <Card title="Integration Designer" icon="plug" href="../../docs/platform-deep-dive/integrations/integration-designer">
    Build more integration workflows for external systems
  </Card>

  <Card title="Task Management" icon="list-check" href="../../docs/platform-deep-dive/core-extensions/task-management/task-management-overview">
    Configure task routing and assignment rules
  </Card>

  <Card title="Mortgage advisor chatbot" icon="building-columns" href="./mortgage-advisor">
    Build a conversational AI app as a companion to this BPMN tutorial
  </Card>
</CardGroup>
