Complete Know Your Customer (KYC) Verification

Overview

The Know Your Customer (KYC) API enables you to programmatically verify the information of users onboarding to your platform and ensure regulatory compliance. KYC verification of your users is required before they can use BitGo products on your platform.

Prerequisites

KYC Lifecycle

The KYC verification process consists of multiple stages involving data collection, review steps, and status transitions. The diagram below provides a high-level overview of the complete lifecycle, illustrating the various states an identity can progress through from initial submission to final decision.

Crypto-as-a-Service KYC Crypto-as-a-Service KYC

1. Submit KYC

Submit basic KYC information for your user. If necessary, uploading supporting documents occurs in the next step.

Endpoint: Create Identity

export ACCESS_TOKEN="<SERVICE_USER_ACCESS_TOKEN>"
export ORGANIZATION_ID="<YOUR_ORGANIZATION_ID>"

curl -X POST \
  https://app.bitgo-test.com/api/evs/v1/identity \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -d '{
    "enterpriseId": "59cd72485007a239fb00282ed480da1f", # newly created child enterprise for end user
    "userId": "67d28e3dea178ede86f2962e959865a0", # primary contact for the end user of the child enterprise
    "organizationId": "'"$ORGANIZATION_ID"'",
    "nameFirst": "John",
    "nameLast": "Doe",
    "nameMiddle": "F",
    "debugStatus": "passed",
    "debugFailureReason": "",
    "phoneNumber": "+442083661173",
    "birthdate": "2025-03-13T00:00:00Z",
    "occupation": "Other - Default",
    "countryOfCitizenship": "USA",
    "countryOfResidence": "USA",
    "govIdCountryOfIssuance": "USA",
    "identificationNumber": "494-14-2205",
    "politicallyExposedPerson": false,
    "addressStreet1": "Street 1",
    "addressStreet2": "Unit 100",
    "addressCity": "San Jose",
    "addressSubdivision": "CA",
    "country": "USA",
    "transactionType": "institutionalIndividual",
    "addressPostalCode": "33604"
}'
import superagent from 'superagent';
const ACCESS_TOKEN = '<SERVICE_USER_ACCESS_TOKEN>';
const organizationId = '<YOUR_ORGANIZATION_ID>';

const apiUrl = 'https://app.bitgo-test.com/api/evs/v1/identity/';

const params = {
 birthdate: new Date().toISOString(),
 country: 'USA',
 countryOfIncorporation: 'USA',
 countryOfCitizenship: 'USA',
 countryOfResidence: 'USA',
 govIdCountryOfIssuance: 'USA',
 identificationNumber: '494-14-2205',
 addressStreet1: 'Street 1',
 addressCity: 'San Jose',
 addressSubdivision: 'CA',
 addressPostalCode: '33604',
 dateOfIncorporation: '2025-03-13T00:00:00Z',
 debugFailureReason: '',
 debugStatus: 'passed',
 enterpriseId: '59cd72485007a239fb00282ed480da1f', // newly created child enterprise for end user
 userId: '67d28e3dea178ede86f2962e959865a0', // primary contact for the end user of the child enterprise
 nameFirst: 'John',
 nameMiddle: 'F',
 nameLast: 'Doe',
 phoneNumber: '+442083661173',
 occupation: 'Other - Default',
 politicallyExposedPerson: false,
 organizationId,
 transactionType: 'institutionalIndividual',
};

response = await superagent
 .post(apiUrl)
 .set('Authorization', `Bearer ${ACCESS_TOKEN}`)
 .set('Accept', 'application/json')
 .send(params);

Note: You can simulate KYC verification end-to-end flows in the test environment by following the Simulating KYC Flows guide.

Step Result

You create an identity for KYC purposes and BitGo returns the id and status of the created identity. The status is initiating which confirms that BitGo has received the end user's KYC information and is reviewing it.

{
  "id": "993ec85b-ad23-4259-bde5-703a071a0783",
  "status": "initiating"
}

2. Check KYC status

Check the KYC status of your end user to determine next steps.

Endpoint: Get Identity

export ACCESS_TOKEN="<SERVICE_USER_ACCESS_TOKEN>"
export ORGANIZATION_ID="<YOUR_ORGANIZATION_ID>"
export ENTERPRISE_ID="<CHILD_ENTERPRISE_ID>"
export IDENTITY_ID="<END_USER_IDENTITY_ID>"

curl -X GET \
  "https://app.bitgo-test.com/api/evs/v1/identity?organizationId=$ORGANIZATION_ID&enterpriseId=$ENTERPRISE_ID&identityId=$IDENTITY_ID" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $ACCESS_TOKEN"
