> ## 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.

# Overview

> Verify your customers' identities through Fluz — either by embedding the Fluz widget or by submitting verifications directly through the API.

Fluz requires customers to be identity-verified (KYC) before they can perform certain transactions, and verification is what unlocks higher transaction limits. Until a customer is verified, parts of the platform — funding a wallet, withdrawing funds, and virtual card issuance — remain unavailable to them.

There are two ways to get a customer verified. The choice comes down to whether you want Fluz to own the verification experience, or whether you want to own it yourself.

<CardGroup cols={2}>
  <Card title="Verify by Widget" icon="app-window" href="/verify-customers-by-widget">
    Embed the Fluz widget. Verification is handled as a built-in step — Fluz collects everything from the customer, in Fluz-hosted UI. You collect and store nothing.
  </Card>

  <Card title="Verify by API" icon="code" href="#verifying-through-the-api">
    Submit verifications yourself. You control the experience end to end and choose which method to use for each customer.
  </Card>
</CardGroup>

## Verifying through the widget

If you have already embedded the Fluz widget, you may not need to build a verification flow at all. The widget includes verification as a gate: when a customer who is not yet verified enters it, the widget walks them through verification and then returns them to whatever they were doing.

This is the lowest-effort path, and the only one where identity data never touches your systems. See [Verify by Widget](/verify-customers-by-widget).

## Verifying through the API

If you are integrating directly against the API, you submit verifications yourself. There are three methods available today, and they differ in what you collect from the customer.

<CardGroup cols={2}>
  <Card title="KYC Autofill" icon="zap" href="/verify-customers-by-autofill">
    You collect nothing. Fluz resolves and verifies the customer's identity from data already on file with `verifyUserPrefillInformation`.
  </Card>

  <Card title="Pass us the SSN information" icon="id-card" href="/verify-customers-by-ssn">
    You collect the customer's legal name, address, date of birth, and SSN, then submit it with `verifyUserInformation` for an immediate decision.
  </Card>

  <Card title="Request an IDV URL" icon="camera" href="/verify-customers-by-documents">
    You request a verification link with `requestDocumentVerificationLink`. Fluz returns a hosted URL; your customer uploads their government ID and a selfie to Fluz directly.
  </Card>
</CardGroup>

### Choosing between the API methods

Most integrations escalate only when the lower-friction attempt does not succeed:

<Steps>
  <Step title="Try KYC Autofill first">
    It is a single synchronous call with no fields to collect, and it runs once per customer. Use it as your default first attempt.
  </Step>

  <Step title="Fall back to SSN verification">
    If Autofill declines, and you already hold — or can reasonably ask for — the customer's identity details, submit them directly for another immediate decision.
  </Step>

  <Step title="Fall back to an IDV URL">
    If SSN verification also declines, request a verification link and have the customer upload their ID and a selfie. This is the higher-assurance path and resolves asynchronously.
  </Step>
</Steps>

<Note>
  A customer only needs to pass once, and all methods share a single verification state. Once a customer reaches `APPROVED` — by any method, including through the widget — further attempts are rejected with an `ERROR` status.
</Note>

Each method has a recipe you can walk through right here, without leaving the page:

