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

# Onboard an individual customer

> Onboard an individual to Bastion end-to-end: create an identity, submit KYC data, handle optional IDV checks, and open a transacting account.

## Goal

Onboard an individual customer to Bastion so they can hold digital assets and use conversions and transfers.

## Key entities

| Entity                  | Description                                                                                                                                         |
| ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Your platform**       | The product integrating Bastion's APIs                                                                                                              |
| **Individual customer** | A person your platform onboards and verifies                                                                                                        |
| **Identity**            | The record of an individual customer in Bastion, used for KYC and permissions                                                                       |
| **Account**             | A unique operational unit tied to an Identity. Each Account has its own wallet address per supported blockchain and is used to perform transactions |

***

## Flow overview

The steps needed to onboard and transact an individual user are:

1. Create identity
2. Submit KYC
3. Track status
   1. (Conditionally required) Identity verification check
4. Create account
5. Start transacting

### Simulation for sandbox testing

Refer to [sandbox testing and simulations](/guides/getting-started/sandbox-testing-and-simulations) for a guide on how to use simulation labels to simulate both the onboarding and IDV checks to facilitate testing.

***

## Prerequisites

* You have access to your dashboard
* You've created an API key
* You've registered a webhook

***

## Step 1 – create an individual identity

Create an identity in Bastion as soon as your customer signs up or when they first try to use financial features.

**Request**

<CodeGroup>
  ```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  POST /v2/identities
  Content-Type: application/json
  Authorization: Bearer YOUR_API_KEY
  ```
</CodeGroup>

<CodeGroup>
  ```json theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  {
    "label": "merchant-user-48291",
    "identity_type": "INDIVIDUAL"
  }
  ```
</CodeGroup>

**Response**

<CodeGroup>
  ```json theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  {
   "id": "01HZX8QK9VM5T9JY8ZQXQZQXQZ",
   "identity_type": "INDIVIDUAL",
   "account_ids": [],
    "label": "merchant-user-48291",
    "fiat_operations_enabled": false,
    "status": "PENDING_ONBOARDING",
    "sub_status": "CREATED"
  }
  ```
</CodeGroup>

<Warning>
  Store the returned `identity_id` alongside your internal customer ID for all future calls.
</Warning>

> **Tip:** Use `label` for a stable, non-PII identifier (e.g., your internal customer ID). Do not include names, emails, or phone numbers.

***

## Step 2 – submit KYC data

Submit KYC information for the identity. Your platform collects this data and sends it to Bastion.

**Request**

<CodeGroup>
  ```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  POST /v2/identities/{identity_id}/compliance
  Content-Type: application/json
  Authorization: Bearer YOUR_API_KEY
  ```
</CodeGroup>

<CodeGroup>
  ```json theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  {
    "kyc": {
      "first_name": "Jane",
      "middle_name": "Q",
      "last_name": "Doe",
      "date_of_birth": "1990-04-20",
      "ip_address": "203.0.113.10",
      "physical_address": {
        "street_line_1": "123 Market St",
        "street_line_2": "Suite 400",
        "city": "San Francisco",
        "postal_code": "94105",
        "country": "US",
        "region_code": "CA"
      },
      "tax_id_number": "123-45-6789",
      "phone_number": "+14155552671",
      "email_address": "[email protected]",
      "account_purpose": "TRADING",
      "source_of_funds": "EMPLOYMENT_SALARY",
      "occupation": "FINANCE_AND_INSURANCE",
      "nationality": "US",
      "country_of_birth": "US",
      "additional_id_number": "D1234567"
    }
  }
  ```
</CodeGroup>

KYC is processed asynchronously. Bastion will update status over time and emit webhook events, or you can poll as described below.

***

## Step 3 – track KYC status

Track KYC status via

* **`identity_status_update` webhooks (recommended) or**
* **polling** via [`GET /v2/identities/{identity_id}`](/v2/api-reference/identities/get-identity)

Typically, most KYC checks are approved within seconds once data is submitted.

### Option A: webhooks

Configure a webhook endpoint in the Bastion dashboard to react to KYC status changes in real time.

Event: `identity_status_update`

**Example event**

<CodeGroup>
  ```json theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  {
    "id": "01HZX8QK9VM5T9JY8ZQXQZQXQZ",
    "type": "identity_status_update",
    "data": {
      "identity": {
        "id": "3CPgtujGnpORK9EKaXu5U9OclxU",
        "label": "merchant-user-48291",
        "identity_type": "INDIVIDUAL",
        "status": "ACTIVE"
      },
      "affected_account_ids": [],
      "status_reason": null,
      "previous_status": "PENDING_ONBOARDING",
      "previous_sub_status": "COMPLIANCE_CHECKS_IN_PROGRESS"
    }
  }
  ```
</CodeGroup>

Use this event to:

* Mark the customer as approved or failed in your system
* Enable or block access to financial features

### Option B: polling

Poll the identity endpoint as a fallback.

<CodeGroup>
  ```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  GET /v2/identities/{identity_id}
  Authorization: Bearer YOUR_API_KEY
  ```
</CodeGroup>

**Response (approved)**

<CodeGroup>
  ```json theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  {
   "id": "01HZX8QK9VM5T9JY8ZQXQZQXQZ",
   "identity_type": "INDIVIDUAL",
   "account_ids": [],
    "label": "merchant-user-48291",
    "fiat_operations_enabled": false,
    "status": "ACTIVE",
    "sub_status": "IDENTITY_SUB_STATUS_UNSPECIFIED"
  }
  ```
</CodeGroup>

**Key fields**