import superagent from 'superagent';
const ACCESS_TOKEN = '<SERVICE_USER_ACCESS_TOKEN>';
const organizationId = '<YOUR_ORGANIZATION_ID>';
const enterpriseId = '<CHILD_ENTERPRISE_ID>';
const identityId = '<END_USER_IDENTITY_ID>';

const apiUrl = `https://app.bitgo-test.com/api/evs/v1/identity?organizationId=${organizationId}&enterpriseId=${enterpriseId}&identityId=${identityId}`;

const response = await superagent
  .get(apiUrl)
  .set('Authorization', `Bearer ${ACCESS_TOKEN}`);

Note: Use both the organization and enterprise IDs to successfully fetch your end user KYC identity.

Step Result

One of the following KYC statuses is returned:

  • initiating - BitGo confirms receipt of the end user's KYC information.
  • evaluating submission - The KYC information is in initial review by automated checks.
  • awaiting document upload - BitGo requires supporting identity verification documents to continue with the review process.
  • in review - BitGo manually reviews the KYC information.
  • awaiting signature - BitGo completes review of the KYC information and now requires you to confirm verification with a signature.
  • incomplete verifications - BitGo can't complete verification using the provided documents. You must re-upload identity verification documents for the end user. BitGo recommends uploading a different kind of identity verification document.
  • approved - BitGo approves your end user's KYC verification and marks it as complete.
{
    "identities": [
        {
            "id": "5436ff2e-1129-49f1-8334-1510955ffe00",
            "status": "awaiting document upload",
            "organizationId": "68928b9066417fe09212fe9965c88552",
            "enterpriseId": "690a234346b3252214cdaf72070c549e",
            "userId": "690a234046b3252214cdadfe8600df80",
            "createdAt": "2025-11-04T16:05:15.359Z",
            "updatedAt": "2025-11-04T16:05:17.975Z",
            "signaturesSubmitted": [],
            "signaturesRequired": []
        }
    ]
}

3. Submit Documents

If the end user's KYC status is awaiting document upload, you must submit their identity verification documents. Document submission requirements also vary by citizenship:

  • United States citizens: No documentation required unless BitGo explicitly requests it.
  • Non-U.S. citizens: Identity verification documents are required.

BitGo recommends submitting all necessary documents for an end user in a single API call. Document files can't exceed 15MB and must be at least 200x200px.

Note: BitGo limits the number of times you can submit documents for each end user. Ensure document quality before submission to avoid exhausting retry attempts.

3.1 Add Documents

Endpoint: Create Identity Document

export IDENTITY_ID="<END_USER_IDENTITY_ID>"
export ACCESS_TOKEN="<SERVICE_USER_ACCESS_TOKEN>"
export ID_CLASS="<DOCUMENT_ID_CLASS>"

curl -X POST \
  "https://app.bitgo-test.com/api/evs/v1/identity/$IDENTITY_ID/document" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -F "selectedIdClass=$ID_CLASS" \
  -F "frontPhoto=@<PATH_TO_FRONT_PHOTO>" \
  -F "backPhoto=@<PATH_TO_BACK_PHOTO>" \
  -F "proofOfResidency=@<PATH_TO_PROOF_OF_RESIDENCY>"
import superagent from 'superagent';
import FormData from 'form-data';
import fs from 'fs';

const ACCESS_TOKEN = '<SERVICE_USER_ACCESS_TOKEN>';
const identityId = '<END_USER_IDENTITY_ID>';
const idClass = '<DOCUMENT_ID_CLASS>';

const apiUrl = `https://app.bitgo-test.com/api/evs/v1/identity/${identityId}/document`;

const formData = new FormData();
formData.append('selectedIdClass', idClass);
formData.append('frontPhoto', fs.createReadStream('<PATH_TO_FRONT_PHOTO>'));
formData.append('backPhoto', fs.createReadStream('<PATH_TO_BACK_PHOTO>'));
formData.append('proofOfResidency', fs.createReadStream('<PATH_TO_PROOF_OF_RESIDENCY>'));

const response = await superagent
  .post(apiUrl)
  .set('Authorization', `Bearer ${ACCESS_TOKEN}`)
  .send(formData);

Step Result

BitGo returns the following:

  • The document ID - A unique ID for the document submission. Store this ID for use if you need to update the document submission.
  • The document status - the initial status is pending which confirms that BitGo has received the document. Other possible statuses include:
    • processing - BitGo is validating the document
    • approved - The document has been successfully verified
    • rejected - The document failed verification
  • The list of files that you uploaded for the end user including details such as file upload status, document type, and size.
{
  "id": "02f3186c-8589-4lfa-a800-1e5cc169c726",
  "status": "pending",
  "selectedIdClass": "pp",
  "fileUploads": [
      {
          "fileName": "passport-front.jpeg",
          "fileSize": 11275,
          "uploadStatus": "pending",
          "documentType": "frontPhoto"
      }
  ]
}