<AccordionGroup>
  <Accordion title="Recipe: Request KYC Autofill" icon="code">
    <Steps>
      <Step title="Prepare the verifyUserPrefillInformation mutation">
        There is no input to collect — the customer is identified entirely by the access token.

        ```javascript theme={null}
        import { GraphQLClient, gql } from 'graphql-request';

        const VERIFY_USER_PREFILL_INFORMATION = gql`
          mutation verifyUserPrefillInformation {
            verifyUserPrefillInformation {
              status
              message
            }
          }
        `;
        ```
      </Step>

      <Step title="Prepare the GraphQL client">
        Authenticate with a [user access token](/recipes/generate-user-access-token) generated for the customer being verified.

        ```javascript theme={null}
        const API_URL = 'https://transactional-graph.fluzapp.com/api/v1/graphql';

        const client = new GraphQLClient(API_URL, {
          headers: {
            Authorization: `Bearer <<USER_ACCESS_TOKEN>>`,
            'Content-Type': 'application/json',
          },
        });
        ```
      </Step>

      <Step title="Process the request">
        The decision comes back in the same response.

        ```javascript theme={null}
        const response = await client.request(VERIFY_USER_PREFILL_INFORMATION);

        console.log(response);
        ```

        ```json Response theme={null}
        {
          "data": {
            "verifyUserPrefillInformation": {
              "status": "APPROVED",
              "message": "User verification successful"
            }
          }
        }
        ```
      </Step>
    </Steps>

    Copy-and-run version: [Request KYC Autofill](/recipes/verify-user-autofill). Full field reference: [KYC Autofill](/verify-customers-by-autofill).
  </Accordion>

  <Accordion title="Recipe: Request a user KYC verification" icon="code">
    <Steps>
      <Step title="Prepare the verifyUserInformation mutation">
        Pass the user information to be verified.

        ```javascript theme={null}
        import { GraphQLClient, gql } from 'graphql-request';

        const VERIFY_USER_INFORMATION = gql`
          mutation verifyUserInformation($input: VerifyUserInformationInput!) {
            verifyUserInformation(input: $input) {
              status
              message
            }
          }
        `;
        ```
      </Step>

      <Step title="Prepare the GraphQL client">
        Authenticate with a [user access token](/recipes/generate-user-access-token) generated for the customer being verified.

        ```javascript theme={null}
        const API_URL = 'https://transactional-graph.fluzapp.com/api/v1/graphql';

        const client = new GraphQLClient(API_URL, {
          headers: {
            Authorization: `Bearer <<USER_ACCESS_TOKEN>>`,
            'Content-Type': 'application/json',
          },
        });
        ```
      </Step>

      <Step title="Process the request">
        The decision comes back in the same response.

        ```javascript theme={null}
        const response = await client.request(VERIFY_USER_INFORMATION, {
          input: {
            firstName: 'John',
            lastName: 'Smith',
            streetLine1: '123 Main St',
            streetLine2: '',
            city: 'Los Angeles',
            state: 'CA',
            postalCode: '91234',
            country: 'United States',
            dateOfBirth: '01/28/1975',
            ssnLast4: '1234',
          },
        });

        console.log(response);
        ```

        ```json Response theme={null}
        {
          "data": {
            "verifyUserInformation": {
              "status": "APPROVED",
              "message": "User verification successful"
            }
          }
        }
        ```
      </Step>
    </Steps>

    Copy-and-run version: [Request a User KYC Verification](/recipes/verify-user-kyc). Full field reference: [Verify by SSN](/verify-customers-by-ssn).
  </Accordion>

  <Accordion title="Recipe: Request a document verification link" icon="code">
    <Steps>
      <Step title="Prepare the requestDocumentVerificationLink mutation">
        The customer is identified by the access token, so the input only carries consent and prefill flags.

        ```javascript theme={null}
        import { GraphQLClient, gql } from 'graphql-request';

        const REQUEST_DOC_VERIFICATION = gql`
          mutation RequestDocumentVerificationLink($input: RequestDocumentVerificationLinkInput!) {
            requestDocumentVerificationLink(input: $input) {
              userId
              verificationType
              verificationId
              verificationUrl
              status
              message
            }
          }
        `;
        ```
      </Step>

      <Step title="Prepare the GraphQL client">
        Authenticate with a [user access token](/recipes/generate-user-access-token) generated for the customer being verified.

        ```javascript theme={null}
        const API_URL = 'https://transactional-graph.fluzapp.com/api/v1/graphql';

        const client = new GraphQLClient(API_URL, {
          headers: {
            Authorization: `Bearer <<USER_ACCESS_TOKEN>>`,
            'Content-Type': 'application/json',
          },
        });
        ```
      </Step>

      <Step title="Process the request">
        Deliver the returned `verificationUrl` to your customer, and store the `verificationId` to reconcile the webhook that carries the outcome.

        ```javascript theme={null}
        const response = await client.request(REQUEST_DOC_VERIFICATION, {
          input: {
            gaveConsent: true,
            prefillData: true,
          },
        });

        console.log(response);
        ```

        ```json Response theme={null}
        {
          "data": {
            "requestDocumentVerificationLink": {
              "userId": "eb910e93-5e39-4f53-99b9-0b033dd8e54b",
              "verificationType": "DOCUMENT_VERIFICATION",
              "verificationId": "idv_9MpDJC8aotDaxw",
              "verificationUrl": "https://verify.fluz.app/idv/idv_9MpDJC8aotDaxw?key=27f09ec042881c2c56945680c53108a4",
              "status": "OK",
              "message": "Verification link request successful"
            }
          }
        }
        ```
      </Step>
    </Steps>

    Copy-and-run version: [Request a Document Verification Link](/recipes/request-document-verification-link). Full field reference: [Verify by Documents](/verify-customers-by-documents).
  </Accordion>