| Field                           | Meaning                                                                                                                                        |
| ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| `status`                        | `ACTIVE`: Onboarding completed and identity is available to transact                                                                           |
| `sub_status`                    | Onboarding detail when status is `PENDING_ONBOARDING` (e.g. compliance running, IDV steps, RFI); otherwise not meaningful / often unspecified. |
| `allowed_to_transact_fungibles` | Whether the identity can use crypto transfers                                                                                                  |
| `fiat_operations_enabled`       | Whether on/off-ramp conversions are enabled                                                                                                    |

> Only proceed to account creation after `status = ACTIVE`.

***

## Step 3a – identity verification

For a subset of identities undergoing KYC, Bastion may require you to run an additional identity verification (IDV) process.

When required, Bastion emits the `identity_status_update` event.

<CodeGroup>
  ```json theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  {
    "id": "01HZX8QK9VM5T9JY8ZQXQZQXQZ",
    "type": "identity_status_update",
    "data": {
      "identity": {
        "id": "3CPgtujGnpORK9EKaXu5U9OclxU",
        "label": "merchant-user-48291",
        "identity_type": "INDIVIDUAL",
        "status": "PENDING_ONBOARDING",
        "sub_status": "IDV_REQUIRED"
      },
      "affected_account_ids": [],
      "status_reason": null,
      "previous_status": "PENDING_ONBOARDING",
      "previous_sub_status": null
    }
  }
  ```
</CodeGroup>

When IDV is required, create a new IDV by using the endpoint `/v2/identities/{identity_id}/id_verification`

**Request**

<CodeGroup>
  ```json theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  {
    "identity_id": "01HZX8QK9VM5T9JY8ZQXQZQXQZ",
    "redirect_url": "https://yourapp.example/idv/return",
    "reason": "ONBOARDING",
    "request_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7"
  }
  ```
</CodeGroup>

**Response**

<CodeGroup>
  ```json theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  {
    "id": "01HZXIDVCHK0000000001",
    "identity_id": "01HZX8QK9VM5T9JY8ZQXQZQXQZ",
    "verification_url": "https://idv.vendor.example/session/abc123",
    "reason": "ONBOARDING",
    "request_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
    "redirect_url": "https://yourapp.example/idv/return",
    "status": "IN_PROGRESS",
    "created_at": "2026-04-20T16:00:00Z",
    "updated_at": "2026-04-20T16:00:00Z"
  }
  ```
</CodeGroup>

Provide the returned `verification_url` to the onboarding end user to undergo IDV.

Continue polling (or listening to the webhook) for the identity `status` to turn `ACTIVE` as the signal for KYC completion.

* The source of truth for identity status is the identity's `status` field — `data.identity.status` on the `identity_status_update` webhook, or [GET identity](/v2/api-reference/identities/get-identity). Do not rely on the status of IDV checks

***

## Step 4 – create an account

Once `status` is `ACTIVE`, create an account linked to the identity.

Use a unique `request_id` (UUID) as an idempotency key. Reuse the same `request_id` on retries to avoid duplicate accounts.

**Request**

<CodeGroup>
  ```bash theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  POST /v2/accounts
  Content-Type: application/json
  Authorization: Bearer YOUR_API_KEY
  ```
</CodeGroup>

<CodeGroup>
  ```json theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  {
    "request_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
    "identity_id": "01HZX8QK9VM5T9JY8ZQXQZQXQZ",
    "type": "SEGREGATED"
  }
  ```

  **Response**

  ```json theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
  {
    "request_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
    "identity_type": "INDIVIDUAL",
    "addresses": [
      {
        "address": "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb",
        "chains": ["ETHEREUM_MAINNET", "BASE_MAINNET"]
      },
      {
        "address": "DYw8jCTfwHNRJhhmFcbXvVDTqWMEVFBX6ZKUmG5CNSKK",
        "chains": ["SOLANA_MAINNET"]
      }
    ],
    "identity_id": "01HZX8QK9VM5T9JY8ZQXQZQXQZ",
    "id": "01HZXBANKACCOUNTIDEXAMPLE0",
    "type": "SEGREGATED",
    "frozen": false,
    "identity_status": "ACTIVE",
    "identity_sub_status": "IDENTITY_SUB_STATUS_UNSPECIFIED",
    "account_status": "ACTIVE"
  }
  ```
</CodeGroup>

Capture the `account_id`. This account is used for:

* Checking balances
* On-ramp and off-ramp conversions
* Crypto transfers

***

## Step 5 – verify setup

Confirm the customer is fully ready:

1. **Check identity status** — [`GET /v2/identities/{identity_id}`](/v2/api-reference/identities/get-identity)
2. **Check account** — Verify `account_ids` includes the new account and `account_status= ACTIVE`
3. **Check balances** (optional) — [`GET /v2/accounts/{account_id}/balances`](/v2/api-reference/accounts/get-account-balances)

**Next actions:**

* Top up via on-ramp (USD → stablecoin)
* Send crypto via `/v2/crypto/transfers`
* Withdraw via off-ramp (stablecoin → USD)

***

## Implementation notes

| Recommendation              | Details                                                                                                                                                                            |
| --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Wait for KYC approval**   | Accounts cannot be created until `status = ACTIVE`. Always wait before enabling money movement. Most KYC checks are approved within seconds once data and documents are submitted. |
| **Mirror in your database** | Store `identity_id`, `account_id`, and status in your own system. Keep in sync via webhooks.                                                                                       |
| **Separate auth from KYC**  | Your login, session, and device security are separate from Bastion's KYC. Treat Bastion as the financial/compliance layer only.                                                    |


## Related topics

- [KYC and KYB requirements](/guides/compliance/kyc-requirements.md)
- [Identities and accounts](/guides/concepts/identities-and-accounts.md)
- [Bastion platform overview](/guides/getting-started/bastion-platform-overview.md)
- [MCP server for LLMs and AI coding tools](/guides/llms/mcp.md)
- [Product updates](/changelog.md)