BitGo processes the documents with the following workflow:

  1. Initial receipt - The end user's KYC status updates to records uploaded.
  2. Automated validation - BitGo runs automated checks on the submitted documents.
  3. Then, one of the following occurs:
    • Validation passed or failed with no retry attempts remaining: - The end user's status updates to in review for manual review by the BitGo compliance team.
    • Validation failed (with retry attempts remaining): The end user's status updates to incomplete verifications, and you must re-upload improved documentation.

3.2 Get Documents

After submitting documents, you can retrieve the document details to check upload status and verify file information.

Endpoint: Get KYC Document

export ACCESS_TOKEN="<SERVICE_USER_ACCESS_TOKEN>"
export IDENTITY_ID="<END_USER_IDENTITY_ID>"
export DOCUMENT_ID="<DOCUMENT_ID>"

curl -X GET \
  "https://app.bitgo-test.com/api/evs/v1/identity/document?identityId=$IDENTITY_ID&documentId=$DOCUMENT_ID" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $ACCESS_TOKEN"
import superagent from 'superagent';

const ACCESS_TOKEN = '<SERVICE_USER_ACCESS_TOKEN>';
const identityId = '<END_USER_IDENTITY_ID>';
const documentId = '<DOCUMENT_ID>';

const apiUrl = `https://app.bitgo-test.com/api/evs/v1/identity/document?identityId=${identityId}&documentId=${documentId}`;

const response = await superagent
  .get(apiUrl)
  .set('Content-Type', 'application/json')
  .set('Authorization', `Bearer ${ACCESS_TOKEN}`);

Note: You can query by either identityId or documentId, or both. At least one parameter is required.

Step Result

{
  "documents": [
    {
      "id": "02f3186c-8589-4lfa-a800-1e5cc169c726",
      "status": "processing",
      "selectedIdClass": "pp",
      "fileUploads": [
        {
          "fileName": "passport-front.jpeg",
          "fileSize": 11275,
          "uploadStatus": "uploaded",
          "documentType": "frontPhoto"
        },
        {
          "fileName": "passport-back.jpeg",
          "fileSize": 10842,
          "uploadStatus": "uploaded",
          "documentType": "backPhoto"
        }
      ]
    }
  ]
}

3.3 Update Documents

You can only update documents when the KYC status is incomplete verifications. Verify the status before proceeding.

Endpoint: Update Identity Document

export IDENTITY_ID="<END_USER_IDENTITY_ID>"
export ACCESS_TOKEN="<SERVICE_USER_ACCESS_TOKEN>"
export ID_CLASS="<DOCUMENT_ID_CLASS>"
export DOCUMENT_ID="<DOCUMENT_ID>" # The ID of the document initially created

curl -X POST \
  "https://app.bitgo-test.com/api/evs/v1/identity/$IDENTITY_ID/document/$DOCUMENT_ID" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -F "selectedIdClass=$ID_CLASS" \
  -F "frontPhoto=@<PATH_TO_FRONT_PHOTO>" \
  -F "backPhoto=@<PATH_TO_BACK_PHOTO>" \
  -F "proofOfResidency=@<PATH_TO_PROOF_OF_RESIDENCY>"
import superagent from 'superagent';
import FormData from 'form-data';
import fs from 'fs';

const ACCESS_TOKEN = '<SERVICE_USER_ACCESS_TOKEN>';
const identityId = '<END_USER_IDENTITY_ID>';
const idClass = '<DOCUMENT_ID_CLASS>';
const documentId = '<DOCUMENT_ID>'; // The ID of the document initially created

const apiUrl = `https://app.bitgo-test.com/api/evs/v1/identity/${identityId}/document/${documentId}`;

const formData = new FormData();
formData.append('selectedIdClass', idClass);
formData.append('frontPhoto', fs.createReadStream('<PATH_TO_FRONT_PHOTO>'));
formData.append('backPhoto', fs.createReadStream('<PATH_TO_BACK_PHOTO>'));
formData.append('proofOfResidency', fs.createReadStream('<PATH_TO_PROOF_OF_RESIDENCY>'));

const response = await superagent
  .post(apiUrl)
  .set('Authorization', `Bearer ${ACCESS_TOKEN}`)
  .send(formData);

Step Result

{
  "id": "02f3186c-8589-4lfa-a800-1e5cc169c726",
  "status": "pending",
  "selectedIdClass": "pp",
  "fileUploads": [
      {
          "fileName": "passport-front.jpeg",
          "fileSize": 11275,
          "uploadStatus": "pending",
          "documentType": "frontPhoto"
      }
  ]
}