</AccordionGroup>

## Required scope

Every verification method requires the **`VERIFY_KYC`** scope, which allows your application to request identity verification on a customer's behalf.

<Warning>
  `VERIFY_KYC` is not self-serve. It has to be enabled on your application by Fluz — reach out to your Fluz contact to have it turned on before you begin building.
</Warning>

There are two layers of permission, and you need both:

| Layer                  | What it is                                                                          | How it is granted                                                                    |
| :--------------------- | :---------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------- |
| Application scope      | Your app is permitted to request verifications at all.                              | Enabled on your application by Fluz.                                                 |
| Customer authorization | An individual customer permits *your* app to submit a verification on their behalf. | Granted by the customer during the OAuth flow. Widget apps request it automatically. |

The scope must also be included when you [generate the user access token](/recipes/generate-user-access-token) you use for the call. See [Application Scopes](/fluz-dashboard/application-scopes) for the full catalog.

## Set up a webhook

Register a webhook endpoint before you send your first verification. Verification is not always resolved inside the API response — document verification in particular completes whenever the customer chooses to finish it, which may be minutes or days after you request the link. Widget verifications complete outside your application entirely. Webhooks are how you learn the outcome.

<Warning>
  Do not poll for verification status, and do not treat the absence of a webhook as a decline. Register an endpoint and react to the event.
</Warning>

See [Webhooks](/fluz-dashboard/webhooks) for endpoint requirements, signature verification, retry behavior, and payload formats. In short:

1. Register an HTTPS endpoint on your application in the Developer Portal.
2. Subscribe to the identity verification events.
3. Verify the `X-HMAC-Signature` header against the raw request body on every delivery.
4. Deduplicate on `X-Event-ID` and respond `2xx` within 30 seconds.

Your application — and, for OAuth and widget apps, the individual customer's grant — must hold `VERIFY_KYC` to receive verification events.

## Verification statuses

Every method resolves to one of the following statuses.

| Status      | Message                                  | Description                                                                |
| :---------- | :--------------------------------------- | :------------------------------------------------------------------------- |
| `APPROVED`  | User verification successful             | The customer is KYC verified.                                              |
| `DECLINED`  | Verification declined                    | The customer is not KYC verified. You may escalate to another method.      |
| `DUPLICATE` | Duplicate user verification              | The customer is verified, but their SSN matches an existing Fluz customer. |
| `ERROR`     | Error encountered with user verification | The request could not be processed. See [Attempt limits](#attempt-limits). |

<Note>
  `DUPLICATE` is determined by **SSN only**, not by address — customers legitimately have multiple addresses over time. Fluz does not disclose which other customer matched.
</Note>

An `ERROR` status is returned when the customer is already verified, when the verification attempt limit has been reached, or when the document verification limit has been reached.

## Attempt limits

Verification attempts are capped to prevent customers from guessing their way to an approval.

* A customer may attempt SSN verification up to **3 times** per user ID through the API.
* Document verification requests are capped separately.
* **KYC Autofill runs once per customer**, whether it approves or declines.
* Once a customer is `APPROVED`, no further attempts are accepted.

## Address formatting

Every method that accepts an address expects the customer's **residential** address in the structured fields, with a consistent city, state, and postal code. A malformed or mismatched address is a common cause of an otherwise valid customer being `DECLINED`.

<Warning>
  PO boxes are not accepted as a legal address and will cause verification to fail. Submit a physical street address.
</Warning>

International addresses are accepted. See [Address Formatting Requirements](/concepts/address-formatting-requirements) for the full rules.

## Testing

Use the staging environment and the published test identities to exercise each path, including deliberate declines, before going live. See [Testing KYC Flows](/test-kyc-flows).
