KYC Service
Subject to change
API reference for the Know Your Customer (KYC) verification process for users.
VERTEX ENGINE
Table of Contents
- Introduction to KYC
- KYC States and User Verification
- Document Upload to NATS Object Storage
- Submitting KYC Documents
- Querying User Information
1. Introduction to KYC
Know Your Customer (KYC) is a critical process for verifying the identity of users. The KYC process is integrated into the user aggregate and affects the user's verified status.
KYC Process Flow
- User Registration: Personal entity user created - KYC state is
Invalid - Document Upload: User uploads documents to NATS object storage
- Document Submission: User submits document references - KYC state transitions to
Pending - Review Process: Admin reviews submitted documents
- Approval/Rejection: Admin approves or rejects KYC - KYC state becomes
ApprovedorRejected - User Verification: Upon KYC approval, the user's
verifiedfield is set totrue
2. KYC States and User Verification
KYC States
The system tracks KYC progress through the following states:
- Invalid (0): Initial state, no KYC documents uploaded
- Pending (1): KYC documents uploaded, awaiting review
- Approved (2): KYC documents reviewed and approved
- Rejected (3): KYC documents reviewed and rejected
User Verification
The verified field on the user aggregate indicates whether a user has completed KYC verification:
verified: false- User has not completed KYC verificationverified: true- User has successfully completed KYC verification (KYC state isApproved)
The verifiedAt timestamp is set when KYC is approved.
3. Document Upload to NATS Object Storage
KYC documents should be uploaded to NATS object storage before submitting the verification request. The API is storage-agnostic and accepts document storage keys from any storage solution, but NATS object storage is recommended for consistency across the platform.
Document Naming Convention
Documents should follow these naming patterns for consistency:
Identity Documents:
kyc_id_front_<user_id>_<timestamp>- Front of ID documentkyc_id_back_<user_id>_<timestamp>- Back of ID document (if applicable)kyc_passport_<user_id>_<timestamp>- Passport (if using passport instead of ID)
Proof of Residence:
kyc_proof_of_residence_<user_id>_<timestamp>- Utility bill, bank statement, etc.
Command Line Upload Method
# Example: Upload multiple KYC documents
xargs -I {} nats obj put --name={} kyc_dropbox_<user_id> ./document.pdf << EOF
kyc_id_front_<user_id>_<timestamp>
kyc_id_back_<user_id>_<timestamp>
kyc_proof_of_residence_<user_id>_<timestamp>
EOFProgrammatic Access
import { MsgHdrsImpl } from "nats.ws";
// Connect to NATS and get object store
const userId = "550e8400-e29b-41d4-a716-446655440000";
const bucketName = `kyc_dropbox_${userId}`;
const os = await natsConn?.jetstream().views.os(bucketName);
// Upload KYC document with headers
async function uploadKYCDocument(os, userId, documentType, file) {
const timestamp = Math.floor(Date.now() / 1000);
let documentName = '';
switch(documentType) {
case 'ID_FRONT':
documentName = `kyc_id_front_${userId}_${timestamp}`;
break;
case 'ID_BACK':
documentName = `kyc_id_back_${userId}_${timestamp}`;
break;
case 'PASSPORT':
documentName = `kyc_passport_${userId}_${timestamp}`;
break;
case 'PROOF_OF_RESIDENCE':
documentName = `kyc_proof_of_residence_${userId}_${timestamp}`;
break;
default:
throw new Error('Invalid document type');
}
const headers = new MsgHdrsImpl();
headers.set("original_filename", file.name);
try {
const info = await os?.put(
{
name: documentName,
headers,
},
fileToReadableStream(file)
);
console.log(`Uploaded ${file.name} as ${documentName}`);
return documentName;
} catch (err) {
console.error("Upload error:", err);
throw err;
}
}
// Helper function to convert File to ReadableStream
function fileToReadableStream(file) {
return new ReadableStream({
async start(controller) {
const reader = file.stream().getReader();
try {
while (true) {
const { done, value } = await reader.read();
if (done) break;
controller.enqueue(value);
}
} finally {
controller.close();
}
}
});
}4. Submitting KYC Documents
After uploading documents to your storage solution, submit the document references to initiate the verification process.
Subject: svc.user.<partner_id>.upload_kyc_documents
Permissions Required: write
Request
{
"user_id": "550e8400-e29b-41d4-a716-446655440000", // Required - UUID of the user
"id_document": "string", // Optional - Storage key for ID document
"proof_of_residence": "string" // Optional - Storage key for proof of residence
}Important Notes
- At least one document must be provided (either
id_documentorproof_of_residence) - Both documents can be provided, but only one is required
- The storage keys should reference documents already uploaded to your storage solution
- Documents can be re-uploaded if KYC is in
PendingorRejectedstate
Example Request:
{
"user_id": "550e8400-e29b-41d4-a716-446655440000",
"id_document": "kyc_id_front_550e8400-e29b-41d4-a716-446655440000_1726220326",
"proof_of_residence": "kyc_proof_of_residence_550e8400-e29b-41d4-a716-446655440000_1726220276"
}Response
{
"success": true,
"message": "KYC documents uploaded and submitted for verification"
}Effects
- KYC state transitions from
InvalidtoPending kycSubmittedAttimestamp is set- Emits
KYCDocumentsUploadedevent - Documents are stored in the user aggregate's
kycDocumentsmap
Possible Errors
400 Bad Request - Missing Documents
{
"error": {
"code": 400,
"message": "missing documents"
}
}Returned when no documents are provided.
400 Bad Request - Already Verified
{
"error": {
"code": 400,
"message": "user already verified"
}
}Returned when the user is already verified or KYC is already approved.
5. Querying User Information
Get User
Retrieves a single user by user ID, including KYC status and verification details.
Subject: svc.user.<partner_id>.get
Permissions Required: read
Request
{
"user_id": "40ec785e-d915-49de-9053-4630042d8182" // Required - UUID of the user to retrieve
}Required Fields
user_id- UUID of the user to retrieve
Response
{
"user": {
"id": "40ec785e-d915-49de-9053-4630042d8182",
"first_name": "John",
"last_name": "Doe",
"email": "john.doe@example.com",
"phone_number": "+27123456789",
"entity_id": "550e8400-e29b-41d4-a716-446655440000",
"created_at": "2023-11-15T10:30:45Z",
"updated_at": "2023-11-16T14:22:31Z",
"verified": true,
"verified_at": "2024-08-01T15:30:45Z",
"gender": "Male",
"date_of_birth": "1990-01-01T00:00:00Z",
"country": "ZAF",
"city": "Johannesburg",
"residency": "South Africa",
"id_number": "9001015800084",
"id_type": "National ID",
"id_issue_date": "2020-01-15T00:00:00Z",
"id_issue_expiry": "2030-01-15T00:00:00Z",
"title": "Mr",
"permit_number": "",
"kyc_status": "approved",
"kyc_submitted_at": "2024-08-01T14:00:00Z",
"kyc_reviewed_at": "2024-08-01T15:30:45Z",
"kyc_rejection_reason": ""
}
}KYC Fields
verified- Boolean indicating whether the user is currently KYC verified. This is true when kyc_status is approved, and false when kyc_status is not_started, pending, or rejected. Use kyc_status for the detailed KYC lifecycle state, and verified as the simple approved/not-approved flag.kyc_status- Current KYC status. One of: not_started, pending, approved, rejected.kyc_submitted_at- Timestamp when KYC was submitted. Omitted if KYC has not been submitted.kyc_reviewed_at- Timestamp when KYC was approved or rejected. Omitted while KYC is pending or not started.kyc_rejection_reason- Reason for rejection. Only populated when kyc_status is rejected.
List Users
Retrieves a list of users, including KYC status and verification details. Can be filtered by entity ID.
Subject: svc.user.<partner_id>.list
Permissions Required: read
Request
{
"entity_id": "550e8400-e29b-41d4-a716-446655440000" // Optional - Filter by entity UUID
}Optional Fields
entity_id- Filter results by entity
Response
{
"users": [
{
"id": "40ec785e-d915-49de-9053-4630042d8182",
"first_name": "John",
"last_name": "Doe",
"email": "john.doe@example.com",
"phone_number": "+27123456789",
"entity_id": "550e8400-e29b-41d4-a716-446655440000",
"created_at": "2023-11-15T10:30:45Z",
"updated_at": "2023-11-16T14:22:31Z",
"verified": true,
"verified_at": "2024-08-01T15:30:45Z",
"gender": "Male",
"date_of_birth": "1990-01-01T00:00:00Z",
"country": "ZAF",
"city": "Johannesburg",
"residency": "South Africa",
"id_number": "9001015800084",
"id_type": "National ID",
"id_issue_date": "2020-01-15T00:00:00Z",
"id_issue_expiry": "2030-01-15T00:00:00Z",
"title": "Mr",
"date_registered": "2023-11-15T10:30:45Z",
"permit_number": "",
"kyc_status": "approved",
"kyc_submitted_at": "2024-08-01T14:00:00Z",
"kyc_reviewed_at": "2024-08-01T15:30:45Z",
"kyc_rejection_reason": ""
}
]
}KYC Fields
verified- Boolean indicating whether the user is currently KYC verified. This is true when kyc_status is approved, and false when kyc_status is not_started, pending, or rejected. Use kyc_status for the detailed KYC lifecycle state, and verified as the simple approved/not-approved flag.kyc_status- Current KYC status. One of: not_started, pending, approved, rejected.kyc_submitted_at- Timestamp when KYC was submitted. Omitted if KYC has not been submitted.kyc_reviewed_at- Timestamp when KYC was approved or rejected. Omitted while KYC is pending or not started.kyc_rejection_reason- Reason for rejection. Only populated when kyc_status is rejected.