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

# KYC WebSDK

> The hosted verification flow: create the customer, launch them into Etherfuse, and the customer completes every step in the widget.

The default flow. You create the customer and launch them; the customer does everything else in the widget at `/idv`.

## The flow

<Steps>
  <Step title="Create the customer's organization">
    Create a `personal` organization with [`POST /ramp/organization`](/api-reference/organizations/create-child-org). **You generate the `id`.** This UUID is the customer's org id and the `customer_id` you use everywhere else.

    <CodeGroup>
      ```bash Sandbox theme={null}
      curl -X POST https://api.sand.etherfuse.com/ramp/organization \
        -H "Authorization: <api_key>" \
        -H "Content-Type: application/json" \
        -d '{
          "id": "<customer_uuid>",
          "displayName": "Ana Ejemplo",
          "accountType": "personal",
          "userInfo": {
            "email": "ana@example.com",
            "displayName": "Ana Ejemplo"
          }
        }'
      ```

      ```bash Production theme={null}
      curl -X POST https://api.etherfuse.com/ramp/organization \
        -H "Authorization: <api_key>" \
        -H "Content-Type: application/json" \
        -d '{
          "id": "<customer_uuid>",
          "displayName": "Ana Ejemplo",
          "accountType": "personal",
          "userInfo": {
            "email": "ana@example.com",
            "displayName": "Ana Ejemplo"
          }
        }'
      ```
    </CodeGroup>

    <Warning>
      **Send `userInfo.email`.** It is the address the customer confirms during verification, and the only place it comes from. A customer with no email on file cannot be verified.
    </Warning>
  </Step>

  <Step title="Launch the customer into /idv">
    An [Identity verification](/guides/user-launch-flows#identity-verification) launch, using [JWT User Authentication](/guides/jwt-authentication#sign-a-user-jwt).

    ```html theme={null}
    <form method="POST" action="https://sandbox.etherfuse.com/auth/launch">
      <input type="hidden" name="grant_type" value="urn:ietf:params:oauth:grant-type:jwt-bearer" />
      <input type="hidden" name="assertion" value="<verification_scoped_jwt>" />
      <input type="hidden" name="target" value="/idv" />
    </form>
    ```

    <Warning>
      **The JWT `sub` MUST equal the org `id` from step 1.** Any other `sub` registers a brand-new person instead, and the verification won't attach to your customer.
    </Warning>

    <Tip>
      Append **`&lang=es`** to the target (`/idv?lang=es`) for Spanish. Pass an optional `return_url` on the launch to send the customer back to your app when they finish.
    </Tip>
  </Step>

  <Step title="The customer completes the steps in the widget">
    Everything in the [Requirements List](/guides/onboarding#requirements-list), plus any [country-specific](/guides/onboarding#country-specific-requirements) requirement, in one authenticated session. The customer can leave and come back; progress is kept.
  </Step>

  <Step title="Watch for the result">
    The [`kyc_updated`](/guides/verifying-webhooks) webhook fires with `status: "approved"` and the customer's wallet becomes order-eligible. See [Tracking verification status](/guides/onboarding#tracking-verification-status).
  </Step>
</Steps>

## Error handling

Failures specific to launching. Verification outcomes (`denied`, needs-work) are in [Tracking verification status](/guides/onboarding#tracking-verification-status); a rejected customer is resubmitted by launching them again.

| What you see                                 | Meaning                                                                                                                  | What to do                                                                                        |
| -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------- |
| The launch never reaches `/idv`              | The JWT was rejected: wrong scope, expired, bad signature, or an unrecognized `target`.                                  | See [Authentication errors](/guides/authentication-errors).                                       |
| The customer lands in `/idv` as a new person | The JWT `sub` wasn't the org `id`.                                                                                       | Re-mint with `sub` = the org `id`. Don't reuse the old session.                                   |
| The liveness scan never starts in an iframe  | Camera permission. The bare `allow="camera"` grants only the iframe's own origin, but the scan runs from a nested frame. | Set `allow="camera *; microphone *"` on the iframe. A top-level tab or popup needs nothing extra. |

## Sandbox

The verification checks don't run, so any document images and any selfie will pass. The one exception is Mexico's constancia. See [Country-specific requirements](/guides/onboarding#country-specific-requirements) and [Test environment](/test-environment).

## When to reach for something else

<CardGroup cols={2}>
  <Card title="KYC API" icon="code" href="/guides/kyc-api">
    You already collect identity data and documents in your own UI.
  </Card>

  <Card title="Sumsub shared token" icon="share-nodes" href="/guides/kyc-sumsub-shared-token">
    You already verified the customer in your own Sumsub account.
  </Card>
</CardGroup>
