Individual KYC process

Core concepts

  • The account KYC process is built around submissions, which are essentially sessions with requirements and data you put into them.
  • After fulfilling all requirements, a submission may be closed and becomes uneditable.
  • After the submission has been reviewed, the applicant becomes active.
  • If new data is required, a new submission may be created for an applicant.

In this guide, we'll cover the KYC process for an individual account.

Create an account

You want to call the create account endpoint first if you have not done it yet.

curl --request POST \
     --url https://api.due.network/v1/accounts \
     --header 'accept: application/json' \
     --header 'authorization: Bearer <API_KEY>' \
     --header 'content-type: application/json' \
     --data '
{
  "type": "individual",
  "name": "John Snow",
  "email": "[email protected]",
  "country": "GB",
  "category": "employed"
}
'

Grab the id from the response - this is the ACCOUNT_ID.

{
    "id": "acct_DlsiOQ7RxG1hgJVL",
    // ...
}

Initiate the KYC process

curl --request POST \
     --url https://api.due.network/v1/kyc \
     --header 'Due-Account-Id: <ACCOUNT_ID>' \
     --header 'accept: application/json' \
     --header 'authorization: Bearer <API_KEY>'

This will return a submission entity containing all the requirements you need to fulfill.

{
    "id": "ksub_i3DCwMJ3ztI3TyXR",
    "ownerId": "acct_Dt9Ar4sm8NAu1uMu",
    "applicantId": "ka_TSVn8uuUIl0ur5c5",
    "status": "open",
    "info": {},
    "requirements": [
        {
            "info": [
                {
                    "key": "firstName",
                    "type": "string"
                },
                {
                    "key": "lastName",
                    "type": "string"
                },
                {
                    "key": "dob",
                    "type": "date"
                },
                {
                    "key": "phone",
                    "type": "e164"
                },
                {
                    "key": "nationality",
                    "type": "iso3166_1_alpha2"
                }
            ],
            "kind": "static"
        },
        {
            "documents": [
                {
                    "category": "ID",
                    "oneOf": [
                        {
                            "type": "ID_CARD",
                            "variants": [
                                "FRONT_SIDE",
                                "BACK_SIDE"
                            ]
                        },
                        {
                            "type": "RESIDENCE_PERMIT",
                            "variants": [
                                "FRONT_SIDE",
                                "BACK_SIDE"
                            ]
                        },
                        {
                            "type": "PASSPORT"
                        }
                    ]
                },
                {
                    "category": "SELFIE",
                    "oneOf": [
                        {
                            "type": "SELFIE"
                        }
                    ]
                }
            ],
            "kind": "static"
        },
        {
            "documents": [
                {
                    "category": "PROOF_OF_ADDRESS",
                    "oneOf": [
                        {
                            "type": "UTILITY_BILL"
                        }
                    ]
                }
            ],
            "kind": "static"
        },
                        {
                            "templateId": "kycQuestionnaireV1",
                            "questions": [
                                {
                                    "id": "purpose",
                                    "title": "How do you plan to use Due?",
                                    "requiredWhen": null,
                                    "schema": {
                                        "min": 1,
                                        "options": [
                                            {
                                                "id": "trade",
                                                "label": "Buy / sell crypto-assets"
                                            },
                                            {
                                                "id": "payroll",
                                                "label": "Receive salary / payroll"
                                            },
                                            {
                                                "id": "invest",
                                                "label": "Capital / investments"
                                            },
                                            {
                                                "id": "remit",
                                                "label": "Send money to family / friends"
                                            },
                                            {
                                                "id": "spend",
                                                "label": "Everyday spending / purchases"
                                            },
                                            {
                                                "id": "business",
                                                "label": "Business operations / payments"
                                            }
                                        ],
                                        "type": "multiple_choice"
                                    }
                                },
                                {
                                    "id": "volumes",
                                    "title": "How much do you plan to transact on Due per month?",
                                    "requiredWhen": null,
                                    "schema": {
                                        "options": [
                                            {
                                                "id": "min",
                                                "label": "Less than $1,000"
                                            },
                                            {
                                                "id": "10k",
                                                "label": "$1,000 ~ 10,000"
                                            },
                                            {
                                                "id": "50k",
                                                "label": "$10,000 ~ 50,000"
                                            },
                                            {
                                                "id": "max",
                                                "label": "$50,000 or more"
                                            }
                                        ],
                                        "type": "choice"
                                    }
                                },
                                {
                                    "id": "occupation",
                                    "title": "What is your occupation?",
                                    "requiredWhen": null,
                                    "schema": {
                                        "options": [... ],
                                        "type": "choice"
                                    }
                                },
                                {
                                    "id": "sow_other",
                                    "title": "If 'Other', please specify.",
                                    "requiredWhen": {
                                        "id": "sow",
                                        "value": "other"
                                    },
                                    "schema": {
                                        "type": "text"
                                    }
                                }
                            ]
                        }
                    ]
                }
            ]
        }
    ],
    "agreements": [],
    "documents": [],
    "questionnaires": [],
    "presetCode": "individual_nongeo",
    "source": "api"
}

