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
- Get Started
- Set Up Organization
- Set Up Child Enterprise
- Create Organization Webhooks (optional)
- You can set up webhooks to monitor the KYC process in real-time. Create an
identityStatustype webhook to receive events whenever the identity status changes.
- You can set up webhooks to monitor the KYC process in real-time. Create an
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.
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
pendingwhich confirms that BitGo has received the document. Other possible statuses include:processing- BitGo is validating the documentapproved- The document has been successfully verifiedrejected- 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:
- Initial receipt - The end user's KYC status updates to
records uploaded. - Automated validation - BitGo runs automated checks on the submitted documents.
- Then, one of the following occurs:
- Validation passed or failed with no retry attempts remaining: - The end user's status updates to
in reviewfor 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.
- Validation passed or failed with no retry attempts remaining: - The end user's status updates to
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
- Create an identity for a non-PEP (politically exposed person) US citizen without the
debugStatusparameter.- Recommendation: Avoid using New York (NY) as the state, as NY-based identities may sometimes require additional documentation.
- The identity status automatically progresses to
awaiting signature, bypassing document upload. - 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
Updated 1 day ago