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

# Verify by Documents

> Request a hosted verification link, deliver it to your customer, and let them upload their government ID and a selfie.

Requesting an IDV URL is the higher-assurance API method, and the fallback when [passing us the SSN information](/verify-customers-by-ssn) does not produce an approval. Rather than collecting identity documents yourself, you request a hosted verification link from Fluz and hand it to your customer. Fluz collects and screens the documents directly.

<Info>
  **Prerequisites**

  * The `VERIFY_KYC` scope enabled on your application by Fluz. See [Required scope](/user-kyc-verification#required-scope).
  * A [user access token](/recipes/generate-user-access-token) generated for the customer being verified, including `VERIFY_KYC` in its scopes.
  * A registered webhook endpoint. **This is required for document verification** — the outcome is not available in the API response. See [Verify Customers](/user-kyc-verification#set-up-a-webhook).
</Info>

## How it works

<Steps>
  <Step title="You identify the customer and request a link">
    Call `requestDocumentVerificationLink` using a user access token generated for that customer. The token is what tells Fluz which customer the verification belongs to.
  </Step>

  <Step title="Fluz returns a verification link">
    The response contains a `verificationUrl` and a `verificationId`. Store the `verificationId` — it is how you reconcile the eventual webhook with this request.
  </Step>

  <Step title="You deliver the link to your customer">
    Send it however suits your product: email, SMS, push notification, or an in-app redirect. The customer can complete verification at any time.
  </Step>

  <Step title="Your customer uploads their documents">
    On the hosted page, the customer captures the front and back of a government-issued ID (driver's license, passport, state ID, or military ID) and takes a selfie for a biometric match.
  </Step>

  <Step title="Fluz notifies you of the outcome">
    When the customer finishes, Fluz screens the submission and sends an `APPROVED` or `DECLINED` result to your webhook endpoint.
  </Step>
</Steps>

<Warning>
  The verification link is unique to one customer and one verification attempt. Never reuse a link across customers, log it in a shared system, or expose it anywhere the intended customer is not the only reader — it grants access to an identity submission session.
</Warning>

## Request

| Field          | Type    | Required | Description                                                                                                                                       |
| :------------- | :------ | :------- | :------------------------------------------------------------------------------------------------------------------------------------------------ |
| `gaveConsent`  | Boolean | Yes      | Whether the customer consented to identity verification. Must be `true`, or the request is rejected.                                              |
| `prefillData`  | Boolean | Yes      | Whether to prefill the verification form with the identity information Fluz already holds. When `true`, you do not need to send the fields below. |
| `firstName`    | String  | No       | The customer's legal first name.                                                                                                                  |
| `lastName`     | String  | No       | The customer's legal last name.                                                                                                                   |
| `streetLine1`  | String  | No       | Residential street address.                                                                                                                       |
| `streetLine2`  | String  | No       | Apartment, suite, or unit.                                                                                                                        |
| `city`         | String  | No       | City.                                                                                                                                             |
| `region`       | String  | No       | State or region.                                                                                                                                  |
| `postalCode`   | String  | No       | ZIP or postal code.                                                                                                                               |
| `country`      | String  | No       | Country, in ISO 3166-1 alpha-2 format.                                                                                                            |
| `dateOfBirth`  | String  | No       | Date of birth, formatted `YYYY-MM-DD`.                                                                                                            |
| `emailAddress` | String  | No       | The customer's email address.                                                                                                                     |
| `phoneNumber`  | String  | No       | The customer's phone number in E.164 format.                                                                                                      |

<Note>
  You must capture and record the customer's consent before setting `gaveConsent: true`. Anything you pass is used to prefill the form — the customer can review and correct it before submitting, so treat these fields as a convenience, not as the values that will be verified.
</Note>

<Warning>
  Note the field naming difference from [SSN verification](/verify-customers-by-ssn): this mutation uses `region` where the other uses `state`, and expects `dateOfBirth` as `YYYY-MM-DD` rather than `MM/DD/YYYY`.
</Warning>

## Example

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

const API_URL = 'https://transactional-graph.fluzapp.com/api/v1/graphql';

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

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

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"
    }
  }
}
```

<Note>
  The `status` in this response (`OK`) confirms only that the **link was issued** — it is not the verification decision. The decision arrives later, by webhook.
</Note>

<Card title="Open the recipe" icon="code" horizontal href="/recipes/request-document-verification-link">
  A copy-and-run version of this example, ready to adapt to your integration.
</Card>

## Response fields

| Field              | Type   | Description                                                                  |
| :----------------- | :----- | :--------------------------------------------------------------------------- |
| `userId`           | UUID   | The Fluz customer the verification belongs to.                               |
| `verificationType` | String | Always `DOCUMENT_VERIFICATION`.                                              |
| `verificationId`   | String | Identifier for this verification attempt. Store it to reconcile the webhook. |
| `verificationUrl`  | String | The hosted link to deliver to your customer.                                 |
| `status`           | String | `OK` when the link was issued successfully.                                  |
| `message`          | String | Human-readable detail.                                                       |

## What the customer sees

The hosted flow prefills known information for the customer to review, then asks them to:

1. Capture the front and back of a supported government-issued photo ID.
2. Take a selfie, which is matched biometrically against the ID photo.

Document authenticity, tampering, and the biometric match are all screened before a decision is returned.

## Receiving the outcome

Because the customer may complete verification long after you issue the link, the outcome arrives at your webhook endpoint. Match the incoming event to your stored `verificationId`, then act on the result:

* **`APPROVED`** — the customer is verified. Unlock the relevant functionality.
* **`DECLINED`** — the customer is not verified. Document verification is the final step in the escalation path; a decline here generally requires manual review rather than another automated attempt.

Document verification requests are capped per customer. Once the limit is reached, further requests return `ERROR` with `Exceeded document verification limit`.

## Testing

In staging you can complete the full document flow using sample driver's licenses and test identities. Document authenticity and other security features are not enforced in staging, and you may reuse the front image for the back capture. See [Testing KYC Flows](/test-kyc-flows).