The id field would be the SUBMISSION_ID.

Understanding requirements

Want a visual overview? See the Individual KYC requirements diagram for the current verification flow without diving into the schema structure.


Note: The tax residence country used to determine applicable requirements is derived from the country field provided when creating the account.

The requirements field contains an array of requirement objects. The structure is uniform across all requirement types, allowing for flexible and composable verification flows.

Note: Examples in this documentation are illustrative and may not reflect actual requirements. Always refer to the requirements returned by the API for your specific account.

Top-level structure

The requirements array uses AND logic — every item in the array must be satisfied for the submission to be complete.

Requirement kinds

Each requirement object has a kind field indicating its type:

KindDescription
staticAlways required. Contains info, documents, and/or questionnaires fields.
conditionalOnly required when a condition is met. Contains if and then fields.
oneOfExactly one of the options must be satisfied. Contains a oneOf array.

Static requirements

Static requirements are always required regardless of any conditions.

{
  "kind": "static",
  "info": [
    {
      "key": "firstName",
      "type": "string"
    },
    {
      "key": "lastName",
      "type": "string"
    }
  ],
  "documents": [
    {
      "category": "ID",
      "oneOf": [
        { "type": "PASSPORT" },
        { "type": "ID_CARD", "variants": ["FRONT_SIDE", "BACK_SIDE"] }
      ]
    }
  ],
  "questionnaires": [
    {
      "templateId": "kycQuestionnaireV1",
      "questions": [...]
    }
  ]
}

Conditional requirements

Conditional requirements are only required when the if condition is satisfied. The if field contains an array of requirements (using AND logic) that act as the condition. The then field contains an array of requirements to fulfill when the condition is met.

{
  "kind": "conditional",
  "if": [
    {
      "kind": "static",
      "documents": [
        {
          "category": "ID",
          "oneOf": [
            { "type": "PASSPORT" }
          ]
        }
      ]
    }
  ],
  "then": [
    {
      "kind": "static",
      "info": [
        {
          "key": "passportExpiryDate",
          "type": "date",
          "required": true
        }
      ]
    }
  ]
}

In this example, the passportExpiryDate field is only required if a PASSPORT is provided.

OneOf requirements

OneOf requirements allow the applicant to satisfy exactly one of the provided options.

{
  "kind": "oneOf",
  "oneOf": [
    {
      "kind": "static",
      "info": [
        {
          "key": "address",
          "type": "object",
          "fields": [
            { "key": "country", "type": "iso3166_1_alpha2" },
            { "key": "postCode", "type": "string" },
            { "key": "town", "type": "string" },
            { "key": "street", "type": "string" }
          ]
        }
      ]
    },
    {
      "kind": "static",
      "documents": [
        {
          "category": "PROOF_OF_ADDRESS",
          "oneOf": [
            { "type": "UTILITY_BILL" },
            { "type": "OTHER" }
          ]
        }
      ]
    }
  ]
}

In this example, the applicant can either provide a structured address or upload a proof of address document.

Nesting

Requirements can be nested. For example, a conditional requirement's then clause can contain oneOf requirements, and vice versa. This allows for complex verification flows while maintaining a uniform structure.

Document structure

The documents array describes required documents grouped by category. Each category contains a oneOf array listing acceptable document types.

{
  "category": "ID",
  "oneOf": [
    { "type": "PASSPORT" },
    { "type": "ID_CARD", "variants": ["FRONT_SIDE", "BACK_SIDE"] },
    { "type": "RESIDENCE_PERMIT", "variants": ["FRONT_SIDE", "BACK_SIDE"] }
  ]
}

The variants field indicates which parts of a document need to be uploaded. If no variants are specified, a single upload is expected.

Provide applicant information

From the requirements, identify all info fields across static and applicable conditional requirements:

curl --request POST \
     --url https://api.due.network/v1/kyc/submissions/<SUBMISSION_ID>/info \
     --header 'Due-Account-Id: <ACCOUNT_ID>' \
     --header 'accept: application/json' \
     --header 'authorization: Bearer <API_KEY>' \
     --header 'content-type: application/json' \
     --data '
{
  "firstName": "John",
  "lastName": "Snow",
  "dob": "1980-01-01",
  "phone": "+440000000000",
  "taxResidenceCountry": "GB"
}
'

