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

# Onboard individual users via the KYC Widget

> Collect KYC data from individual users using the Uphold KYC Widget, without building a custom verification interface.

The [KYC Widget](/widgets/kyc/introduction) is an embeddable UI component for collecting KYC data from individual users. Instead of building and maintaining a custom verification interface, you embed the Widget and it handles forms, document uploads, and process state on your behalf. It's fully customizable — themes, fonts, and brand colors — so it can match your app's look and feel. See the [KYC Widget SDK reference](/widgets/kyc/sdk-reference) for the full configuration reference.

The KYC Widget currently covers three processes: `identity`, `profile`, and `proof-of-address`. Any remaining required processes (e.g. `customerDueDiligence`, `enhancedDueDiligence`, `taxDetails`) must still be completed via the [REST API](/developer-guides/user-onboarding/individual/via-api) or the [KYC Connector](/developer-guides/user-onboarding/individual/via-kyc-connector).

## Prerequisites

* API client credentials with the scopes to create users and access the KYC Widget.
* The KYC Widget SDK is installed in your frontend. See [Installation and setup](/widgets/kyc/installation-and-setup).

## Walkthrough

```mermaid theme={null}
sequenceDiagram
  autonumber
  participant U as Client App
  participant B as Your Backend
  participant A as Core API
  participant K as KYC Widget
  participant W as Webhooks

  U->>B: User provides personal, address, and contact details
  B->>A: GET /core/terms-of-service?type=general&country={country}
  A-->>B: { termsOfService[] }
  B-->>U: Display Terms of Service
  U->>B: User accepts Terms of Service
  B->>A: POST /core/users (with ToS + X-Uphold-User-Ip)
  A-->>B: { user }
  B->>A: POST /widgets/kyc/sessions
  A-->>B: { session }
  B-->>U: { session }
  U->>K: Initialize Widget
  U->>K: User completes verification processes
  K-->>U: complete
  U->>U: Unmount Widget, show pending state
  A-->>W: core.identity.status-changed / core.proof-of-address.status-changed
  B->>A: GET /core/kyc (verify process statuses)
  B->>A: PATCH /core/kyc/* (complete remaining processes via API)
  B->>A: GET /core/capabilities
  A-->>B: { capabilities[] }
  B-->>U: User is ready to transact
```

## Retrieve terms of service

Before creating a user, retrieve the general Terms of Service applicable to their country of residence by calling the [List terms of service](/rest-apis/core-api/terms-of-service/list-terms-of-service) endpoint with `type=general` and the user's country code.

```http theme={null}
GET /core/terms-of-service?type=general&country={country}
```

Display the Terms of Service content to the user and record their acceptance.

## Create the user

Once the user has accepted the Terms of Service, call [Create user](/rest-apis/core-api/users/create-user) to register them on the platform.

<Tip>The `X-Uphold-User-Ip` [user context](/rest-apis/headers#user-context) header is **mandatory** when creating a user, as it records the user's IP address at the time of Terms of Service acceptance.</Tip>

```http theme={null}
POST /core/users
{
  "type": "individual",
  "email": "john.doe@example.com",
  "termsOfService": "general-gb-fca",
  "fullName": "John Doe",
  "birthdate": "1987-01-01",
  "primaryCitizenship": "GB",
  "address": {
    "country": "GB",
    "subdivision": "GB-MAN",
    "city": "Manchester",
    "line1": "1 High Street",
    "postalCode": "M4 1AA"
  },
  "phone": {
    "number": "+447911123456",
    "country": "GB"
  }
}
```

A successful response returns the created user's information.

```json Response theme={null}
{
  "user": {
    "id": "cd21b26d-35d2-408a-9201-b8fdbef7a604",
    "type": "individual",
    "email": "john.doe@uphold.com",
    "fullName": "John Doe",
    "birthdate": "1987-01-01",
    "primaryCitizenship": "GB",
    "address": {
      "country": "GB",
      "subdivision": "GB-MAN",
      "city": "Manchester",
      "line1": "1 High Street",
      "postalCode": "M4 1AA"
    },
    "phone": {
      "number": "+447911123456",
      "country": "GB"
    },
    "createdAt": "2024-03-13T20:20:39Z",
    "updatedAt": "2024-03-13T20:20:39Z"
  }
}
```

You can optionally include custom [entity metadata](/rest-apis/entity-metadata) in the `metadata` field to store your own business data (e.g. external IDs or tracking parameters).

Subscribe to the `core.user.created` webhook to be notified asynchronously.

## Create a session

Call [Create kyc session](/rest-apis/widgets-api/kyc/create-session) on your backend with the `verify` flow. Specify `processes` to limit which verification steps the user sees, or omit it to run all available processes.

```http theme={null}
POST /widgets/kyc/sessions
{
  "flow": "verify",
  "processes": ["identity", "proof-of-address"]
}
```

```json theme={null}
{
  "session": {
    "flow": "verify",
    "data": { "processes": ["identity", "proof-of-address"] },
    "url": "https://kyc-widget.enterprise.uphold.com/...",
    "token": "GEbRxBN...edjnXbL"
  }
}
```

Pass `response.session` to your frontend (for example, as part of your page response or via your own API endpoint). The SDK takes that `session` object as a single argument.

## Set up the Widget

Wire up event handlers before calling `mountIframe`. The Widget does not unmount itself — you must call `widget.unmount()` from each terminal handler.

```javascript theme={null}
import { KycWidget } from '@uphold/enterprise-kyc-widget-web-sdk';

const widget = new KycWidget(session);

widget.on('ready', () => {
  // Hide your loading state
});

widget.on('complete', () => {
  widget.unmount();
  // Show a pending state — verification outcome arrives via webhook
});

widget.on('cancel', () => {
  widget.unmount();
  // Return the user to the previous screen
});

widget.on('error', (event) => {
  console.error('KYC error:', event.detail.error);
  widget.unmount();
});

widget.mountIframe(document.getElementById('kyc-container'));
```

See [Installation and setup](/widgets/kyc/installation-and-setup) for SDK installation, CSP configuration, and native app integration.

## Monitor outcomes

The `complete` event signals submission, not approval. Final outcomes arrive asynchronously on your backend via webhooks. Subscribe to the relevant events for the processes you requested:

* [`identity.status-changed`](/rest-apis/core-api/kyc/webhooks/identity-status-changed) — identity verification result
* [`proof-of-address.status-changed`](/rest-apis/core-api/kyc/webhooks/proof-of-address-status-changed) — proof of address result

<Warning>In Sandbox, proof-of-address submissions are automatically approved — no validation is performed on the documents you submit for this process through the Widget at the moment.</Warning>

You can also poll [Get KYC overview](/rest-apis/core-api/kyc/get-overview) (`GET /core/kyc`) at any time to check the current status of all KYC processes for a user.