4. Sign Completed KYC

Once BitGo approves the KYC verification, your end user must sign to confirm. You can repeat step 2, Check KYC status, to verify when the end user is awaiting signature.

Endpoint: Submit KYC Signatures

export IDENTITY_ID="<END_USER_IDENTITY_ID>"
export ACCESS_TOKEN="<SERVICE_USER_ACCESS_TOKEN>"

curl -X POST \
  "https://app.bitgo-test.com/api/evs/v1/identity/$IDENTITY_ID/signatures" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -d '[
    {
      "contractSignerNameFull": "John Smith",
      "contractSignedDate": "2025-03-13 13:14:09.767480+00:00",
      "contractSignedIPAddress": "123.123.123.123",
      "contractVersion": "1.0",
      "contractType": "csa",
      "userAgreesToTerms": true
    },
    {
      "contractSignerNameFull": "John Smith",
      "contractSignedDate": "2025-03-13 13:14:09.767480+00:00",
      "contractSignedIPAddress": "123.123.123.123",
      "contractVersion": "1.0",
      "contractType": "mpa",
      "userAgreesToTerms": true
    },
    {
      "contractSignerNameFull": "Jerry Smith",
      "contractSignedDate": "2025-03-13 13:14:09.767480+00:00",
      "contractSignedIPAddress": "123.123.123.123",
      "contractVersion": "1.0",
      "contractType": "mic",
      "userAgreesToTerms": true
    }
  ]'
import superagent from 'superagent';
const ACCESS_TOKEN = '<SERVICE_USER_ACCESS_TOKEN>';
const IDENTITY_ID = '<END_USER_IDENTITY_ID>';

const apiUrl = `https://app.bitgo-test.com/api/evs/v1/identity/${IDENTITY_ID}/signatures`;

const params = [
 {
   "contractSignerNameFull": "John Smith",
   "contractSignedDate": "2025-03-13 13:14:09.767480+00:00",
   "contractSignedIPAddress": "123.123.123.123",
   "contractVersion": "1.0",
   "contractType": "csa",
   "userAgreesToTerms": true
 },
 {
   "contractSignerNameFull": "Jay Smith",
   "contractSignedDate": "2025-03-13 13:14:09.767480+00:00",
   "contractSignedIPAddress": "123.123.123.123",
   "contractVersion": "1.0",
   "contractType": "mpa",
   "userAgreesToTerms": true
 },
 {
   "contractSignerNameFull": "Jerry Smith",
   "contractSignedDate": "2025-03-13 13:14:09.767480+00:00",
   "contractSignedIPAddress": "123.123.123.123",
   "contractVersion": "1.0",
   "contractType": "mic",
   "userAgreesToTerms": true
 }
];

response = await superagent
 .post(apiUrl)
 .set('Authorization', `Bearer ${ACCESS_TOKEN}`)
 .set('Accept', 'application/json')
 .send(params);

Step Result

{
  "id": "cf5d766d-bef1-4874-8b02-54ded0e8333e",
  "status": "signature submitted",
  "organizationId": "6573806ef79cafdd1aff42fbffc2ab20",
  "enterpriseId": "67d28e40ea178ede86f296ac90032286",
  "userId": "67d28e3dea178ede86f2962e959865a0",
  "updatedAt": "2025-03-13T07:59:52.564Z",
  "createdAt": "2025-03-13T07:54:18.842Z",
  "errorDescription": "",
  "signaturesSubmitted": ["csa", "mpa", "mic"],
  "signaturesRequired": []
}

Simulating KYC Flows

You can simulate different KYC verification flows to test your integration without manual review from BitGo. The debugStatus field allows you to control the KYC verification flow during testing. It accepts failed and passed as valid values and defaults to passed.

You can also pass debugFailureReason to get more specific errors on the identity errorDescription, this does not impact the KYC flow and is supplementary to debugStatus. Some possible values with specific error message mappings are: expired, cannot_verify_id, portrait_not_found, portrait_not_clear, face_mismatch, etc.

You can set both debugStatus and debugFailureReason when creating an identity and you can only update these fields when the identity status is awaiting document upload or incomplete verifications using the Update Identity endpoint.

Note: The following test paths are only available in the BitGo test environment.

This path simulates the fastest approval scenario where document upload is not required.

Steps

  1. Create an identity for a non-PEP (politically exposed person) US citizen without the debugStatus parameter.
    • Recommendation: Avoid using New York (NY) as the state, as NY-based identities may sometimes require additional documentation.
  2. The identity status automatically progresses to awaiting signature, bypassing document upload.
  3. Submit the required signatures.

Expected Outcome

The identity status updates to approved without requiring document upload.

Next Steps

Create Go Accounts for your users.

See Also