Add verification documents

Step 1: Request document upload token

To upload a document, request a token specifying the document type, issuing country, and filename.

curl --request POST \
     --url https://api.due.network/v1/kyc/submissions/<SUBMISSION_ID>/documents \
     --header 'Due-Account-Id: <ACCOUNT_ID>' \
     --header 'accept: application/json' \
     --header 'authorization: Bearer <API_KEY>' \
     --header 'content-type: application/json' \
     --data '
{
  "type": "PASSPORT",
  "issuingCountry": "GB",
  "filename": "passport.jpg"
}
'

For documents with variants, include the variant field:

{
  "type": "ID_CARD",
  "variant": "FRONT_SIDE",
  "issuingCountry": "GB",
  "filename": "id_card_front.jpg"
}

This returns a token for uploading:

{
    "token": "eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9..."
}

Step 2: Upload the document

curl --request POST \
     --url https://api.due.network/v1/kyc/submissions/documents/<TOKEN> \
     --header 'Due-Account-Id: <ACCOUNT_ID>' \
     --header 'accept: application/json' \
     --header 'authorization: Bearer <API_KEY>' \
     --header 'content-type: image/jpeg' \
     --data-binary '@path/to/passport.jpg'

Repeat for all required documents based on the applicable requirements.

Complete the submission

After all requirements are fulfilled, complete the submission:

curl --request POST \
     --url https://api.due.network/v1/kyc/submissions/<SUBMISSION_ID>/complete \
     --header 'Due-Account-Id: <ACCOUNT_ID>' \
     --header 'accept: application/json' \
     --header 'authorization: Bearer <API_KEY>'

After review and approval, a webhook is sent:

{
  "type": "bp.kyc.status_changed",
  "data": {
    "id": "<ACCOUNT_ID>",
    "kyc": {
      "status": "passed"
    }
  }
}

Handle KYC status webhooks

Subscribe to bp.kyc.status_changed events to keep your onboarding state in sync after the applicant completes the KYC flow. The webhook data object contains the account, including data.id and data.kyc.status.

Process each webhook idempotently:

  1. Verify the webhook signature before reading the payload. See Incoming Events for the signature verification flow.
  2. Use the webhook id to ignore duplicate deliveries.
  3. Read data.id as the ACCOUNT_ID.
  4. Read data.kyc.status and update the account state in your system.
  5. Fetch the latest account before enabling account actions that depend on KYC status.
{
  "id": "wh_evt__123",
  "type": "bp.kyc.status_changed",
  "data": {
    "id": "acct_98765ABCDE",
    "type": "individual",
    "name": "Demo Account",
    "email": "[email protected]",
    "kyc": {
      "status": "resubmission_required",
      "link": "https://..."
    }
  },
  "occurredAt": "2025-09-22T12:00:00Z",
  "attemptedAt": "2025-09-22T12:00:00Z"
}

Handle each KYC status explicitly:

StatusMeaningRecommended handling
pendingThe application is awaiting review.Keep the account in onboarding or review state. Do not ask the applicant to resubmit documents unless the status changes.
passedThe application was approved.Mark KYC as complete for the account. You can enable product flows that require KYC, subject to any other requirements such as Terms of Service acceptance.
resubmission_requiredMore information is required.Notify the applicant and send them back to the KYC flow using data.kyc.link when present. If the link has expired, generate a new Sumsub link with force: true.
failedVerification failed.Mark KYC as failed and block KYC-gated actions for the account. Do not treat additional document uploads as accepted unless a later webhook changes the status.
circle-info

Webhooks can be retried and may arrive more than once. Store the latest KYC status per account and make your webhook handler safe to run multiple times for the same event.

Sumsub link

Use the POST /v1/kyc/session endpoint to generate a Sumsub link that you can share with an applicant so they can complete their KYC information.

curl --request POST \
     --url https://api.due.network/v1/kyc/session \
     --header 'Due-Account-Id: <ACCOUNT_ID>' \
     --header 'accept: application/json' \
     --header 'authorization: Bearer <API_KEY>'

A successful response returns a link and access token:

{
  "link": "https://...",
  "accessToken": "..."
}

If the link has expired, generate a new link by setting force to true in the request body:

curl --request POST \
     --url https://api.due.network/v1/kyc/session \
     --header 'Due-Account-Id: <ACCOUNT_ID>' \
     --header 'accept: application/json' \
     --header 'authorization: Bearer <API_KEY>' \
     --header 'content-type: application/json' \
     --data '
{
  "force": true
}
'


Did this page help you?