1. Introduction
Veri5Digital VideoIdKyc Solution offers Android SDK that can be seamlessly integrated with clients business application to do the KYC processing of the user.
This document explains technical aspects and steps to integrate Veri5Digital VideoIdKyc Android SDK with clients application.
Note:
Please refer attached Veri5Digital - VideoIdKyc Android SDK Technical Specification Companion Guide for sample code snippets.
2. Steps To Integrate VideoIdKyc Solution
Clients should follow below steps for integrating Veri5Digital VideoIdKyc Android SDK with their native android application.
Client should get on boarded or registered with Veri5Digital Platform and get client credentials and other parameters required for integration.
Clients Android Application should integrate VideoIdKyc Android SDK.
Integrate fetchKYCInfo API to retrieve the KYC Info processed by VideoIdKyc Android SDK.
Veri5Digital Platform provides Sandbox Environment for Integration and Production Environment for actual production purpose.
We recommend clients to first integrate their application with Veri5Digital VideoIdKyc Android SDK in Sandbox environment and then move to production post testing and certification in Sandbox Environment.
Below sections explain these steps in detail.
2.1 Client Registration
For a client to get access to SDK and do integration, client registration is mandatory.
As part of registration , Client has to obtain values for following params for both Sandbox and Production Environments.
Client Code (client_code) , that uniquely identify the client that is onboarded to Veri5Digital Platform.
API-Key (api_key ) , will be provided during onboarding and is used for managing access to services.
Salt ( salt), salt is for calculating hash of request and response payload and will be shared during onboarding. Each api_key will have its own salt. Hash comparison will be used for checking the integrity of data.
Maven Credentials ( Username and Password ).
SDK Version. This needs to be specified in the gradle file.
SandBox URL & Production URL
Note :
Clients should get these details from Veri5Digital Onboarding Team.
It is recommended that client should first get on boarded in Sandbox environment to do technical integration and testing before moving to production.
2.2 How to integrate Android SDK in Client’s Application
Client should have received the credential information mentioned in Section 2.1 before starting integration.
2.2.1 Step 1 : Retrieve VideoIdKyc SDK from maven repository
Client applications build system should pull VideoIdKyc SDK from Veri5Digital Platforms hosted repository.Please refer below for details.
1. Add the following dependency in client application level gradle file located at project/build.gradle . You should replace username and password with the maven credentials you have received during onboarding.
allprojects {
...
repositories {
...
maven { url "https://jitpack.io" }
maven {
url "https://repo.aadhaarbridge.com/repository/android-sdk/"
credentials {
username '<<your username>>’
password '<<your password>>'
}
}
...
}
...
}
Sample Code Snippet [ Project Level build.gradle ]:
-------------------------------------------------
private void handleFetchKycInfoRes(FetchKycInfo.Res apiResponse)
throws Exception {
String hash = calculateResponseHash(
getIntent().getStringExtra("client_code"),
getIntent().getStringExtra("user_id"),
apiResponse.getKycInfo(),
getIntent().getStringExtra("api_key"),
getIntent().getStringExtra("salt")
);
Log.i(
TAG,
"handleFetchKycInfoRes: Api hash = "
+ apiResponse.getHash() + ", Calculated hash = " + hash + "."
);
if (hash != null && !hash.equalsIgnoreCase(apiResponse.getHash())) {
throw new IllegalArgumentException("Invalid hash!");
}
byte[] decodedResponse = Base64.decode(apiResponse.getKycInfo(), Base64.DEFAULT);
if (apiResponse.getEncrypted().equalsIgnoreCase("yes")) {
showPasswordDialog(decodedResponse);
} else {
refreshUi(decodedResponse);
}
}
2. Add the following dependency in client application’s module level gradle file app/build.gradle
android{
...
packagingOptions {
pickFirst 'lib/x86_64/libopencv_java3.so'
pickFirst 'lib/x86/libopencv_java3.so'
pickFirst 'lib/armeabi-v7a/libopencv_java3.so'
pickFirst 'lib/arm64-v8a/libopencv_java3.so'
}
defaultConfig{
ndk{
abiFilters “arm64-v8a”, “armeabi-v7a”, “x86”,
vectorDrawables.useSupportLibrary true
renderscriptTargetApi 21
renderscriptSupportModeEnabled true
}
...
}
...
}
Sample Code Snippet [ App Level build.gradle ]:
----------------------------------------------
apply plugin: 'com.android.application'
android {
packagingOptions {
pickFirst 'lib/x86_64/libopencv_java3.so'
pickFirst 'lib/x86/libopencv_java3.so'
pickFirst 'lib/armeabi-v7a/libopencv_java3.so'
pickFirst 'lib/arm64-v8a/libopencv_java3.so'
}
compileSdkVersion 28
//buildToolsVersion "29.0.1"
defaultConfig {
applicationId "com.example.vikycsampleapp"
minSdkVersion 21
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
ndk {
abiFilters "arm64-v8a", "armeabi-v7a", "x86", "x86_64"
}
vectorDrawables.useSupportLibrary true
renderscriptTargetApi 21
renderscriptSupportModeEnabled true
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
implementation 'com.jakewharton:butterknife:10.0.0'
annotationProcessor 'com.jakewharton:butterknife-compiler:10.0.0'
implementation "org.bouncycastle:bcprov-jdk16:$bouncyCastleVersion"
implementation 'com.khoslalabs.sdk:videoidkyc:4.2.0-SB'
implementation 'com.khoslalabs.sdk:base:4.2.0-SB'
implementation 'com.khoslalabs.sdk:ocrsdk:4.2.0-SB'
implementation 'com.khoslalabs.sdk:facesdk:4.2.0-SB'
}
Note :
- If client is using other build systems like maven, they should incorporate these dependencies accordingly.
2.2.2 Step 2 : Configure VideoIdKyc Module Dependencies
In this step you should add different sdk modules into your module level gradle file depending on the functionalities like Document Scan , Liveness Check etc that you have chosen.
Add following dependencies in app/build.gradle :
dependencies {
...
implementation 'com.khoslalabs.sdk:videoidkyc:<<latest version>>'
implementation 'com.khoslalabs.sdk:base:<<latest version>>'
// <<1>> Add if Document Scan functionality is required
implementation 'com.khoslalabs.sdk:ocrsdk:<<latest version>>'
// <<2>> Add if Livenes functionality is required
implementation 'com.khoslalabs.sdk:facesdk:<<latest version>>'
...
}
Note:
You should replace latest version with actual version received during onboarding. Please refer above code snippet for reference, where version is <<4.2.0-SB>>
2.2.3 Step 3 : Initiation of VideoIdKyc SDK from Clients Application
Inside client application’s calling activity include below code to initiate VideoIdKyc SDK. Refer the below code snippet.
- VideoIdKycInitRequest videoIdKycInitRequest =new VideoIdKycInitRequest.Builder(
<<your client code>>,
<<your api key>>,
<<purpose>>,
<<your request id>>,
<<calculated hash>>
)
// if watermarking of selfie and document is required, pass plmareq = "YES" else "NO"
.plmaRequired(plmaReq) (mandatory)
//pass CAF No, agent name and agent id if watermarking is required as accordingly- (optional)
.cafNumber(cafNo)
.agentName(agentName)
.agentId(agentId)
// pass customer_id if required- (optional)
.customerId(customerId)
.moduleFactory(OcrSdkModuleFactory.newInstance())
.moduleFactory(FaceSdkModuleFactory.newInstance())
.build();
Table : VideoIdKycInitRequest Parameters
Name | Value | Comments | Example |
---|---|---|---|
client_code | String, Mandatory | Your client code received during onboarding | a1b2c3 |
request_id | String, Mandatory | Unique transaction identifier Client app has to generate the same.For each request it should be unique.This is used for uniquely identifying the transaction. | 12345678910111 |
api_key | String, Mandatory | Pre-shared api key controls the access to various Api’s. Api Key will be shared during onboarding.There will be separate api_key for SandBox and Production Environment. | 1A3b5c7D910111 |
hash | String, Mandatory | This should be a SHA-256 value of Hash Sequence defined for _init request. Purpose of this is to ensure the integrity of the request. Refer Appendix B for hash generation logic. | 9780cd0d2ce77eef8f64942f54e0281a0e220ff6bbcce0a03df27a2b15575f58 |
purpose | String, Mandatory | String that specifies the actual intention of SDK invocation. | Eg : “For customer Onboarding”. You can pass “” ( Empty String ) in case you do not need to pass any specific value.. |
Intent myIntent = new Intent(
<<calling activity>>
, VideoIdKycInitActivity.class); // calling_activity refers to the calling application’s class where this snippet is embedded.myIntent.putExtra("init_request", videoIdKycInitRequest);
startActivityForResult(myIntent,
<<your init request code>>
);
// your_init_request_code refers to the unique code that calling application is passing while invoking SDK, and this will be later used for retrieving the results from SDK using onActivityResult Method.
Refer Section 4.1 in Companion Guide for supported customization capabilities and details.
Refer Section 5 in Companion Guide for sample Android Code Snippet.
2.2.4 Step 4 : SDK Response Processing
Response from SDK will be received via onActivityResult method of the activity that invoked VideoIdKyc SDK.
Client Application will receive following three parameters via onActivityResult method :
requestCode : The unique code (
<<your_init_request_code>>
) that client has passed while initiating SDKresultCode : Signifies the status of the SDK processing
data : An intent containing data that is passed from SDK.
If SDK processing results in a successful transaction, client will get a parameter user_id, that client app can later pass in the fetchKYC API to retrieve the KYC data and results.
If SDK processing results in an unsuccessful transaction,client will get appropriate error_code and error_message.
Below snippet of code demonstrates the same :
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
if (requestCode == `<your request code>`) {
if (resultCode == ViKycResults.RESULT_OK || resultCode == ViKycResults.RESULT_DOC_COMPLETE) {
if (data != null) {
// use the user id
String userId = data.getStringExtra("user_id");
}
} else {
if (data != null) {
// use the error code and error message
int code = data.getIntExtra(“error_code”, 333);
String msg = data.getStringExtra(“error_message”);
}
}
}
}
Refer Section 6 in Companion Guide for sample Android Code Snippet.
2.3 Fetch KYC Request
On successful response from SDK is received, client application should invoke fetchKYCInfo API to retrieve the processed KYC info.
API Name : fetchKYCInfo
Method : POST
Sandbox URL : https://sandbox.veri5digital.com/video-id-kyc/api/1.0/fetchKYCInfo
Production URL : https://prod.veri5digital.com/video-id-kyc/api/1.0/fetchKYCInfo
2.3.1 fetchKYCInfo API Request Details
Calling application should form the Request Payload in the following Json format.
Request Header
Content-Type: application/json
Request Body
Please note “headers” tag below is part of Https Request Body.
{
"headers":{
"client_code":"<your client_code>",
"sub_client_code":"<should be same as client_code>",
"actor_type":"NA",
"channel_code":"ANDROID_SDK",
"stan":"<System Trace Number>",
"user_handle_type":"<>",
"user_handle_value":"<>",
"location":"",
"transmission_datetime":"<<System Time In Milli Seconds",
"run_mode":"REAL",
"client_ip":"<<Your IP Address>>",
"operation_mode":"SELF",
"channel_version":"",
"function_code":"REVISED",
"function_sub_code":"DEFAULT"
},
"request":{
"api_key":"<api-key>",
"user_id":"<received in response from android-sdk>",
“hash” : “###########”
}
}
Field Name | Type | Comments | Example |
---|---|---|---|
client_code | String, Mandatory | Unique code assigned to a client.This will be assigned to the client during onboarding. | client123 |
sub_client_code | String, Not Mandatory | Its useful in case calling client needs any customization w.r.to different departments. | subclient123 |
actor_type | String, Not Mandatory | Type of user who is making the request from the application. | CUSTOMER or AGENT |
channel_code | String, Not Mandatory | Channel code used by the client to initiate request. | ANDROID_SDK |
stan | String, Mandatory | A unique number generated per request by the client. It is used to uniquely identify a request from a client and can be used for troubleshooting.Max length can be 64 characters long. | 98321892319 |
user_handle_type | String, Not Mandatory | Indicates the type of the user identifier, who has initiated the request. Eg : UUID, MSISDN, EMAIL. | |
user_handle_value | String, Not Mandatory | Value of user_handle_type. If user_handle_type is EMAIL, then user_handle_value is actual email. | a@b.com |
location | String, Not Mandatory | Location of the device that initiated the request. Can be empty. | |
transmission_datetime | String, Not Mandatory | Time in System milli seconds at which request was initiated from client. | 1553255080298 |
run_mode | String, Not Mandatory | Execution Mode in which transaction is being done.Possible values are REAL or TRIAL. | REAL |
client_ip | String, Not Mandatory | IP Address of the client device. | |
operation_mode | String, Not Mandatory | The way in which the transaction is executed.Example values can be “SELF”,”ASSISTED”,”SYSTEM” etc. | SELF |
channel_version | String, Not Mandatory | It can be Android SDK Version | 3.1.7 |
function_code | String, Mandatory | Defines the behaviour of this api.Value should be ‘REVISED’ | REVISED |
function_sub_code | String, Mandatory | Indicates default behaviour for this api. If no customized behaviour requirements, then value will be “DEFAULT” | DEFAULT |
user_id | String, Mandatory | This is same as user_id passed on to client application as part of SDK response. | userid123 |
hash | String, Mandatory | Hash to be calculated by client to verify integrity of request. Please refer APPENDIX C to see how to generate the hash. | |
api_key | String, Mandatory | api_key shared with client during onboarding. | samplekey123 |
Refer Section 7 in Companion Guide for details and sample Code Snippet.
Points to Note :
URL should be changed based on the environment whether its SandBox or Production.
Please ensure correct api_key, client_code,redirect url to be passed.
Ensure that no Spaces before or after the parameter values.
Https protocol is recommended.
Please make sure you invoke fetchKYCInfo within 20 minutes after completing SDK flow. Data will not be retained beyond this duration.
2.3.2 fetchKYCInfo API Response Details
fetchKYCInfo API Response Payload structure is given below :
Response Payload
Response Payload holds the following :
{
“response_status”:{
“status” : ”<<status of API Execution>>”,
“code” : ”<<status code of API Execution>>”,
“message” :”<<message based on the status and code>>”
},
“response_data”:{
“is_encrypted” :”<<YES|NO>>”,
“kyc_info” :”<<base64 encoded KYC info>>”,
“hash” : <<sha256 of hash sequence defined for fetch response>>,
"Location" : <<latitude and longitude location of place where transaction is done">> // if location permission is granted.
}
}
response_status block encapsulates the status of API invocation
- status : Possible values are SUCCESS or FAIL
code : Response code, based on status.
If status is SUCCESS code value will be ‘000’
If status is FAIL, refer to Appendix A for code values.
response_data block is a base64 encoded string comprised of :
- Processed KYC Info (kyc_info) Refer Detailed Structure of kyc_info block for details.
- encrypted flag ( is_encrypted ) with possible values “YES” or “NO” that indicates whether kyc info is encrypted or not. It depends on whether the client has explicitly chosen the provision for data encryption or not during onboarding.By default value will be ‘NO’
- Computed response hash (hash). Refer Appendix B for hash generation logic.
Response Payload Example :
Below given is an example for a successful response.
{
"response_status": {
"status": "SUCCESS",
"code": "000",
"message": "Kyc data fetched successfully"
},
"response_data": {
"is_encrypted": "NO",
"kyc_info": "Base64 Encoded kyc_info block",
"hash": "5eea1888a2920b6fddf25f6c12ec2aba5e7ce2bff6d9391c18fd1b2749a4ba47"
}
}
2.3.2.1 Detailed Structure of kyc_info block
Details of kyc_info block of response payload is given below :
Case 1 : is_encrypted flag is “NO”
If encrypted is NO then kyc_info block is just base64 encoded. Client application should just decode the Base64 encoded kyc_info block to get the kyc data.By default value of is_encrypted flag will be ‘NO’.
Case 2 : is_encrypted flag is “YES”
This means client has chosen the option to have the kyc_info data to be encrypted.If encrypted is YES then base64 encoded kyc_info block is encrypted.
In this case client application should:
First decrypt the kyc_info block. This will give the Base64 encoded kyc_info block.
Decode the Base64 encoded kyc_info block to get the kyc data.
Please refer to Section 3 in Companion Guide to understand the requirements for decryption in case data encryption is chosen, ie if “is_encrypted” is “YES”.
kyc_info Data Structure
Structure of kyc_info block post decoding of Base64 encoded data is given below. Please refer the Json and table given below for more field level details.
There are mainly following blocks :
“original_kyc_info” block holds the KYC Info that is extracted as a result of source document scan.
“declared_kyc_info” block holds the KYC info that is edited/declared by the user post scanning of document.
“photo” block holds face image specific data.
“doc_image” block holds the document image details.
Points to Note :
Please note that below structure contains the exhaustive list of data elements that SDK can provide. For example passport_front or passport_back will be present in case doc_type is “PASSPORT”
Depending on the flow that is chosen few of the fields might not be present.
High level structure of the response remains the same.
Client application should handle the data elements they are interested in.
{
// original_kyc_info tag encapsulates info extracted from the Original ID Document through OCR
"original_kyc_info":{
"address":"<address>",
"gender":"M",
"dob":"17/12/1993",
"name":"John Doe",
“father_name”:””,
"mother_name":"",
"expiry_date":"",
"email":"",
// Value will be Input Mobile#, in case Mobile Validation flow is chosen and hash of input mobile# matches with hash of mobile present in UIDAI Aadhaar XML.
"mobile":"",
"doc_type":"AADHAAR",
"document_id":"XXXXXXXX7059",
"dist": ""
"house":"",
"loc":"" ,
"pc":"" ,
"po":"" ,
"state":"" ,
"street":"" ,
"subdist":"" ,
"vtc":"" ,
"country":"",
“verified_by”:”UIDAI”,
“verified_using”:”DIGITAL_SIGNATURE”,
“document_verification_status”:”SUCCESS|FAIL”
“document_verification_code”:””
“document_verification_message”:””
},
// This tag encapsulates info that is declared or edited by the user after reviewing data extracted from the Original ID Document
"declared_kyc_info":{
"address":"NULL",
"gender":"M",
"dob":"17/12/1993",
"name":"John Doe",
“father_name”:””,
"mother_name":"",
"expiry_date":"",
"Email":"",
// Value will be Input Mobile#, in case Mobile Validation flow is chosen.
"mobile":"",
"doc_type":"AADHAAR",
"document_id":"XXXXXXXX7059",
“Verified_by”:””,
“verified_using”:””,
“document_verification_status”:”SUCCESS|FAIL”
“document_verification_code”:””
“document_verification_message”:””
},
"photo":{
"match_rate":””,
"match_status":"SUCCESS|FAIL|UNCERTAIN",
"document_image":"<Base64 encoded image>",
"live_image":"<base64 encoded image>"
},
"doc_image":{
"pan":"<Base64 encoded image>",
"aadhaar_front":"<Base64 encoded image>",
"aadhaar_back":"<Base64 encoded image>",
"passport_front":"<Base64 encoded image>",
"passport_back":"<Base64 encoded image>"
}
}
Field Name | Block name | Comments |
---|---|---|
address | original_kyc_info | Address info extracted from document as a result of OCR. Address info consists of all address fields as a single string. |
gender | original_kyc_info | Gender Information extracted from document if available in document. |
dob | original_kyc_info | Date Of Birth information extracted from document if available in document. |
name | original_kyc_info | Name extracted from document if available in document. |
father_name | original_kyc_info | Father Name extracted from document if available in document. |
mother_name | original_kyc_info | Mother’s Name extracted from document if available in document. |
expiry_date | original_kyc_info | Expiry Date extracted from document if available in document. |
original_kyc_info | Email extracted from document if available in document. | |
mobile | original_kyc_info | Mobile number user input in client application. It’s an optional field. If no mobile number is given as input, value will be an empty string. Its present in original_kyc_block means hash of input Mobile# matches with mobile hash present in UIDAI Aadhaar XML in case document type is ‘AADHAAR’ |
doc_type | original_kyc_info | Specifies the type of the document. |
document_id | original_kyc_info | Is the unique Document Identifier extracted from the document. For example, if doc_type is PAN then it will be PAN#. If doc_type is Aadhaar, it will be Aadhaar Number with first 8 digits masked. |
dist | original_kyc_info | District info extracted from Address in case of Aadhaar XML. |
house | original_kyc_info | House Name field extracted from Address in Aadhaar XML. |
loc | original_kyc_info | Locality field extracted from Aadhaar XML. |
pc | original_kyc_info | Pincode field extracted from Address in Aadhaar XML. |
po | original_kyc_info | Post Office field extracted from Address in Aadhaar XML. |
state | original_kyc_info | State field extracted from Address in Aadhaar XML. |
street | original_kyc_info | Street field extracted from Address in Aadhaar XML. |
subdist | original_kyc_info | Sub District field extracted from Address in Aadhaar XML. |
vtc | original_kyc_info | Village/Town/City field extracted from Address in Aadhaar XML. |
country | original_kyc_info | Street field extracted from Address in Aadhaar XML. |
verified_by | original_kyc_info | This is applicable in case of Aadhaar XML. Value will be “UIDAI” in this case. |
verified_using | original_kyc_info | This is applicable in case of Aadhaar XML. Value will be “DIGITAL_SIGNATURE” in this case. |
document_verification_status | original_kyc_info | In case the client has opted for verification of the Id doc against government database, this indicates the document verification status. Possible values are SUCCESS |
document_verification_code | original_kyc_info | In case the client has opted for verification of the Id doc (Verification API) against government database, this indicates the document verification status based on declared info. Please refer to Section 4.4 in Companion Guide for more details. If this service is not opted value will be an Empty String. |
document_verification_message | original_kyc_info | In case the client has opted for verification of the Id doc (Verification API) against government database, this indicates the document verification status based on declared info. Please refer to Section 4.4 in Companion Guide for more details. If this service is not opted value will be an Empty String. |
address | declared_kyc_info | Address info that is declared by the user after reviewing data extracted from the Original ID Document. There is a review page where user can edit the info. |
gender | declared_kyc_info | Gender Information info that is declared by the user after reviewing data extracted from the Original ID Document |
dob | declared_kyc_info | Date Of Birth information info that is declared by the user after reviewing data extracted from the Original ID Document |
name | declared_kyc_info | Name info that is declared by the user after reviewing data extracted from the Original ID Document. |
father_name | declared_kyc_info | Father Name info that is declared by the user after reviewing data extracted from the Original ID Document |
mother_name | declared_kyc_info | Mother’s Name info that is declared by the user after reviewing data extracted from the Original ID Document |
expiry_date | declared_kyc_info | Expiry Date info that is declared by the user after reviewing data extracted from the Original ID Document |
declared_kyc_info | Email info that is declared by the user after reviewing data extracted from the Original ID Document | |
mobile | declared_kyc_info | Mobile number user input in client application. It’s an optional field. If no mobile number is given as input, value will be an empty string. Its present in declared_kyc_info means hash of input Mobile# does not match with mobile hash present in UIDAI Aadhaar XML in case document type is ‘AADHAAR’ |
doc_type | declared_kyc_info | Specifies the type of the document. |
document_id | declared_kyc_info | Is the unique Document Identifier info that is declared by the user after reviewing data extracted from the Original ID Document For example, if doc_type is PAN then it will be PAN#. If doc_type is Aadhaar, it will be Aadhaar Number . |
verified_by | declared_kyc_info | This is applicable in case of Aadhaar XML. Value will be “UIDAI” in this case. |
verified_using | declared_kyc_info | This is applicable in case of Aadhaar XML. Value will be “DIGITAL_SIGNATURE” in this case. document_verification_status |
document_verification_code | declared_kyc_info | In case the client has opted for verification of the Id doc (Verification API) against government database, this indicates the document verification status based on declared info. Please refer to Section 4.4 in Companion Guide for more details. If this service is not opted value will be an Empty String. |
document_verification_message | declared_kyc_info | In case the client has opted for verification of the Id doc (Verification API) against government database, this indicates the document verification status based on declared info. Please refer to Section 4.4 in Companion Guide for more details. If this service is not opted value will be an Empty String. |
match_rate | photo | Face Match Confidence Percentage. |
match_status | photo | Possible values are SUCCESS/FAIL/UNCERTAIN. If face match confidence rate is above True Positive (TP) threshold rate configured, then status will be SUCCESS. If face match confidence rate is below True Negative (TN) threshold rate configured, then status will be FAIL. If face match confidence rate is between True Positive (TP) Threshold and True Negative (TN) Threshold, then status will be UNCERTAIN. |
document_image | photo | Base64 Encoded String of face image of the user that is extracted from document captured by SDK. |
live_image | photo | Base64 Encoded String of Live Face captured by SDK for liveness check. |
pan | doc_image | Base64 Encoded String of PAN Document captured by SDK. |
aadhaar_front | doc_image | Base64 Encoded String of Aadhaar Front Document captured by SDK. |
aadhaar_back | doc_image | Base64 Encoded String of Aadhaar Back Document captured by SDK. |
passport_front | doc_image | Base64 Encoded String of Passport Front Page Document captured by SDK. |
passport_back | doc_image | Base64 Encoded String of Passport Back Page Document captured by SDK. |
Appendix A: Error Codes
Error Code | Scenario | Message |
---|---|---|
333 | SDK | There seems to be an unknown issue. Please try again later. |
777 | SDK | Transaction Expired |
111 | SDK | Invalid otp type. |
222 | SDK | Aadhaar Card and Aadhaar QR Details do not match. Please initiate process with the same Aadhaar Card for OCR Scan and QR. |
444 | SDK | Document not supported yet! |
555 | SDK | Transaction cancelled! |
666 | SDK | Dependencies required for your KYC flow were not found. |
888 | SDK | Please check your internet connection and retry. |
999 | SDK | There seems to be an issue (UIDAI). Please try again later. |
1111 | SDK | Seems like you might not have received OTP (UIDAI). Please try again later. |
380001 | SDK | api_key is mandatory |
380002 | SDK | request_id is mandatory. |
380003 | SDK | purpose is mandatory. |
380036 | SDK | Session error occurred. |
380037 | SDK | Session error occurred. |
380039 | SDK | Session error occurred. |
380040 | SDK | Session error occurred. |
380048 | SDK | Invalid api_key or client_code |
380049 | SDK | Invalid api_key or client_code |
380050 | SDK | Invalid api_key configuration. |
380092 | SDK | Invalid api_key configuration. |
380090 | SDK | Hash is mandatory. |
380091 | SDK | Hash validation failed. |
240002 | SDK | Insufficient Balance. |
240003 | SDK | Subscribed Service is inactive. |
240004 | SDK | Subscription of the client is inactive. |
240005 | SDK | Sorry, You have exceeded the consumption limit. Please contact customer.support@aadhaarbridge.com |
240006 | SDK | Subscription info not found. |
380024 | fetchKYC | user_id is mandatory |
380025 | fetchKYC | hash is mandatory |
380026 | fetchKYC | api_key is mandatory |
380048 | fetchKYC | Invalid api_key or client_code |
380049 | fetchKYC | Invalid api_key or client_code |
380091 | fetchKYC | Hash validation failed |
380076 | fetchKYC | No such user-id exists |
380114 | fetchKYC | Session does not exist. |
380116 | fetchKYC | Transaction does not exist. |
333 | fetchKYC | Unexpected Error Occured |
Appendix B: Hash generation
It is essential that we have a definitive protocol to verify the integrity of all the communication between Khosla Labs Platform and Client.
So for every request coming to KL, client has to supply a hash which KL will use as the first step of verification.
In return, all responses will also contain hash supplied by KL Platform.
Hash should be calculated using the following method: hash=SHA256(Hash-Sequence)
Hash Sequence is specified as follows(no space, no commas, no single/double quotes)
--> Initiate SDK Request :
client_code
|request_id
|api_key
|salt
--> fetchKYCInfo Request :
client_code
|user_id
|api_key
|salt
--> fetchKYCInfo Response :
client_code
|user_id
|kyc_info
|api_key
|salt
Example for _init request, If your
client_code=a1b2c3,
api_key=123,
requestId=1234567890101112,
salt=e1d2c3b4a,
then
Hash-Sequence=a1b2c3|1234567890101112|123|e1d2c3b4a
hash =SHA-256(Hash-Sequence)
For validation:
Receiving end should calculate hash based on request parameters and match it against the received hash.
If receivedHash=calculatedHash, then only you should proceed with your application logic.
api_key and salt are the key parameters here. It is known only to the client and Khosla Labs.
One api_key is mapped to one salt. This allows us to have multiple salts active at a time.
api_key and hash need to be passed in each API call. Salt is never transmitted in any API call.
Sample Code Snippet _init Request Hash Calculation
private String calculateHash(String clientCode, String requestId, String apiKey, String salt)
throws NoSuchAlgorithmException {
MessageDigest digest;
digest = MessageDigest.getInstance("SHA-256");
if (digest != null) {
byte[] hash = digest.digest((clientCode + "|" + requestId + "|" + apiKey + "|" + salt).getBytes());
return bytesToHex(hash);
}
return null;
}
private final static char[] hexArray = "0123456789ABCDEF".toCharArray();
public static String bytesToHex(byte[] bytes) {
char[] hexChars = new char[bytes.length * 2];
for (int j = 0; j < bytes.length; j++) {
int v = bytes[j] & 0xFF;
hexChars[j * 2] = hexArray[v >>> 4];
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
}
return new String(hexChars);}
Appendix C : GitHub Link For Sample Application
Below is the github repository link of codebase of sample App integrated to our video-id-kyc android SDK alongwith implementation of fetchKYCInfo API.
https://github.com/dev-kl/video-id-kyc-trial-app
Companion Guide
1. Introduction
Veri5Digital VideoIdKyc Solution offers Android SDK that can be seamlessly integrated with clients business application to do the KYC processing of the user.
This document serves as a companion guide to Veri5Digital VideoIdKyc Product Technical Integration Document. This document demonstrates sample code snippets relevant for integration steps as applicable is given.
2. Hash Generation
It is essential that we have a definitive protocol to verify the integrity of all the communication between Khosla Labs Platform and Client.
So for every request coming to KL, client has to supply a hash which KL will use as the first step of verification.
In return, all responses will also contain hash supplied by KL Platform.
Hash should be calculated using the following method:
hash=SHA256(Hash-Sequence)
Hash Sequence is specified as follows(no space, no commas, no single/double quotes)
--> Initiate SDK Request :
client_code
|request_id
|api_key
|salt
--> fetchKYCInfo Request :
client_code
|user_id
|api_key
|salt
--> fetchKYCInfo Response :
client_code
|user_id
|kyc_info
|api_key
|salt
Example for _init request, If your
client_code=a1b2c3,
api_key=123,
requestId=1234567890101112,
salt=e1d2c3b4a,
then
Hash-Sequence=a1b2c3|1234567890101112|123|e1d2c3b4a
hash =SHA-256(Hash-Sequence)
For validation:
Receiving end should calculate hash based on request parameters and match it against the received hash.
If receivedHash=calculatedHash, then only you should proceed with your application logic.
api_key and salt are the key parameters here. It is known only to the client and Khosla Labs.
One api_key is mapped to one salt. This allows us to have multiple salts active at a time.
api_key and hash need to be passed in each API call. Salt is never transmitted in any API call.
Sample Code Snippet _init Request Hash Calculation
private String calculateHash(String clientCode, String requestId, String apiKey, String salt) throws NoSuchAlgorithmException {
MessageDigest digest;
digest = MessageDigest.getInstance("SHA-256");
if (digest != null) {
byte[] hash = digest.digest((clientCode + "|" + requestId + "|" + apiKey + "|"+ salt).getBytes());
return bytesToHex(hash);
}
return null;
}
private final static char[] hexArray = "0123456789ABCDEF".toCharArray();
public static String bytesToHex(byte[] bytes) {
char[] hexChars = new char[bytes.length * 2];
for (int j = 0; j < bytes.length; j++) {
int v = bytes[j] & 0xFF;
hexChars[j * 2] = hexArray[v >>> 4];
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
}
return new String(hexChars);
}
3. Data Decryption Sample Code
This section demonstrates the process and sample code for implementation of decrypting kyc response in case client has opted for encryption of response data.
Client has to :
Generate the Key Pair that consists of Public and Private Keys
Implement decryption Logic
Details of these steps are explained in below sections.
3.1 Key Pair Generation Process
If client has opted for kyc data encryption, client should create the public-private key pair that will be used for encryption and decryption. Generated public key should be shared with KL.
SDK will encrypt kyc_data using the public key shared with KL. Client application should decrypt the response using the private key.
This section explains the steps to generate the public-private key pair.
Steps to generate a CSR file using OpenSSL.
- Create private key with a validity
keytool -genkey -alias ALIAS_NAME -keyalg RSA -keystore COMP_NAME.jks -keysize 2048 -dname "CN=COMMON_NAME, EMAILADDRESS=sample@abc.com, C=IN, OU=Khosla Labs, O=ORGANIZATION_NAME"
Note :
ALIAS_NAME can be replaced with any string of your choice.
COMP_NAME can be replaced by your company name..
CN : Should give a string value.This should be the same as the contact person’s name.
EMAILADDRESS :Should give a string value. This should be some valid email id.Eg sample@abc.com
ORG_UNIT: Value should be “Khosla Labs”
ORGANIZATION_NAME: This should be the same as the organization name.
Create CSR for above key
keytool -certreq -alias ALIAS_NAME -file COMP_NAME.csr -keystore COMP_NAME.jks
Convert jks store to p12
keytool -importkeystore -srckeystore COMP_NAME.jks -destkeystore COMP_NAME.p12 -srcstoretype jks -deststoretype pkcs12
Get private key from .p12 file
openssl pkcs12 -in COMP_NAME.p12 -nodes -out COMP_NAME.key -nocerts
Self sign .csr file using the private key to get .cer file
openssl x509 -req -in COMP_NAME.csr -signkey COMP_NAME.key -out COMP_NAME.cer
Provide us the .cer file (public key certificate) generated above using which KYC response will be encrypted and use .p12 (private key) for decryption.
3.2 Code Snippet : kyc_info Data Block Decryption
Below given is Java Code Snippet that can be used as a reference for decryption using your private key.
Snippet 1: Response Reading and Decryption
Below code will read the Base64 encoded response string returned by the API. First it will Base64 decoding and then followed by decryption using your private key.This uses few utility classes given in Snippet 2 and Snippet 3.
// byte array of the base64 decoded response
byte[] decodeResponse = `<base64_decode_response>`;
// open your p12 file
// to generate your p12 file
InputStream inputStream = new FileInputStream("`<your_p12_file>.p12`");
// your keystore credentials
char[] keystorePassword = "`<your_keystore_password>`".toCharArray();
char[] aliasPassword = "`<your_alias_password>`".toCharArray();
String alias = "`<your_alias>`";
KeyStore keyStore = KeyStore.getInstance("PKCS12", "BC");
keyStore.load(inputStream, keystorePassword);
// get the private key
PrivateKey privateKey = (PrivateKey) keyStore.getKey(alias, aliasPassword);
ResponseByteArraySpliter splitter = new ResponseByteArraySpliter(decodeResponse);
// get the symmetric key
byte[] sKey = DecryptionUtil.decryptUsingPrivateKey(
privateKey, splitter.getEncryptedSymmetricKey()
);
// byte array of the decrypted data
byte[] decryptedData =
DecryptionUtil.decryptUsingSymmetricKey(
sKey, splitter.getEncryptedData()
);
Snippet 2 : ResponseByteArraySplitter Class.
// ResponseByteArraySplitter class
// Note: Please alter the access modifiers according to your packaging strategy.
public class ResponseByteArraySplitter {
private static final int SYMMETRIC_KEY_SIZE = 256;
private final byte[] encryptedSymmetricKey;
private final byte[] encryptedData;
public ResponseByteArraySplitter(byte[] data) throws Exception {
int offset = 0;
encryptedSymmetricKey = new byte[SYMMETRIC_KEY_SIZE];
copyByteArray(
data,
offset,
encryptedSymmetricKey.length,
encryptedSymmetricKey
);
offset = offset + SYMMETRIC_KEY_SIZE;
encryptedData = new byte[data.length - offset];
copyByteArray(data, offset, encryptedData.length, encryptedData);
}
byte[] getEncryptedSymmetricKey() {
return encryptedSymmetricKey;
}
byte[] getEncryptedData() {
return encryptedData;
}
private void copyByteArray(byte[] src, int offset, int length, byte[] dest) throws Exception {
try {
System.arraycopy(src, offset, dest, 0, length);
} catch (Exception e) {
throw new Exception("Decryption failed, Corrupted packet!", e);
}
}
}
Snippet 3 : Decryption Util Class.
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.PrivateKey;
import javax.crypto.Cipher;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.engines.AESEngine;
import org.bouncycastle.crypto.paddings.PKCS7Padding;
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
import org.bouncycastle.crypto.params.KeyParameter;
public class DecryptionUtil {
private static final String ASYMMETRIC_ALGO = "RSA/ECB/PKCS1Padding";
public static byte[] decryptUsingPrivateKey( PrivateKeyprivateKey, byte[] data) throws IOException, GeneralSecurityException {
Cipher pkCipher = Cipher.getInstance(ASYMMETRIC_ALGO);
pkCipher.init(Cipher.DECRYPT_MODE, privateKey);
return pkCipher.doFinal(data);
}
public static byte[] decryptUsingSymmetricKey( byte[] symmetricKey, byte[] data) throws InvalidCipherTextException {
PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher( new AESEngine(), new PKCS7Padding());
cipher.init(false, new KeyParameter(symmetricKey));
int outputSize = cipher.getOutputSize(data.length);
byte[] tempOP = new byte[outputSize];
int processLen = cipher.processBytes(data, 0, data.length, tempOP, 0);
int outputLen = cipher.doFinal(tempOP, processLen);
byte[] result = new byte[processLen + outputLen];
System.arraycopy(tempOP, 0, result, 0, result.length);
return result;
}
}
4. Customization Capabilities
VideoIdKyc SDK provide the client application ability to do different customizations.Supported customization capabilities and how to implement these customisations are explained below.
Note :
Clients should specify these customizations to the onboarding team during onboarding process. This is required as some configurations has to be done accordingly.
4.1 SDK Initialization And Customization
SDK provides the option to choose Mobile Validation using OTP. If client has opted for OTP flow then following highlighted lines has to be added in VideoIdKycInitiRequest initiation as shown below. If this flow is enabled, there will be an OTP Input screen and client has to pass mobile number and Otp Type as ‘MOB_NO’ in the request.
- VideoIdKycInitRequest videoIdKycInitRequest = new VideoIdKycInitRequest
.Builder(
<<your client code>>
,<<your api key>>
,<<your purpose>>
,<<your request id>>
,<<calculated hash>>
)
// Add In Case OTP Validation is chosen, specify OTP Tye.
.otpType(
// Add In Case OTP Validation is chosen and channel is MOBILE
.mobile(<mobile number>
)
.moduleFactory(OcrSdkModuleFactory.newInstance())
.moduleFactory(FaceSdkModuleFactory.newInstance())
.build();
Table : VideoIdKycInitRequest Parameters
Name | Value | Comments | Example |
---|---|---|---|
client_code | String, Mandatory | Your client code received during onboarding | a1b2c3 |
request_id | String, Mandatory | Unique transaction identifier Client app has to generate the same.For each request it should be unique.This is used for uniquely identifying the transaction. | 12345678910111 |
api_key | String, Mandatory | Pre-shared api key controls the access to various Api’s. Api Key will be shared during onboarding.There will be separate api_key for SandBox and Production Environment. | 1A3b5c7D910111 |
hash | String, Mandatory | This should be a SHA-256 value of Hash Sequence defined for _init request. Purpose of this is to ensure the integrity of the request. Refer Hash generation for hash generation logic. | 9780cd0d2ce77eef8f6 4942f54e0281a0e220f f6bbcce0a03df27a2b1 5575f58 |
purpose | String, Mandatory | String that specifies the actual intention of SDK invocation. | Eg : “For customer Onboarding”. If there is no need to pass purpose, please pass “” ( empty ) string. |
4.2 Application Obfuscation And Proguard Rules
If the client requires obfuscation of application code, please add the below proguard rules in your app’s proguard-rules.pro file.These rules specify the exclusion list that will not impact VideoIdKyc SDK which is already obfuscated.
# Preserve all annotations.
-keepattributes *Annotation*
# Preserve R.*.* things.
-keepclassmembers class **.R$* {
public static <fields>;
}
# Preserve things required for parcelable classes.
-keepclassmembers class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator CREATOR;
}
# Preserve serializable things.
-keepclassmembers class * implements java.io.Serializable {
static final long serialVersionUID;
static final java.io.ObjectStreamField[] serialPersistentFields;
private void writeObject(java.io.ObjectOutputStream);
private void readObject(java.io.ObjectInputStream);
java.lang.Object writeReplace();
java.lang.Object readResolve();
}
# Preserve enums.
-keepclassmembers class * extends java.lang.Enum {
public static **[] values();
public static ** valueOf(java.lang.String);
}
# Preserve all .class method names.
-keepclassmembernames class * {
java.lang.Class class$(java.lang.String);
java.lang.Class class$(java.lang.String, boolean);
}
# Preserve all native method names and the names of their classes.
-keepclasseswithmembernames class * {
native <methods>;
}
# Ignore support lib warnings.
-dontwarn android.support.**
# Preserve support libs.
-keep interface android.support.v4.** { *; }
-keep interface android.support.v7.** { *; }
-keep class android.support.** { *; }
# Preserve khosla labs libs.
-keep public class com.khoslalabs.** { *; }
-keep class com.khoslalabs.vikycapi.** { *; }
Note :
Proguard is a delicate tool, make sure you test your app thoroughly after enabling it.
4.3 User Interface And Customization
VideoIdKyc SDK can be customized using Android Theming/Styles framework by adding styles in Client Application’s styles.xml file.
Following customization capabilities are supported :
Basic Colors
Action Bar Background Color
Fonts
Action Button Text Color
4.3.1 Customizing Basic Colors
To change basic color theme of the sdk, add the following in client applications styles.xml file. This facilitates to change Primary Color, PrimaryDark color and Accent Color.
The style elements include :
<style>
name="VideoIdKycSdkTheme"
parent="Theme.AppCompat.Light.NoActionBar">
<item name="colorPrimary"> your primary color </item>
<item name="colorPrimaryDark"> your primary dark color </item>
<item name="colorAccent"> your accent color here </item>
</style>
4.3.2 Action Bar Background Color
To change the background color of the action bar, add the following in the client app’s styles.xml file.
<style>
name="VideoIdKycSdkTheme.Widget.Toolbar"
parent="Widget.AppCompat.Toolbar">
<item name="android:background"> your background color </item>
<item name="titleTextAppearance">
@style/TextAppearance.AppCompat.Widget.ActionBar.Title.Inverse
</item>
</style>
If the required action bar background color is light, change the titleTextAppearance and appropriate dark icons as given below.
<style
name="VideoIdKycSdkTheme.Widget.Toolbar"
parent="Widget.AppCompat.Toolbar">
<item name="android:background">
@color/colorVikycSdkActionBarBackground
</item>
<item name="titleTextAppearance">
@style/VideoIdKycSdkTheme.TextAppearance.Toolbar
</item>
</style>
<style
name="VideoIdKycSdkTheme.TextAppearance.Toolbar"
parent="TextAppearance.AppCompat.Widget.ActionBar.Title">
</style>
<style name="VideoIdKycSdkTheme.Widget.Toolbar.Back">
<item name="navigationIcon"> appropriate dark back icon </item>
</style>
<style name="VideoIdKycSdkTheme.Widget.Toolbar.Close">
<item name="navigationIcon"> appropriate dark close icon </item>
</style>
4.3.3 Font Style Customization
To change the font in the whole SDK:
1. Add the .otf/.ttf file of the required font in the font resource folder (res/font/)
2. Set the font as the value for fontFamily param in VideoIdKycSdkTheme in the client’s styles.xml file.
<style
name="VideoIdKycSdkTheme"
parent="Theme.AppCompat.Light.NoActionBar">
<item name="android:fontFamily">@font/your_font_name</item>
</style>
3. Pass the same font as the value of font parameter of the VideoIdKycInitRequest.
CodeSnippet
VideoIdKycInitRequest.Builder(...)
.
.
.
.font(<res id of the font>)
4. Make sure to pass the font in both places as mentioned in points 2 and 3.
If the required action bar color is different than the app’s font, add the following in the client app’s styles.xml file.
```CodeSnippet
<style
name="VideoIdKycSdkTheme.Widget.Toolbar"
parent="Widget.AppCompat.Toolbar">
<item name="android:background"> you action bar background color </item>
<item name="titleTextAppearance">
@style/VideoIdKycSdkTheme.TextAppearance.Toolbar
</item>
</style>
<style
name="VideoIdKycSdkTheme.TextAppearance.Toolbar"
parent="TextAppearance.AppCompat.Widget.ActionBar.Title.Inverse">
<item name="android:fontFamily">@font/your_font_name</item>
</style>
4.3.4 Action Button Text Style Customization
To change the action button text color, add the following in the client app’s styles.xml file.
<style
name="VideoIdKycSdkTheme.TextAppearance.ActionButton"
parent="TextAppearance.AppCompat.Widget.Button">
<item name="android:textColor"> your action button text color </item>
</style>
4.4 Verification API
VideoIdKyc Solution provides the capability to verify document details ( PAN and Aadhaar ) against government databases like income tax database for PAN document and UIDAI database for Aadhaar Document. Client has to opt for this service during onboarding. Accordingly the extracted data will be verified against the actual database source.Following table summarizes different Verification Status, Verification Code and Verification Message that can be returned.
SI# | Verified By | Verification Status | Verification Code | Verification Message |
---|---|---|---|---|
1 | PAN | SUCCESS | 000 | Verification done Successfully |
2 | PAN | FAIL | 400101 | PAN format is correct and PAN number is correct matching against DOB. However, Name is Incorrect. |
3 | PAN | FAIL | 400102 | PAN format is correct but PAN or Name or DOB is incorrect. |
4 | PAN | FAIL | 400103 | Invalid PAN |
5 | PAN | FAIL | 410007 | Verification Service Not Available. |
6 | PAN | FAIL | 400099 | Unknown Error Occurred.Please try after some time. |
7 | Aadhaar | SUCCESS | 000 | Verification done Successfully and details are matching with the PAN Database. |
8 | Aadhaar | FAIL | 400301 | Aadhaar number is not present. |
9 | Aadhaar | FAIL | 400302 | Verification is not complete. Please Try again. |
10 | Aadhaar | FAIL | 400303 | Not a valid Aadhaar number. |
11 | Aadhaar | FAIL | 400304 | UIDAI website not reachable. |
12 | Aadhaar | FAIL | 333 | Unknown Error Occurred.Please try after some time. |
5. SDK Invocation - Reference Android Code Snippet
This section demonstrates reference Android Code that invokes the SDK.
/**
* Reference code that can be used Android Application developer for integrating Veri5Digital
* VideoIdKyc SDK into hosting application
*
*/
package com.demo.basics;
/**
* This is a reference Activity class that can be used by clients to implement their own Activity to
invoke VideoIdKyc SDK.
* MainActivity can be replaced by clients own Activity Name.
* @author Aditya Upadhay
*
*/
public class MainActivity extends AppCompatActivity {
private static final String TAG = MainActivity.class.getSimpleName();
private static final int VI_KYC_SDK_REQ_CODE = 7879;
@BindView(R.id.smtButton)
Button buton;
@BindView(R.id.client_code)
EditText etClientcode;
@BindView(R.id.apiKey)
EditText etAPIkey;
@BindView(R.id.client_salt)
EditText etSalt;
@BindView(R.id.mob_No)
EditText etMobNo;
@BindView(R.id.email_Id)
EditText etEmail;
@BindView(R.id.progressBar)
ContentLoadingProgressBar progressBar;
String salt;
/**
* Dummy function to demonstrate the required params for invoking SDK.
*
* @param savedInstanceState
*/
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
buton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
salt = (etSalt.getText().toString().equals(""))? "BYPASS" : etSalt.getText().toString();
startSdk(
etClientcode.getText().toString(),
etAPIkey.getText().toString(),
salt,
getOtpType(), //Optional
etMobNo.getText().toString(), // Optional
null,
true,
false,
false,
false
);
}
});
}
/**
* This function is used to invoke the VideoIDKyc SDK.
* For invoking SDK critical params like clientCode, apiKey,otpType( if OTP is not opted pass
* NULL), and other params.
**/
public void startSdk(
String clientCode, // Client Code shared during Onboarding
String apiKey, // Api Key shared during Onboarding
String salt, // Salt shared during Onboarding
String otpType, // OTP Type option opted by client it can be null, "MOB_NO""
String mobNo, //Moble No for getting OTP if otpType is MOB_NO
boolean ocrSdk,
boolean faceSdk,
) {
try {
/**
* ReuestId is a unique Id that should be passed for each transaction request.
* Clients can use their own mechanism for generating requestId. Below given is just for sample.
*/
String requestId = clientCode + "-" + TimeUtil.getCurTimeMilisec() + "";
// For request integrity check Hash Digest is also passed. Refer below how to do the same.
String hash = calculateHash(clientCode, requestId, apiKey, salt);
if (hash == null) {
throw new Exception("Hash cannot be generated.");
}
Intent myIntent = new Intent(MainActivity.this, VideoIdKycInitActivity.class);
// Forming VideoIdKyc Request for invoking SDK.
VideoIdKycInitRequest.Builder videoIdKycInitRequestBuilder =
new VideoIdKycInitRequest
.Builder(
clientCode,
apiKey,
"Testing Demo Application",
requestId,
hash
)
.otpType(otpType)
.email(email)
.mobile(mobNo)
.screenTitle(screenTitle)
.salt(salt);
videoIdKycInitRequestBuilder.moduleFactory(OcrSdkModuleFactory.newInstance());
videoIdKycInitRequestBuilder.moduleFactory(FaceSdkModuleFactory.newInstance());
myIntent.putExtra("init_request", videoIdKycInitRequestBuilder.build());
startActivityForResult(myIntent, VI_KYC_SDK_REQ_CODE);
} catch (Exception e) {
Log.e(TAG, "startSdk: ", e);
}
}
/**
* This function handles response from SDK post processing UserId will be returned from veri5Digital
* android SDK result, which then client will use for getting the
* KYCinfo of user by calling fetchKycInfo API.
*
*/
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
if (requestCode == VI_KYC_SDK_REQ_CODE) {
if (resultCode == ViKycResults.RESULT_OK || resultCode == ViKycResults.RESULT_DOC_COMPLETE) {
if (data != null) {
String userId = data.getStringExtra(ViKycConstants.KEY_USER_ID); //here user id is taken, will be used by the client to get the KYCinfo block of user
if (userId != null) {
/**
* here we have are calling one activity method(startkycDataActivity) as demo to process the KYC info block
*/
startKycDataActivity(
userId,
etClientcode.getText().toString(),
etAPIkey.getText().toString(),
salt
);
}
}
else {
showToast("Video Id KYC was not completed!");
}
} else {
if (data != null) {
int code = data.getIntExtra(ViKycConstants.KEY_ERROR_CODE, 333);
String msg = data.getStringExtra(ViKycConstants.KEY_ERROR_MESSAGE);
if (msg != null) {
showToast(code + " : " + msg);
}
} else {
showToast("Video Id KYC was not completed!");
}
}
}
}
/**
* using userId client can implement their own mechanism to get KYCinfo block
* here we have just implemented one activity method(startkycDataActivity) as demo
*/
private void startKycDataActivity(String userId,
String clientCode,
String apiKey,
String salt) {
Intent i = new Intent(this, KycDataActivity.class);
i.putExtra("user_id", userId);
i.putExtra("client_code", clientCode);
i.putExtra("api_key", apiKey);
i.putExtra("salt", salt);
startActivity(i);
}
private void showToast(String s) {
Toast.makeText(MainActivity.this, s + "", Toast.LENGTH_LONG).show();
}
}
/**
*
* Function to calculate Hash Digest. It uses sequence of clientCode|requestId|apiKey|salt
*
* @param clientCode
* @param requestId
* @param apiKey
* @param salt
* @return
* @throws NoSuchAlgorithmException
*/
private String calculateHash(String clientCode, String requestId, String apiKey, String salt) throws NoSuchAlgorithmException {
MessageDigest digest;
digest = MessageDigest.getInstance("SHA-256");
if (digest != null) {
byte[] hash =
digest
.digest(
(clientCode + "|" + requestId + "|" + apiKey + "|" + salt)
.getBytes()
);
return bytesToHex(hash);
}
return null;
}
private final static char[] hexArray = "0123456789ABCDEF".toCharArray();
public static String bytesToHex(byte[] bytes) {
char[] hexChars = new char[bytes.length * 2];
for (int j = 0; j < bytes.length; j++) {
int v = bytes[j] & 0xFF;
hexChars[j * 2] = hexArray[v >>> 4];
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
}
return new String(hexChars);
}
6. SDK Response Processing - Reference Android Code Snippet
/**
* This is a reference Activity class that can be used by clients to implement their own Activity to
* invoke VideoIdKyc SDK.
* MainActivity can be replaced by clients own Activity Name.
* @author Aditya Upadhay
*
*/
public class MainActivity extends AppCompatActivity {
private static final String TAG = MainActivity.class.getSimpleName();
private static final int VI_KYC_SDK_REQ_CODE = 7879;
@BindView(R.id.smtButton)
Button buton;
@BindView(R.id.client_code)
EditText etClientcode;
@BindView(R.id.apiKey)
EditText etAPIkey;
@BindView(R.id.client_salt)
EditText etSalt;
@BindView(R.id.mob_No)
EditText etMobNo;
@BindView(R.id.email_Id)
EditText etEmail;
@BindView(R.id.progressBar)
ContentLoadingProgressBar progressBar;
String salt;
/**
* This function handles response from SDK post processing
* UserId will be returned from veri5Digital android SDK result, which then client will use for
* getting the KYCinfo of user by calling fetchKycInfo API.
*
*/
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
if (requestCode == VI_KYC_SDK_REQ_CODE) {
if (resultCode == ViKycResults.RESULT_OK || resultCode == ViKycResults.RESULT_DOC_COMPLETE) {
if (data != null) {
String userId = data.getStringExtra(ViKycConstants.KEY_USER_ID); //here user id is taken, will be used by client to get the KYCinfo block of user
if (userId != null) {
/**
* here we are calling one activity method(startkycDataActivity) as demo to process
*the KYCinfo block.
*/
startKycDataActivity(
userId,
etClientcode.getText().toString(),
etAPIkey.getText().toString(),
salt
);
}
} else {
showToast("Video Id KYC was not completed!");
}
} else {
if (data != null) {
int code = data.getIntExtra(ViKycConstants.KEY_ERROR_CODE, 333);
String msg = data.getStringExtra(ViKycConstants.KEY_ERROR_MESSAGE);
if (msg != null) {
showToast(code + " : " + msg);
}
} else {
showToast("Video Id KYC was not completed!");
}
}
}
}
/**
* using userId client can implement their own mechanism to get KYCinfo block
* here we have just implemented one activity method(startkycDataActivity) as demo
*/
private void startKycDataActivity(String userId,
String clientCode,
String apiKey,
String salt) {
Intent i = new Intent(this, KycDataActivity.class);
i.putExtra("user_id", userId);
i.putExtra("client_code", clientCode);
i.putExtra("api_key", apiKey);
i.putExtra("salt", salt);
startActivity(i);
}
private void showToast(String s) {
Toast.makeText(MainActivity.this, s + "", Toast.LENGTH_LONG).show();
}
}
7. Invoking fetchKYCInfo API - Reference Android Code Snippet
7.1 fetcKYCInfo API Request
---------------------------
This code snippet demonstrates the Android RxJava Code with Retrofit for making fetchKYCInfo API.
Disposable d = Util.apiService()
.fetchKycInfo(apiRequest)
.flatMap(
new Function<ApiResponse<FetchKycInfo.Res>, ObservableSource<ApiResponse<FetchKycInfo.Res>>>() {
@Override
public ObservableSource<ApiResponse<FetchKycInfo.Res>> apply(ApiResponse<FetchKycInfo.Res> resApiResponse) {
return Util.apiResponseInterceptor(resApiResponse);
}
}
)
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.doOnSubscribe(
new Consumer<Disposable>() {
@Override
public void accept(Disposable disposable) {
showLoading("Fetching KYC Info...");
}
}
)
.subscribe(
new Consumer<ApiResponse<FetchKycInfo.Res>>() {
@Override
public void accept(ApiResponse<FetchKycInfo.Res> resApiResponse)
throws Exception {
handleFetchKycInfoRes(resApiResponse.getResponseData());
hideLoading();
}
},
new Consumer<Throwable>() {
@SuppressWarnings("RedundantCast")
@Override
public void accept(Throwable throwable) {
throwable.printStackTrace();
hideLoading();
if (throwable instanceof ApiException) {
showToast(((ApiException) throwable).getMessage());
matchStatusTv.setText(((ApiException) throwable).getMessage());
} else {
showToast(throwable.getMessage());
matchStatusTv.setText(throwable.getMessage());
}
finish();
}
}
);
}
7.2 Decoding Base64 Encoded fetchKYCInfo API Response
-----------------------------------------------------
API response data contains Base64 Encoded kyc_info block that contains actual Kyc data elements.Hence you need to decode the response data to get kyc_info block.
private void handleFetchKycInfoRes(FetchKycInfo.Res apiResponse)
throws Exception {
String hash = calculateResponseHash(
getIntent().getStringExtra("client_code"),
getIntent().getStringExtra("user_id"),
apiResponse.getKycInfo(),
getIntent().getStringExtra("api_key"),
getIntent().getStringExtra("salt")
);
Log.i(
TAG,
"handleFetchKycInfoRes: Api hash = "
+ apiResponse.getHash() + ", Calculated hash = " + hash + "."
);
if (hash != null && !hash.equalsIgnoreCase(apiResponse.getHash())) {
throw new IllegalArgumentException("Invalid hash!");
}
byte[] decodedResponse = Base64.decode(apiResponse.getKycInfo(), Base64.DEFAULT);
if (apiResponse.getEncrypted().equalsIgnoreCase("yes")) {
showPasswordDialog(decodedResponse);
} else {
refreshUi(decodedResponse);
}
}
7.3 POJO Class fetchKYCInfo API Response
class Res {
private String encrypted;
@SerializedName("kyc_info")
private String kycInfo;
private String hash;
public String getEncrypted() {
return encrypted;
}
public String getKycInfo() {
return kycInfo;
}
public String getHash() {
return hash;
}
public static class KycInfo {
private Photo photo;
@SerializedName("original_kyc_info")
private KycData originalKycData;
@SerializedName("declared_kyc_info")
private KycData declaredKycData;
@SerializedName("doc_image")
private Document document;
@SerializedName("verified_agent")
private VerifiedAgent verifiedAgent;
public Photo getPhoto() {
return photo;
}
public KycData getOriginalKycData() {
return originalKycData;
}
public KycData getDeclaredKycData() {
return declaredKycData;
}
public Document getDocument() {
return document;
}
public VerifiedAgent getVerifiedAgent() {
return verifiedAgent;
}
}
public enum VerifiedAgentStatus {
PENDING, APPROVED, REJECTED, NA
}
public static class VerifiedAgent {
@SerializedName("status")
public VerifiedAgentStatus status;
public VerifiedAgentStatus getStatus() {
return status;
}
}
public static class Document {
public String pan;
@SerializedName("aadhaar_front")
public String aadhaarFront;
@SerializedName("aadhaar_back")
public String aadhaarBack;
@SerializedName("passport_front")
public String passportFront;
@SerializedName("passport_back")
public String passportBack;
@SerializedName("voter_id_front")
public String voterIdFront;
@SerializedName("voter_id_back")
public String voterIdBack;
public String getPan() {
return pan;
}
public String getAadhaarFront() {
return aadhaarFront;
}
public String getAadhaarBack() {
return aadhaarBack;
}
public String getPassportFront() {
return passportFront;
}
public String getPassportBack() {
return passportBack;
}
public String getVoterIdFront() {
return voterIdFront;
}
public String getVoterIdBack() {
return voterIdBack;
}
}
public enum MatchStatus {
SUCCESS, UNCERTAIN, FAIL
}
public static class Photo {
@SerializedName("document_image")
private String docFace;
@SerializedName("live_image")
private String liveFace;
@SerializedName("match_rate")
private Double matchRate;
@SerializedName("match_status")
private MatchStatus matchStatus;
public String getDocFace() {
return docFace;
}
public String getLiveFace() {
return liveFace;
}
public Double getMatchRate() {
return matchRate;
}
public MatchStatus getMatchStatus() {
return matchStatus;
}
}
public static class KycData {
private String name;
@SerializedName("father_name")
private String fatherName;
@SerializedName("mother_name")
private String motherName;
private String gender;
private String dob;
private String email;
private String mobile;
private String address;
@SerializedName("doc_type")
private String docType;
@SerializedName("document_id")
private String documentId;
@SerializedName("verified_by")
private String verifiedBy;
@SerializedName("verified_using")
private String verifiedUsing;
@SerializedName("document_verification_status")
private String documentVerificationStatus;
@SerializedName("document_verification_code")
private String documentVerificationCode;
@SerializedName("document_verification_message")
private String documentVerificationMessage;
public String getDocumentVerificationCode() {
return documentVerificationCode;
}
public String getDocumentVerificationMessage() {
return documentVerificationMessage;
}
public String getName() {
return name;
}
public String getFatherName() {
return fatherName;
}
public String getMotherName() {
return motherName;
}
public String getGender() {
return gender;
}
public String getDob() {
return dob;
}
public String getEmail() {
return email;
}
public String getMobile() {
return mobile;
}
public String getAddress() {
return address;
}
public String getDocType() {
return docType;
}
public String getDocumentId() {
return documentId;
}
public String getVerifiedBy() {
return verifiedBy;
}
public String getVerifiedUsing() {
return verifiedUsing;
}
public String getDocumentVerificationStatus() {
return documentVerificationStatus;
}
}
}
Technical Specification Extension
1. Introduction
This document is an extension of the Technical Specification document. This contains following add-on functionalities that are being offered as part of video-id-kyc solution.
- executeAadhaarXMLService
This API offers following functionality:
Download aadhaar xml/zip file obtained from UIDAI as a part of video-id-kyc.
Explicitly delete aadhaar xml/zip file obtained from UIDAI as a part of video-id-kyc
2. executeAadhaarXMLService API
Post processing of VideoIdKyc SDK processing,this api can be used to download or delete aadhaar xml/zip.
Note :
This Api is relevant only in case the user had chosen Offline Aadhaar XML Option for completing the Kyc processing.
2.1 API Request Details
API Name : executeAadhaarXMLService
Method : POST
Sandbox URL :
https://sandbox.veri5digital.com/video-id-kyc/api/1.0/executeAadhaarXMLService
Production URL :
https://prod.veri5digital.com/video-id-kyc/api/1.0/executeAadhaarXMLService
2.1.1 executeAadhaarXMLService API Request Details
--------------------------------------------------
Calling application should form the Request Payload in the following Json format.This should be passed as http Request Body.
{
"headers": {
"client_code": "<<your client_code>>",
"sub_client_code": "<<your sub client code>>",
"actor_type": "NA",
"channel_code": "SDK",
"stan": "<<System Trace Number>>",
"user_handle_type": "",
"user_handle_value": "",
"location": "",
"transmission_datetime": "<<System Time In MilliSeconds">>,
"run_mode": "REAL",
"client_ip": "<<Your IP Address>>",
"operation_mode": "SELF",
"channel_version": "",
"function_code": "<<can be DOWNLOAD or NO_ACTION>>",
"function_sub_code": "<<can be DELETE or NO_ACTION>>"
},
"request": {
"api_key": "<api-key>",
"request_id": "<<Unique Request Id Passed by client>>",
"user_id": "<<received in response from android-sdk>>",
"hash": "<<Generated Hash>>"
}
}
Table : executeAadhaarXMLService API Request Body Parameters
Please refer below table for details of Request Field information.
Field Name | Type | Comments | Example |
---|---|---|---|
client_code | String, Mandatory | Unique code assigned to a client.This will be assigned to the client during onboarding. | client123 |
sub_client_code | String, Not Mandatory | Its useful in case calling client needs any customization w.r.to different departments. | subclient123 |
actor_type | String, Not Mandatory | Type of user who is making the request from the application. | CUSTOMER or AGENT |
channel_code | String, Not Mandatory | Channel code used by the client to initiate request. | ANDROID_SDK |
stan | String, Mandatory | A unique number generated per request by the client. It is used to uniquely identify a request from a client and can be used for troubleshooting.Max length can be 64 characters long. | 98321892319 |
user_handle_type | String, Not Mandatory | Indicates the type of the user identifier, who has initiated the request. Eg : UUID, MSISDN, EMAIL. | |
user_handle_value | String, Not Mandatory | Value of user_handle_type. If user_handle_type is EMAIL, then user_handle_value is actual email. | a@b.com |
location | String, Not Mandatory | Location of the device that initiated the request. Can be empty. | |
transmission_datetime | String, Not Mandatory | Time in System milli seconds at which request was initiated from client. | 1553255080298 |
run_mode | String, Not Mandatory | Execution Mode in which transaction is being done.Possible values are REAL or TRIAL. | REAL |
client_ip | String, Not Mandatory | IP Address of the client device. | |
operation_mode | String, Not Mandatory | The way in which the transaction is executed.Example values can be “SELF”,”ASSISTED”,”SYSTEM” etc. | SELF |
channel_version | String, Not Mandatory | It can be Android SDK Version | 3.1.7 |
function_code | String, Mandatory | Defines the behaviour of this api.Value should be ‘REVISED’ | REVISED |
function_sub_code | String, Mandatory | Indicates default behaviour for this api. If no customized behaviour requirements, then value will be “DEFAULT” | DEFAULT |
user_id | String, Mandatory | This is same as user_id passed on to client application as part of SDK response. | userid123 |
request_id | String, Conditional | Client generated unique Id for referencing the transaction that is passed while invoking SDK. Refer Section 2.1.1.2 for the usage. | requestid123 |
user_id | String, Conditional | This is the unique user identifier returned by SDK post successful processing. Refer Section 2.1.1.2 for the usage. | userid123 |
hash | String, Mandatory | Hash to be calculated by client to verify integrity of request. Please refer APPENDIX A to see how to generate the hash. | |
api_key | String, Mandatory | api_key shared with client during onboarding. | samplekey123 |
2.1.1.1 Function Code & Function Sub Code Map
Table : Action Matrix Based on function_code and function_sub_code
function_code | function_sub_code | Action |
---|---|---|
DOWNLOAD | NO_ACTION | Aadhaar XML Zip File for the requested user_id will be downloaded |
DOWNLOAD | DELETE | Aadhaar XML Zip File for the requested user_id will be downloaded and deleted from server. |
NO_ACTION | DELETE | Aadhaar XML Zip File for the requested user_id will be deleted from server. |
2.1.1.2 request Id & user_id Usage
Table : Action Matrix Based on function_code and function_sub_code
request_id | user_id | Action |
---|---|---|
PRESENT | NOT PRESENT | Aadhaar XML Zip File for the request_id will be returned |
NOT PRESENT | PRESENT | Aadhaar XML Zip File for the given user_id will be downloaded. |
PRESENT | PRESENT | Aadhaar XML Zip File for the given user_id will be downloaded. Please note user_id will get preference and error will be returned if user_id does not exist. |
2.1.2 executeAadhaarXMLService API Response Details
Response data contains processed KYC information in “response_data” tag.Response Data is comprised of doc_format, doc_content and password.
doc_format can be XML/ZIP
doc_content contains base64 encoded string which should be decoded and saved as a file for further processing based on doc_format field.
password field contains the password to open the file ZIP file if it is password protected.
{
“response_status”:{
“status” : ”<<status of API Execution>>”,
“code” : ”<<status code of API Execution>>”,
“message” :”<<message based on the status and code>>”
},
“response_data”:{
“doc_format”:”<<ZIP or XML>>”,
“doc_content”:”<<base64 encoded document>>”
“password”:”<<password to open zip file>>”
},
}
Note - Refer Appendix B for possible code and messages in response_status.
- If you delete the data before the download, than doc_content and password fields will have value “DELETED”.
Appendix A: Hash generation
It is essential that we have a definitive protocol to verify the integrity of all the communication between Khosla Labs Platform and Client.
So for every request coming to KL, client has to supply a hash which KL will use as the first step of verification.
In return, all responses will also contain hash supplied by KL Platform.
Hash should be calculated using the following method: hash=SHA256(Hash-Sequence)
executeAadhaarXMLService :
<client_code>
|<request_id>
|<user_id>
|<api_key>
|<salt>
|<function_code>
|<function>
_sub_code>
If your
client_code=”a1b2c3”,
api_key=”123”,
request_id=”1234567890101112”,
user_id =””,
salt=”e1d2c3b4a”,
function_code=”DOWNLOAD”,
function_sub_code=”DELETE”,
then
Hash-Sequence=
a1b2c3|1234567890101112||123|e1d2c3b4a|DOWNLOAD|DELETE
hash =SHA-256(Hash-Sequence)
For validation:
Receiving end should calculate hash based on request parameters and match it against the received hash.
If receivedHash=calculatedHash, then only you should proceed with your application logic.
api_key and salt are the key parameters here. It is known only to the client and Khosla Labs.
One api_key is mapped to one salt. This allows us to have multiple salts active at a time.
api_key and hash need to be passed in each API call. Salt is never transmitted in any API call.
Sample Code Snippet _init Request Hash Calculation
private String calculateHash(String clientCode, String requestId, String apiKey, String salt)
throws NoSuchAlgorithmException {
MessageDigest digest;
digest = MessageDigest.getInstance("SHA-256");
if (digest != null) {
byte[] hash = digest.digest((clientCode + "|" + requestId + "|" + apiKey + "|" + salt).getBytes());
return bytesToHex(hash);
}
return null;
}
private final static char[] hexArray = "0123456789ABCDEF".toCharArray();
public static String bytesToHex(byte[] bytes) {
char[] hexChars = new char[bytes.length * 2];
for (int j = 0; j < bytes.length; j++) {
int v = bytes[j] & 0xFF;
hexChars[j * 2] = hexArray[v >>> 4];
hexChars[j * 2 + 1] = hexArray[v & 0x0F];
}
return new String(hexChars);}
Appendix B: Error Codes
Error Code | Message |
---|---|
000 | Success |
380145 | please provide either request_id or user_id |
380146 | hash is mandatory |
380147 | api_key is mandatory |
380148 | function_code is mandatory |
380149 | function_sub_code is mandatory |
380150 | Invalid function_code |
380151 | Invalid function_sub_code |
380152 | user_id doesn't exist |
380153 | Required details not found |
380154 | Required details not available |
380155 | request_id doesn't exist |
380048 | Invalid api_key |
380049 | Invalid client_code |
380091 | Hash validation failed. |
Appendix C : Sample Postman Request Response
Method : POST
Sandbox URL :
https://sandbox.veri5digital.com/video-id-kyc/api/1.0/executeAadhaarXMLService
- Production URL :
https://prod.veri5digital.com/video-id-kyc/api/1.0/executeAadhaarXMLService
- HTTP Request Header:
Content-Type : application/json
- HTTP Request Body:
{
"headers":{
"client_code":"your-code",
"sub_client_code":"your-code",
"actor_type":"NA",
"channel_code":"ANDROID_SDK",
"stan":"stanwedfsdfqaaaawerqwer",
"user_handle_type":"EMAIL",
"user_handle_value":"abs@gmail.com",
"location":"",
"transmission_datetime":"1533123525716",
"run_mode":"REAL",
"client_ip":"",
"operation_mode":"SELF",
"channel_version":"0.0.1",
"function_code":"DOWNLOAD",
"function_sub_code":"DELETE"
},
"request":{
"request_id":"dummy-codea-1564137837052",
"api_key":"your-api-key1",
"hash":"dd02c40fe612066bbdff72a0e93200a08866380e726dc31dd15806898cf541de",
"user_id":""
}
}
- Response Payload :
{
"response_status": {
"status ": "SUCCESS ",
"code": "000",
"message": "document details fetched successfully"
},
"response_data": {
"doc_format ": "ZIP ",
"doc_content": "...encoded document string.... >> ",
"password": "3962"
}
}