KYC JAAK para Android
En este apartado explicaremos cómo hacer una implementación de KYC JAAK para API. Antes de comenzar es necesario haber consultado la sección KYC JAAK ya que será necesario contar con un Short Key.
Repositorio de ejemplo
Contamos con un ejemplo en nuestro repositorio GitHub donde podrás ver un ejemplo de la implementación KYC JAAK para Android.https://github.com/jaak-ai/jaak-android-kyc-example
Requisitos
Para comenzar con el desarrollo en Android utilizando Jaak KYC SDK, sigue estos pasos para configurar correctamente tu entorno:
Librerias compatibles de SDK 30 al 35
Nivel de API | AGP | Kotlin | Core KTX | KSP | KAPT (Migrado a KSP) | Hilt | Coroutines Core | Coroutines Android | Coroutines Play Services | Lifecycle Runtime KTX | OkHttp | Retrofit | AppCompat | Material Components | ConstraintLayout | Activity KTX | Core SplashScreen | ML Kit Face Detection | CameraX | Play Services Location | Light Compressor |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
30 | 7.1.3 | 1.7.20 | 1.8.0 | 1.7.20-1.0.29 | N/A | 2.39 | 1.6.2 | 1.6.2 | 1.6.2 | 2.5.0 | 4.9.3 | 2.9.0 | 1.5.1 | 1.6.1 | 2.1.1 | 1.5.1 | 1.0.0 | 16.6.0 | 1.2.1 | 19.3.0 | 0.9.2 |
31 | 7.2.0 | 1.8.0 | 1.11.0 | 1.8.0-1.0.29 | N/A | 2.40 | 1.6.4 | 1.6.4 | 1.6.4 | 2.5.1 | 4.9.3 | 2.9.0 | 1.5.2 | 1.6.2 | 2.1.2 | 1.5.2 | 1.0.0 | 16.7.0 | 1.2.1 | 19.4.0 | 0.9.3 |
32 | 7.2.2 | 1.8.0 | 1.12.0 | 1.8.0-1.0.29 | N/A | 2.36 | 1.6.4 | 1.6.4 | 1.6.4 | 2.5.0 | 4.9.3 | 2.9.0 | 1.5.0 | 1.7.0 | 2.1.2 | 1.5.0 | 1.0.0 | 16.4.0 | 1.3.0 | 20.2.0 | 0.9.0 |
33 | 8.0.2 | 1.9.0 | 1.13.0 | 1.9.0-1.0.30 | N/A | 2.40 | 1.7.0 | 1.7.0 | 1.7.0 | 2.6.0 | 5.0.0 | 2.9.0 | 1.6.0 | 1.9.0 | 2.2.0 | 1.6.0 | 1.0.0 | 17.0.0 | 1.3.0 | 20.3.0 | 1.0.0 |
34 | 8.5.0 | 2.0.0 | 1.14.0 | 2.0.0-1.0.31 | N/A | 2.50 | 1.7.5 | 1.7.5 | 1.7.5 | 2.7.0 | 5.0.0 | 2.9.0 | 1.6.1 | 1.10.0 | 2.2.1 | 1.7.0 | 1.0.0 | 17.0.1 | 1.4.0 | 21.0.0 | 1.1.0 |
35 | 8.7.3 | 2.1.10 | 1.15.0 | 2.1.10-1.0.31 | N/A | 2.51.1 | 1.8.0 | 1.8.0 | 1.8.0 | 2.8.7 | 5.0.0 | 2.9.0 | 1.7.0 | 1.12.0 | 2.2.1 | 1.10.1 | 1.0.1 | 17.1.0 | 1.4.2 | 21.3.0 | 1.2.0e |
Requisitos Previos
- Android Studio: Usar la versión recomendada de Meerkat.
- Compilación del SDK: Establecer el SDK de compilación en 35.
- Compatibilidad de Source y Target: Utilizar Java 11.
- minSdkVersion: Configurar en 24.
- targetSdkVersion: Configurar en 35.
Creación del Proyecto Android con configuración kotlin (default)
-
Abrir Android Studio.
-
Seleccionar Nuevo Proyecto y elegir una plantilla adecuada (por ejemplo, Empty Activity).
-
Configurar el nombre de la aplicación y asegurarse de que el lenguaje sea Kotlin.

- Configurar los valores de compilación en Gradle Scripts:
En libs.versions.toml se integran las versiones que se explicaron al inicio del documento.
[versions]
agp = "8.7.3"
kotlin = "2.1.10"
coreKtx = "1.15.0"
kspVersion = "2.1.10-1.0.31"
hiltVersion = "2.51.1"
kotlinxCoroutinesAndroid = "1.8.0"
kotlinxCoroutinesAndroidVersion = "1.8.0"
kotlinxCoroutinesCore = "1.6.0"
kotlinxCoroutinesPlayServices = "1.8.0"
lifecycleRuntimeKtx = "2.8.7"
lifecycleRuntimeKtxVersion = "2.8.7"
okhttp = "5.0.0-alpha.2"
retrofit = "2.9.0"
appcompat = "1.7.0"
material = "1.12.0"
constraintLayout = "2.2.1"
activityKtx = "1.10.1"
coreSplashscreen = "1.0.1"
jaakDocumentDetector = "2.0.5"
jaakFaceDetector = "2.0.5"
mlkitFaceDetection = "17.1.0"
cameraX = "1.4.2"
playServicesLocation = "21.3.0"
lightCompressor = "1.2.0"
[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
androidx-lifecycle-runtime-ktx-v241 = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtxVersion" }
androidx-lifecycle-viewmodel-ktx = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "lifecycleRuntimeKtx" }
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" }
kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinxCoroutinesAndroid" }
kotlinx-coroutines-android-v160 = {module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinxCoroutinesAndroidVersion" }
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinxCoroutinesCore" }
kotlinx-coroutines-play-services = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-play-services", version.ref = "kotlinxCoroutinesPlayServices" }
hilt-android = { group = "com.google.dagger", name = "hilt-android" , version.ref = "hiltVersion"}
hilt-compiler = { group = "com.google.dagger", name = "hilt-compiler" , version.ref = "hiltVersion"}
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
google-material = { group = "com.google.android.material", name = "material", version.ref = "material" }
androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintLayout" }
androidx-activity-ktx = { group = "androidx.activity", name = "activity-ktx", version.ref = "activityKtx" }
androidx-core-splashscreen = { module = "androidx.core:core-splashscreen", version.ref = "coreSplashscreen" }
jaak-document-detector = { module = "com.jaak.documentdetectorsdk:jaakdocumentdetector-sdk", version.ref = "jaakDocumentDetector" }
jaak-face-detector = { module = "com.jaak.facedetectorsdk:jaakfacedetector-sdk", version.ref = "jaakFaceDetector" }
google-mlkit-face-detection = { module = "com.google.android.gms:play-services-mlkit-face-detection", version.ref = "mlkitFaceDetection" }
androidx-camera-camera2 = { module = "androidx.camera:camera-camera2", version.ref = "cameraX" }
androidx-camera-lifecycle = { module = "androidx.camera:camera-lifecycle", version.ref = "cameraX" }
androidx-camera-core = { module = "androidx.camera:camera-core", version.ref = "cameraX" }
androidx-camera-view = { module = "androidx.camera:camera-view", version.ref = "cameraX" }
androidx-camera-video = { module = "androidx.camera:camera-video", version.ref = "cameraX" }
google-play-services-location = { module = "com.google.android.gms:play-services-location", version.ref = "playServicesLocation" }
light-compressor = { module = "com.github.AbedElazizShe:LightCompressor", version.ref = "lightCompressor" }
retrofit = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" }
retrofit-converter-gson = { group = "com.squareup.retrofit2", name = "converter-gson", version.ref = "retrofit" }
okhttp = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = "okhttp" }
logging-interceptor = { group = "com.squareup.okhttp3", name = "logging-interceptor", version.ref = "okhttp" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
kotlinAndroidKsp = { id = "com.google.devtools.ksp", version.ref ="kspVersion" }
hiltAndroid = { id = "com.google.dagger.hilt.android", version.ref ="hiltVersion" }
En build.gradle (Project), hacemos la referencia de plugin android, kotlin, hiltAndroid y kotlinAndroidKSP:
plugins {
alias(libs.plugins.android.application) apply false
alias(libs.plugins.kotlin.android) apply false
alias(libs.plugins.hiltAndroid) apply false
alias(libs.plugins.kotlinAndroidKsp) apply false
}
En settings.gradle se agrega los maven url faltantes en dependencyResolutionManagement :
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
maven {
url 'https://us-maven.pkg.dev/jaak-platform/jaak-android'
}
maven { url 'https://jitpack.io' }
maven { url 'https://maven.microblink.com' }
}
}
Por ultimo en build.gradle (Module: app), configurar el SDK y la compatibilidad y librerías:
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlinAndroidKsp)
alias(libs.plugins.hiltAndroid)
}
android {
compileSdk 35
defaultConfig {
minSdk 24
targetSdk 35
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = '11'
}
buildFeatures{
viewBinding = true
}
}
dependencies {
// Core Android dependencies
implementation libs.androidx.core.ktx
implementation libs.androidx.lifecycle.runtime.ktx
// Coroutine Lifecycle Scopes
implementation libs.androidx.lifecycle.viewmodel.ktx
implementation libs.androidx.lifecycle.runtime.ktx.v241
// Coroutines
implementation libs.kotlinx.coroutines.core
implementation libs.kotlinx.coroutines.android
implementation libs.kotlinx.coroutines.core
implementation libs.kotlinx.coroutines.android.v160
implementation libs.kotlinx.coroutines.play.services
implementation(libs.hilt.android)
ksp(libs.hilt.compiler)
implementation libs.androidx.appcompat
implementation libs.google.material
implementation libs.androidx.constraintlayout
implementation libs.androidx.activity.ktx
implementation libs.androidx.lifecycle.viewmodel.ktx
implementation libs.androidx.core.splashscreen
implementation(libs.jaak.document.detector)
implementation(libs.jaak.face.detector)
implementation(libs.google.mlkit.face.detection)
implementation(libs.androidx.camera.camera2)
implementation(libs.androidx.camera.lifecycle)
implementation(libs.androidx.camera.core)
implementation(libs.androidx.camera.view)
implementation(libs.androidx.camera.video)
implementation(libs.google.play.services.location)
implementation(libs.light.compressor)
// Retrofit
implementation(libs.retrofit)
implementation(libs.retrofit.converter.gson)
// OkHttp
implementation(libs.okhttp)
implementation(libs.logging.interceptor)
}
- Hacer clic en Sync Now para aplicar los cambios.
Creación del Proyecto Android con configuración Groovy DSL
-
Abrir Android Studio.
-
Seleccionar Nuevo Proyecto y elegir una plantilla adecuada (por ejemplo, Empty Activity).
-
Configurar el nombre de la aplicación y asegurarse de que el lenguaje sea Kotlin.
-
Configurar los valores de compilación en Gradle Scripts:
En build.gradle (Project), configurar la versión de Gradle y el Plugin:
dependencies { classpath 'com.android.tools.build:gradle:8.3.2' }
En build.gradle (Module: app), configurar el SDK y la compatibilidad:
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-all.zip
En build.gradle (Module: app), configurar el SDK y la compatibilidad:
android { compileSdk 33 defaultConfig { minSdk 24 targetSdk 33 } compileOptions { sourceCompatibility JavaVersion.VERSION_18 targetCompatibility JavaVersion.VERSION_18 } }
-
Hacer clic en Sync Now para aplicar los cambios.
Integración de Retrofit para Llamados a Servicios
Todas las llamadas API que realizaremos a continuación será mediante la arquitectura REST (Representational State Transfer), con el estándar web HTTP (HyperText Transfer Protocol) y firmando la comunicación con el formato JSON (JavaScript Object Notation).
Recomendamos tener un conocimiento por lo menos básico de estos 3 conceptos para poder continuar con esta guía.
Ahora usaremos Retrofit, una librería popular para consumo de APIs en Android.
- Agregar Dependencias:
- Abrir el archivo build.gradle (Module: app).
- Agregar las dependencias necesarias para Retrofit y Gson:
- Configuración Kotlin DSL
[versions] okhttp = "5.0.0-alpha.2" retrofit = "2.9.0" [libraries] retrofit = { group = "com.squareup.retrofit2", name = "retrofit", version.ref = "retrofit" } retrofit-converter-gson = { group = "com.squareup.retrofit2", name = "converter-gson", version.ref = "retrofit" } okhttp = { group = "com.squareup.okhttp3", name = "okhttp", version.ref = "okhttp" } logging-interceptor = { group = "com.squareup.okhttp3", name = "logging-interceptor", version.ref = "okhttp" }
// Retrofit implementation(libs.retrofit) implementation(libs.retrofit.converter.gson) // OkHttp implementation(libs.okhttp) implementation(libs.logging.interceptor)
- Configuracion Groovy DSL
dependencies { implementation 'com.squareup.retrofit2:retrofit:2.9.0' implementation 'com.squareup.retrofit2:converter-gson:2.9.0' implementation 'com.squareup.okhttp3:logging-interceptor:4.9.3' }
- Configurar el Cliente HTTP:
- Crear una interfaz con las definiciones de las peticiones.
- Implementar una instancia de Retrofit con OkHttpClient.
- Uso de la API:
- Crear una instancia del servicio y realizar peticiones en una coroutine o CallAdapter.
El código de ejemplo para esta implementación se encuentra disponible en el repositorio Jaak Android KYC Example que se menciona al principio de este documento.
Intercambio de Short Key por Token de Sesión de KYC
Un Short Key es un identificador único de una Sesión KYC. Para poder realizar llamadas a los Servicios JAAK que forman parte de una Sesión KYC, es fundamental autenticarse y autorizar dichas llamadas conociendo el origen de la sesión. Para ello, se utiliza un Token de Sesión KYC, el cual podemos obtener a partir del Short Key.
Para obtener un Token de Sesión KYC a partir de un Short Key debemos hacer la siguiente llamada API:
URL
POST https://sandbox.api.jaak.ai/api/v1/kyc/session
@POST("api/v1/kyc/session")
suspend fun sessionApi(@Header("Short-Key") shortKey: String,
@Header("Origin-Device") originDevice: String
): Response<SessionResponse>
Header
Short-Key: String
Origin-Device String
Parámetro | Tipo | Requerido | Descripción |
---|---|---|---|
Short-Key | string | Si | Short Key de la Sesión KYC de la cual queremos obtener un Token de Sesión KYC. |
Origin-Device | string | Si | Identificador desde donde se realizará la Sesión KYC para esta guía seria API |
Request
{}
Response
{
"accessToken": String,
"step": Integer,
"sessionId": String,
"assets": {
"document": null | String,
"liveness": null | String,
},
"document": String
}
data class SessionResponse(
@SerializedName("accessToken") val accessToken: String,
@SerializedName("step") val step: Int,
@SerializedName("sessionId") val sessionId: String,
@SerializedName("assets") val assets: SessionAssets,
@SerializedName("document") val document: String
)
data class SessionAssets(
@SerializedName("document") val document: String?,
@SerializedName("liveness") val liveness: String?
)
Campo | Tipo | Descripción |
---|---|---|
accessToken | string | Token de Sesión KYC creado a partir de Short Key proporcionado. |
step | integer | Número entero que representa el paso actual del proceso. |
sessionId | string | Identificador único de la sesión. |
assets | string | Contiene información sobre los activos asociados al proceso. |
document | string | Código del documento procesado (ejemplo: "MEX" para México). |
Objeto assets
Campo | Tipo | Descripción |
---|---|---|
document | string | Información del documento asociado (puede ser null o un valor). |
liveness | string | Información sobre la prueba de vida (puede ser null o un valor). |
Todas las siguientes llamadas API deben ser autenticadas mediante una cabecera denominada "Authorization", cuyo valor debe ser "Bearer token-sesion-kyc".
El cual puedes obtenerlo del la propiedad accessToken.
Una vez hecho el intercambio del Short Key y obtenido el Token de Sesión KYC, ya podemos hacer uso de los Servicios JAAK asignados para realizar un proceso KYC.
Servicios JAAK
El proceso KYC JAAK para API requiere de una serie de servicios esenciales, diseñados para verificar la identidad de una persona de manera detallada. Cada uno de los Servicios JAAK mencionado a continuación desempeña un papel fundamental para garantizar que el proceso sea seguro, preciso y cumpla con las normativas legales.
- Geolocalización
- Verificación de documento
- Extracción de datos del documento
- Verificación de datos en lista negra
- Verificación de vida
- Verificación de la titularidad del documento
Todos los servicios proporcionados por JAAK son vitales para fortalecer la seguridad del proceso KYC y garantizar su fiabilidad. Además, aseguran el cumplimiento de las normativas internacionales, protegiendo la identidad y los documentos de los clientes durante todo el proceso.
A continuación se detallan cada uno de estos servicios:
1. Geolocalización
El servicio de Geolocalización de JAAK se utiliza para registrar la ubicación (latitud y longitud) donde se lleva a cabo la Sesión KYC. Es importante tener en cuenta que la ubicación es recolectada desde el dispositivo que utiliza el usuario final para realizar la Sesión KYC.
Agrega los permisos en el AndroidManifest.xml
Debes solicitar los permisos adecuados para acceder a la ubicación:
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
Si la app necesita obtener la ubicación en segundo plano, también agrega:
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
Agrega la dependencia de Google Play Services en el build.gradle de app.
dependencies {
implementation 'com.google.android.gms:play-services-location:21.0.1'
}
Código para obtener la ubicación en Kotlin
Crea una función para obtener la ubicación del usuario usando FusedLocationProviderClient.
import android.Manifest
import android.annotation.SuppressLint
import android.content.pm.PackageManager
import android.os.Bundle
import android.util.Log
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import com.google.android.gms.location.*
class MainActivity : AppCompatActivity() {
private lateinit var fusedLocationClient: FusedLocationProviderClient
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
checkLocationPermission()
}
private fun checkLocationPermission() {
when {
ContextCompat.checkSelfPermission(
this, Manifest.permission.ACCESS_FINE_LOCATION
) == PackageManager.PERMISSION_GRANTED -> {
getLastKnownLocation()
}
else -> {
requestPermissionLauncher.launch(Manifest.permission.ACCESS_FINE_LOCATION)
}
}
}
private val requestPermissionLauncher = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { isGranted: Boolean ->
if (isGranted) {
getLastKnownLocation()
} else {
Log.e("Location", "Permiso denegado")
}
}
@SuppressLint("MissingPermission")
private fun getLastKnownLocation() {
fusedLocationClient.lastLocation.addOnSuccessListener { location ->
if (location != null) {
Log.d("Location", "Latitud: ${location.latitude}, Longitud: ${location.longitude}")
} else {
Log.e("Location", "No se pudo obtener la ubicación")
}
}
}
}
Realiza la siguiente llamada API para registrar la ubicación del dispositivo en la Sesión KYC.
URL
POST https://sandbox.api.jaak.ai/api/v1/kyc/session/location
@POST("api/v1/kyc/session/location")
suspend fun locationApi(@Body request: LocationRequest): Response<LocationResponse>
Header
Authorization: Bearer token-session-kyc
Campo | Tipo | Requerido | Descripción |
---|---|---|---|
Authorization | string | Si | En esta cabecera se debe enviar el Token de Sesión KYC que se ha obtenido del intercambiado con de Short Key |
Request
{
"latitude": Double,
"longitude": Double
}
data class LocationRequest(
@SerializedName("latitude") val latitude: Double,
@SerializedName("longitude") val longitude: Double
)
Campo | Tipo | Requerido | Descripción |
---|---|---|---|
latitude | double | Si | Latitud de la posición obtenida por el dispositivo y que se registrara en la Sesión KYC. |
longitude | double | Si | Longitud de la posición obtenida por el dispositivo y que se registrara en la Sesión KYC. |
Response
{}
El servicio de Geolocalización no tiene un impacto directo en los resultados finales de la sesión, pero es una herramienta útil para la recopilación de datos y en algunos casos, como el financiero, necesaria para llevar a cabo sus procesos.
Continuemos con el siguiente paso...
2. Verificación de documento
La etapa de verificación de documento tiene como objetivo principal validar la autenticidad de los documentos presentados por el usuario, como identificaciones oficiales y así prevenir el fraude con documentos falsos, alterados o que no corresponden a un documento legítimo.
En algunas ocasiones existen documentos que tienen anverso y reverso, de ahí dependerá si se captura una o dos imágenes por documento para usar este servicio, estas imágenes de dichos documentos deben ser convertidas a Base64 antes de ser enviadas al servicio. Por lo tanto, es fundamental realizar el proceso de codificación de imagen para asegurar su correcta transmisión y procesamiento.
Contamos con un componente especializado en la captura de imágenes de documentos para su procesamiento en las siguientes API: Document-Detector SDK.
Puedes encontrar más información en la documentación oficial:
Realiza la siguiente llamada API enviando las imágenes o imagen del documento.
URL
POST https://sandbox.api.jaak.ai/api/v3/document/verify
@POST("api/v3/document/verify")
suspend fun verifyApi(@Header("Authorization") auth: String,
@Body request: VerifyRequest): Response<VerifyResponse>
Header
Authorization: Bearer token-session-kyc
Campo | Tipo | Requerido | Descripción |
---|---|---|---|
Authorization | string | Si | En esta cabecera se debe enviar el Token de Sesión KYC que se ha obtenido del intercambiado con de Short Key |
Request
{
"imageFront": String,
"imageBack": String,
"dataVerification": Boolean
}
data class VerifyRequest(
@SerializedName("imageFront") val document: String, //base64
@SerializedName("imageBack") val document2: String?, //base64
@SerializedName("dataVerification") val dataVerification: Boolean
)
Campo | Tipo | Requerido | Descripción |
---|---|---|---|
imageFront | string | Sí | Imagen del anverso del documento en formato Base64. Es obligatorio proporcionar esta imagen para la verificación. |
imageBack | string | Opcional | Imagen del reverso del documento en formato Base64. Debe incluirse si el documento tiene una contraparte posterior. |
dataVerification | boolean | Opcional | Por defecto false. Si se establece en true, la API realizará una comparación de los datos del documento con la base de datos de RENAPO (solo disponible para documentos emitidos en México). |
Response
{
"eventId": String,
"requestId": String,
"evaluation": String,
"document": {
"type": String,
"side": String,
"country": String,
"icaoCode": String
},
"state": {
"documentCompleteSides": Boolean,
"dataConsistent": Boolean,
"documentLiveness": Boolean,
"securityFeatures": Boolean,
"documentValidity": Boolean,
"imageQuality": Boolean,
"handPresence": Boolean,
"message": String,
"validations": {
"namesMatch": Boolean,
"lastNameMatch": Boolean,
"birthDateMatch": Boolean,
"personalIdNumberMatch": Boolean,
"message": String
},
"photoForgery": Boolean
},
"processTime": Integer
}
data class VeifyResponse(
@SerializedName("document") val document: Document,
@SerializedName("documentType") val documentType: Int,
@SerializedName("eventId") val eventId: String,
@SerializedName("processTime") val processTime: Long,
@SerializedName("requestId") val requestId: String,
@SerializedName("state") val state: State
)
data class Document(
@SerializedName("country") val country: String,
@SerializedName("icaoCode") val icaoCode: String,
@SerializedName("side") val side: String,
@SerializedName("type") val type: String,
@SerializedName("evaluation") val evaluation: String
)
data class State(
@SerializedName("dataConsistent") val dataConsistent: Boolean,
@SerializedName("documentCompleteSides") val documentCompleteSides: Boolean,
@SerializedName("documentLiveness") val documentLiveness: Boolean,
@SerializedName("documentValidity") val documentValidity: Boolean,
@SerializedName("handPresence") val handPresence: Boolean,
@SerializedName("imageQuality") val imageQuality: Boolean,
@SerializedName("message") val message: String,
@SerializedName("securityFeatures") val securityFeatures: Boolean
)
Campo | Tipo | Descripción |
---|---|---|
eventId | string | ID para trazabilidad de los detalles del evento. |
requestId | string | ID para trazabilidad de la petición. |
evaluation | string | Resultado de la evaluación del documento. |
document | object | Información del documento analizado. |
state | object | Resultados de diferentes verificaciones sobre el documento. |
processTime | integer | Tiempo de procesamiento de la verificación en milisegundos. |
Objeto document
Campo | Tipo | Descripción |
---|---|---|
type | string | Tipo de documento <------(INCLUIR LISTA).------> |
side | string | Indica si el documento tiene ambos lados o solo uno. |
country | string | País donde se emite el documento. |
icaoCode | string | Código ICAO asociado al documento.( Descargar lista ) |
Objeto state
Campo | Tipo | Descripción |
---|---|---|
documentCompleteSides | boolean | Indica si el documento ha sido capturado completamente por todos los lados requeridos. |
dataConsistent | boolean | Valida si la información contenida en el documento es coherente y no presenta inconsistencias. |
documentLiveness | boolean | Verifica que el documento sea una identificación física y no una imagen impresa, fotocopia o una fotografía mostrada en una pantalla digital. |
securityFeatures | boolean | Determina si el documento cumple con los elementos de seguridad esperados, como sellos holográficos, logotipos, tipografía y otras características antifraude. |
documentValidity | boolean | Indica si el documento está vigente y no ha expirado. |
imageQuality | boolean | Evalúa la calidad de la imagen del documento para garantizar una verificación precisa. |
handPresence | boolean | Detecta si hay manos sosteniendo la identificación durante la captura de la imagen. |
photoForgery | boolean | Indica si la imagen del documento ha sido manipulada digitalmente con herramientas de edición. |
validations | object | Validaciones especificas de documentos mexicanos |
message | string | Proporciona detalles adicionales en caso de errores o discrepancias con los resultados esperados. |
Objeto state.validations
Campo | Tipo | Descripción |
---|---|---|
namesMatch | boolean | Verifica si el nombre extraído del documento coincide con el nombre asociado al número de identificación personal (CURP en el caso de México) según la base de datos de RENAPO. |
lastNameMatch | boolean | Comprueba si el apellido extraído del documento coincide con el apellido asociado al número de identificación personal (CURP en el caso de México) según la base de datos de RENAPO. |
birthDateMatch | boolean | Indica si la fecha de nacimiento extraída del documento coincide con la registrada en la base de datos de RENAPO para el número de identificación personal (CURP en el caso de México). |
personalIdNumberMatch | boolean | Valida si el número de identificación personal extraído del documento coincide con el CURP en la base de datos de RENAPO. |
message | string | Mensaje que proporciona información adicional en caso de discrepancia en las validaciones. |
El objeto state, ubicado dentro del Response, muestra el estado de verificación del documento a lo largo del proceso de validación. Este objeto incluye un grupo de propiedades que indican si el documento cumple o no con los requisitos de autenticidad, calidad y consistencia.
JAAK recomienda que todas las propiedades de verificación se completen con éxito para una mayor seguridad, aunque esto no es obligatorio por lo tanto puedes seleccionar solo un conjunto de propiedades según sus necesidades. Algunos ejemplos incluyen:
- Aceptar la verificación si todas las validaciones son exitosas.
- Solicitar una nueva captura si la calidad de la imagen es baja o faltan elementos claves.
- Rechazar la verificación si se detectan manipulaciones o inconsistencias graves en los datos.
Cada propiedad dentro de state juega un papel específico en la verificación del documento y pueden ser usadas para tomar decisiones y ser integradas en su sistema de verificación.
La propiedad dataVerification dentro de Request, cuando se establece como true, proporciona validaciones adicionales sobre los datos de los documentos mexicanos. Estas validaciones, que se encuentran en state.validations de Response, se realizan exclusivamente para documentos mexicanos a través de entidades oficiales como RENAPO. Para identificaciones de otras nacionalidades, todas las validaciones serán false debido a la incompatibilidad del servicio con estos documentos.
Después de verificar y validar el documento, se puede proceder a la extracción de datos en el siguiente paso, es importante recordar que las mismas imagenes enviadas en este paso se usaran en el siguiente para buscar tener la mayor coherencia en la Sesión KYC.
3. Extracción de datos del documento
El objetivo de la extracción de datos, es capturar y organizar la información del documento para facilitar su análisis y almacenamiento. Esta información se utilizará posteriormente en los siguientes pasos como la verificación de la titularidad del documento y la búsqueda de datos en listas negras.
Para asegurar la coherencia y eficiencia del proceso, es esencial que uses las mismas imágenes del documento tanto en el paso de verificación del documento como en este paso. Recuerda que la codificación de la imagen a Base64 es crucial antes de enviarlas al servicio para garantizar su correcta transmisión y procesamiento.
Contamos con un componente especializado en la captura de imágenes de documentos para su procesamiento en las siguientes API: Document-Detector SDK.
Puedes encontrar más información en la documentación oficial:
Realiza la siguiente llamada API enviando las imágenes o imagen del documento.
URL
POST https://sandbox.api.jaak.ai/api/v4/document/extract
@POST("api/v3/document/extract-both")
suspend fun documentExtraBothApi(@Header("Authorization") auth: String,
@Body request: DocumentExtraBothRequest): Response<DocumentExtraBothResponse>
Header
Authorization: Bearer token-session-kyc
Campo | Tipo | Requerido | Descripción |
---|---|---|---|
Authorization | string | Sí | En esta cabecera se debe enviar el Token de Sesión KYC que se ha obtenido del intercambiado con de Short Key |
Request
{
"imageFront": String,
"imageBack": String,
"documentSessionSelected": {
"country": String
}
}
data class DocumentExtraBothRequest(
@SerializedName("imageFront") val documentFront: String, //base64
@SerializedName("imageBack") val documentBack: String //base64
@SerializedName("documentSessionSelected") val documentSessionSelected: DocumentSessionSelected
)
data class DocumentSessionSelected(
@SerializedName("country") val country: String,
)
Campo | Tipo | Requerido | Descripción |
---|---|---|---|
imageFront | string | Sí | Imagen del documento en formato Base64. Es obligatorio proporcionar esta imagen para la verificación. |
imageBack | string | opcional | Imagen de la parte trasera del documento en formato Base64. Debe incluirse si el documento tiene una contraparte posterior. |
Response
{
"eventId": String,
"requestId": String,
"status": String,
"processingTime": String,
"state": {
"message": String,
"documentCompleteSides": Boolean
},
"content": {
"data": {
"personal": {
"firstName": String,
"secondName": String,
"surname": String,
"motherSurname": String,
"fullName": String,
"sex": String,
"dateOfBirth": String,
"face": String,
"placeOfBirth": String,
"nationality": String,
"maritalStatus": String,
"extra": {
"ocr": String,
"registerYear": String,
"rfc": String
}
},
"address": {
"fullAddress": String,
"postalCode": String,
"extra": {
"street": String,
"externalNumber": String,
"internalNumber": String,
"neighborhood": String,
"city": String,
"state": String
}
},
"document": {
"type": String,
"side": String,
"country": {
"name": String,
"isoAlpha3Code": String,
"isoAlpha2Code": String,
"icaoCode": String
},
"number": String,
"personalIdNumber": String,
"additionalNumber": String,
"dateOfIssue": String,
"expiration": {
"date": String,
"isPermanent": Boolean
},
"issuingAuthority": String,
"extra": {}
}
}
}
}
data class DocumentExtraBothResponse(
@SerializedName("eventId") val eventId: String,
@SerializedName("requestId") val requestId: String,
@SerializedName("status") val status: Boolean,
@SerializedName("message") val message: String,
@SerializedName("documentType") val documentType: DocumentTypeExtraBoth,
@SerializedName("documentData") val documentData: DocumentDataExtraBoth,
@SerializedName("documentMetadata") val documentMetadata: String,
@SerializedName("processingTime") val processingTime: String, // milliseconds
@SerializedName("state") val state: DocumentStateExtraBoth
)
data class DocumentTypeExtraBoth(
@SerializedName("type") val type: String,
@SerializedName("side") val side: String,
@SerializedName("country") val country: String,
@SerializedName("icaoCode") val icaoCode: String
)
data class DocumentDataExtraBoth(
@SerializedName("generalData") val generalData: GeneralDataExtraBoth,
@SerializedName("mechanicalReadingZone") val mechanicalReadingZone: String,
@SerializedName("specificData") val specificData: List<SpecificDataExtraBoth>
)
data class GeneralDataExtraBoth(
@SerializedName("name") val name: String,
@SerializedName("birthdate") val birthdate: String, // yyyy-mm-dd
@SerializedName("gender") val gender: String,
@SerializedName("nationality") val nationality: String,
@SerializedName("documentNumber") val documentNumber: String
)
data class SpecificDataExtraBoth(
@SerializedName("field") val field: String?,
@SerializedName("value") val value: String?
)
data class DocumentStateExtraBoth(
@SerializedName("message") val message: String,
@SerializedName("isExpired") val isExpired: Boolean,
@SerializedName("isUnderAge") val isUnderAge: Boolean,
@SerializedName("supportedDocument") val supportedDocument: Boolean
)
Campo | Tipo | Descripción |
---|---|---|
eventId | string | ID para trazabilidad de la petición. |
requestId | string | ID para trazabilidad de la petición. |
status | string | Estado del procesamiento |
processingTime | string | Tiempo de procesamiento de la verificación en milisegundos. |
state | Object | Estado detallado del procesamiento de extracción |
content | Object | Contiene la información del contenido extraído del documento |
Objeto state
Campo | Tipo | Descripción |
---|---|---|
documentCompleteSides | boolean | Indica si el documento ha sido capturado completamente por todos los lados requeridos. |
message | string | Proporciona detalles adicionales en caso de errores o discrepancias con los resultados esperados. |
Objeto content
Campo | Tipo | Descripción |
---|---|---|
data | Object | Contiene la información del usuario extraída del documento (ver sub-tablas) |
Objeto content.data
Campo | Tipo | Descripción |
---|---|---|
personal | Objeto | Contiene la información de la persona extraída del documento |
address | Objeto | Contiene la información de la dirección extraída del documento |
document | Objeto | Contiene la información del documento extraída |
Objeto content.data.personal
Campo | Tipo | Descripción |
---|---|---|
firstName | string | Primer nombre del usuario Se usa para el servicio de listas negras |
secondName | string | Segundo nombre del usuario (puede estar vacío).Se usa para el servicio de listas negras |
surname | string | Apellido paterno del usuario.Se usa para el servicio de listas negras |
motherSurname | string | Apellido materno del usuario.Se usa para el servicio de listas negras |
fullName | string | Nombre completo del usuario. |
sex | string | Género del usuario (H para hombre, M para mujer). |
dateOfBirth | string | Fecha de nacimiento en formato DD/MM/AAAA. |
face | string | Imagen de la cara del usuario en formato base64. Usa esta imagen para el servicio de One To One |
placeOfBirth | string | Lugar de nacimiento (puede estar vacío). |
nationality | string | Nacionalidad del usuario.Se usa para el servicio de listas negras |
maritalStatus | string | Estado civil (puede estar vacío). |
extra | Object | Información adicional obtenida del documento |
extra.ocr | string | Número OCR del documento(puede estar vacío).Se usa para el servicio de listas negras |
extra.registerYear | string | Año de registro del documento(puede estar vacío). |
extra.rfc | string | Registro Federal de Contribuyentes (RFC) del usuario(puede estar vacío)(Documentos mexicanos)Se usa para el servicio de listas negras |
Objeto content.data.address
Campo | Tipo | Descripción |
---|---|---|
fullAddress | string | Dirección completa del usuario en formato texto. |
postalCode | string | Código postal del domicilio. |
extra.street | string | Calle del domicilio. |
extra.externalNumber | string | Número exterior del domicilio. |
extra.internalNumber | string | Número interior del domicilio (puede estar vacío). |
extra.neighborhood | string | Género del usuario (H para hombre, M para mujer). |
extra.city | string | Ciudad de residencia. |
extra.state | string | Entidad estatal de residencia. |
Objeto content.data.document
Campo | Tipo | Descripción |
---|---|---|
type | string | Primer nombre del usuario |
side | string | Lados del documento procesados (BOTH para ambos lados). |
country | Object | Información del país emisor |
number | string | Número del documento de identidad. |
personalIdNumber | string | Número de identificación personal asociado al usuario.Se usa para el servicio de listas negras |
additionalNumber | string | Número de identificación adicional del documento(puede estar vacío).Se usa para el servicio de listas negras |
dateOfIssue | string | Fecha de emisión del documento. |
expiration | Object | Datos de expiración del documento |
issuingAuthority | string | Autoridad emisora del documento (puede estar vacío). |
extra | Object | Información adicional obtenida del documento(puede estar vacía). |
Objeto content.data.document.country
Campo | Tipo | Descripción |
---|---|---|
name | string | Nombre del país emisor. |
isoAlpha3Code | string | Código ISO Alpha-3 del país |
isoAlpha2Code | string | Código ISO Alpha-2 del país |
icaoCode | string | Código ICAO del documento |
Objeto content.data.document.expiration
Campo | Tipo | Descripción |
---|---|---|
date | string | Fecha de vencimiento del documento. |
isPermanent | boolean | Indica si el documento es permanente |
El objeto state, ubicado dentro del Response, muestra el estado de la extracción del documento. Este objeto incluye un grupo de propiedades que indican si el documento cumple o no con los requisitos de la extracción.
JAAK recomienda que todas las propiedades de extracción se completen con éxito para una mayor seguridad, aunque esto no es obligatorio por lo tanto puedes seleccionar solo un conjunto de propiedades según sus necesidades. Algunos ejemplos incluyen:
- Pedir la captura del lado faltante o contraparte del documento en caso de ser requerido.
- Solicitar una nueva captura si existe un error dentro la extracción de los datos del documento.
De los datos extraídos del documento en esta etapa, hay algunos que serán utilizados en los siguientes pasos del proceso. En la sección Verificación en listas negras los datos que se necesitan son:
Verificación del servicio INE en listas negras
- content.data.document.additionalNumber (CIC)
- content.data.personal.extra.ocr (OCR)
Verificación del servicio INTERPOL en listas negras
- content.data.personal.name
- content.data.personal.secondName
- content.data.personal.sureName
- content.data.personal.motherSurname
Verificación del servicio OFAC en listas negras
- content.data.personal.name
- content.data.personal.secondName
- content.data.personal.sureName
- content.data.personal.motherSurname
Verificación del servicio RENAPO en listas negras
- content.data.document.personalIdNumber (CURP)
Verificación del servicio SAT en listas negras
- content.data.personal.name
- content.data.personal.secondName
- content.data.personal.sureName
- content.data.personal.motherSurname
- content.data.personal.extra.rfc
En la sección Verificación de la titularidad del documento, el dato necesario obtenido de esta extracción es:
- content.data.personal.face
Una vez extraídos e identificados los datos del documento los utilizaremos en los siguientes pasos.
4. Verificación de datos en listas negras
Una vez extraídos los datos del documento en el paso anterior, procedemos a verificar esta información contra diversos servicios de listas negras como: Interpol y OFAC. Este paso es crucial para asegurar que el cliente no esté relacionado con actividades ilícitas ni vinculado a personas o entidades de alto riesgo. A su vez, hay información que sí deben ser encontrados en otras listas, que no necesariamente son catalogadas como listas negras, como: INE, RENAPO y SAT.
Los servicios de listas negras a los que se tiene acceso en este paso son: INE, Interpol, OFAC, RENAPO y SAT.
En esta etapa, puedes buscar en diferentes servicios de listas negras, pero solo uno a la vez. Cada servicio requiere información específica. La firma para usar estos servicios es la misma para todos, pero los datos que debes enviar varían según la lista que consultes. Después de la explicación de la URL y sus parámetros que se muestra a continuación, encontrarás una serie de tablas que aclaran qué campos debes enviar por servicio y cómo relacionarlos con la información obtenida en el paso anterior.
URL
POST https://sandbox.api.jaak.ai/api/v2/blacklist/investigate
@POST("api/v2/blacklist/investigate")
suspend fun livenessVerifyApi(@Header("Authorization") auth: String,
@Body request: InvestigateRequest): Response<InvestigateResponse>
Header
Authorization: Bearer token-session-kyc
Campo | Tipo | Requerido | Descripción |
---|---|---|---|
Authorization | string | Sí | En esta cabecera se debe enviar el Token de Sesión KYC que se ha obtenido del intercambiado con de Short Key |
Request
{
"services": {
"ine": Boolean,
"interpol": Boolean,
"ofac": Boolean,
"renapo": {
"curp": Boolean
},
"sat": {
"sat69b": Boolean
},
"cdc": {
"rccFico": Boolean
}
},
"payload": {
"person": {
"name": String,
"secondName": String,
"lastName": String,
"secondLastName": String,
"birthDate": String,
"nationality": String,
"additionalLastName": String
},
"address": {
"address": String,
"neighborhood": String,
"municipality": String,
"city": String,
"state": String,
"postalCode": String
},
"identifications": {
"curp": String,
"rfc": String,
"socialSecurityNumber": String,
"electorKey": String,
"ine": {
"cic": String,
"ocr": String
}
},
"extras": {
"commonId": String,
"wantedIn": String
}
}
}
data class InvestigateRequest(
@SerializedName("services") val services: Services,
@SerializedName("payload") val payload: Payload
)
data class Services(
@SerializedName("ine") val ine: Boolean,
@SerializedName("interpol") val interpol: Boolean,
@SerializedName("ofac") val ofac: Boolean,
@SerializedName("renapo") val renapo: Renapo,
@SerializedName("sat") val sat: Sat,
@SerializedName("cdc") val cdc: Cdc
)
data class Renapo(
@SerializedName("curp") val curp: Boolean
)
data class Sat(
@SerializedName("sat69b") val sat69b: Boolean
)
data class Cdc(
@SerializedName("rccFico") val rccFico: Boolean
)
data class Payload(
@SerializedName("person") val person: Person,
@SerializedName("address") val address: Address,
@SerializedName("identifications") val identifications: Identifications,
@SerializedName("extras") val extras: Extras
)
data class Person(
@SerializedName("name") val name: String,
@SerializedName("secondName") val secondName: String,
@SerializedName("lastName") val lastName: String,
@SerializedName("secondLastName") val secondLastName: String,
@SerializedName("birthDate") val birthDate: String,
@SerializedName("nationality") val nationality: String,
@SerializedName("additionalLastName") val additionalLastName: String
)
data class Address(
@SerializedName("address") val address: String,
@SerializedName("neighborhood") val neighborhood: String,
@SerializedName("municipality") val municipality: String,
@SerializedName("city") val city: String,
@SerializedName("state") val state: String,
@SerializedName("postalCode") val postalCode: String
)
data class Identifications(
@SerializedName("curp") val curp: String,
@SerializedName("rfc") val rfc: String,
@SerializedName("socialSecurityNumber") val socialSecurityNumber: String,
@SerializedName("electorKey") val electorKey: String,
@SerializedName("ine") val ine: Ine
)
data class Ine(
@SerializedName("cic") val cic: String,
@SerializedName("ocr") val ocr: String
)
data class Extras(
@SerializedName("commonId") val commonId: String,
@SerializedName("wantedIn") val wantedIn: String
)
Campo | Tipo | Descripción |
---|---|---|
services | Objeto | Contiene los distintos servicios de listas negras en los que es posible realizar una verificación (ver sub-tablas) |
payload | Objeto | Contiene la información a buscar del servicio elegido (ver sub-tablas) |
Objeto services
Campo | tipo | Descripción |
---|---|---|
ine | boolean | Realiza verificación en Instituto Nacional Electoral (INE). |
interpol | boolean | Realiza verificación en Interpol. |
ofac | boolean | Realiza verificación en Oficina de Control de Activos Extranjeros (OFAC). |
renapo.curp | boolean | Realiza verificación en Registro Nacional de Población (RENAPO). |
sat.sat69b | boolean | Realiza verificación en Servicio de Administración Tributaria (SAT). |
cdc.rccFico | boolean | Realiza verificación en Centro de Datos de Crédito (CDC). |
Objeto payload
Campo | Tipo | Descripción |
---|---|---|
payload.person | Objeto | Contiene la información de respuesta (ver sub-tablas) |
payload.address | Objeto | Contiene la información de respuesta (ver sub-tablas) |
payload.identifications | Objeto | Contiene la información de respuesta (ver sub-tablas) |
payload.extras | Objeto | Contiene la información de respuesta (ver sub-tablas) |
Objeto payload.person
Campo | tipo | Descripción |
---|---|---|
name | string | Primer nombre de la persona a buscar. |
secondName | string | Segundo nombre de la persona a buscar. |
lastName | string | Primer apellido de la persona a buscar. |
secondLastName | string | Segundo apellido de la persona a buscar. |
birthDate | string | Fecha de nacimiento de la persona a buscar (Formato: YYYY-MM-DD ). |
nationality | string | Nacionalidad del la persona a buscar. |
additionalLastName | string | Apellido adicional de la persona a buscar. |
Objeto payload.address
Campo | tipo | Descripción |
---|---|---|
address | string | Calle y número de la dirección de la persona a buscar. |
neighborhood | string | Colonia o barrio del usuario de la persona a buscar. |
municipality | string | Municipio o delegación de la persona a buscar. |
city | string | Ciudad de residencia de la persona a buscar. |
state | string | Estado de residencia de la persona a buscar. |
postalCode | string | Código postal de la dirección de la persona a buscar. |
Objeto payload.identifications
Campo | tipo | Descripción |
---|---|---|
curp | string | Clave Única de Registro de Población (CURP) de la persona a buscar. |
rfc | string | Registro Federal de Contribuyentes (RFC) de la persona a buscar. |
socialSecurityNumber | string | Número de Seguridad Social (NSS) de la persona a buscar. |
electorKey | string | Clave de Elector del INE de la persona a buscar. |
ine | Object | Objeto con datos de la credencial del INE de la persona a buscar. (ver sub-tablas) |
Objeto payload.identifications.ine
Campo | Tipo | Descripción |
---|---|---|
cic | string | Código de Identificación de la Credencial INE del documento a buscar. |
ocr | string | Número Oficial de Identificación (OCR) del documento a buscar. |
Objeto payload.extras
Campo | tipo | Descripción |
---|---|---|
commonId | string | Identificador común del usuario (puede ser ID interno del sistema) de la persona a buscar. |
wantedIn | string | Código del país donde se busca al usuario ("MEX" para México) de la persona a buscar. |
Response
{
"eventId": String,
"responseId": String,
"processTime": Double,
"organization": String,
"service": String,
"result": {},
"state": {
"foundInService": Bolean,
"mustBeFound": Boolean,
"message": String
}
}
data class InvestigateResponse(
@SerializedName("eventId") val eventId: String,
@SerializedName("responseId") val responseId: String,
@SerializedName("processTime") val processTime: Double,
@SerializedName("organization") val organization: String,
@SerializedName("service") val service: String,
@SerializedName("result") val result: Any?, // Puede ser null o un objeto, depende del uso
@SerializedName("state") val state: InvestigationState
)
data class InvestigationState(
@SerializedName("message") val message: String,
@SerializedName("foundInService") val foundInService: Boolean,
@SerializedName("mustBeFound") val mustBeFound: Boolean
)
Campo | Tipo | Descripción |
---|---|---|
eventId | string | ID para trazabilidad de los detalles del evento. |
responseId | string | ID de la respuesta. |
organization | string | La entidad gubernamental en la cual se apoya la consulta |
service | string | Es el servicio de la entidad en el cual se realiza la consulta |
processTime | double | Tiempo de procesamiento de la consulta del servicio |
result | object | Objeto con los datos de la persona encontrada si fuera el caso |
state | object | Son estados en el que te puedes apoyar para saber si fue encontrado la persona |
Objeto state
Campo | Tipo | Descripción |
---|---|---|
foundInService | boolean | Indica si la persona fue encontrada en el servicio o no. |
mustBeFound | boolean | Define si se espera encontrar la persona en el servicio para que el proceso sea exitoso o no. Por ejemplo: para OFAC el no encontrar a una persona en el servicio es un caso exitoso, por el contrario en INE si se encuentra a una persona es considerado como un caso exitoso. |
message | string | Mensaje referente a la búsqueda de la persona en servicio |
A continuación, se exhibirán una serie de tablas organizadas por servicio de listas. En estas tablas, se detallarán los campos necesarios para cada lista y su correspondencia con los datos extraídos del paso anterior.
Para hacer la verificación mediante el servicio INE deberás enviar los siguientes datos:
Campo enviado en la llamada API | Requerido | Dato extraído del documento en el paso anterior |
---|---|---|
payload.identifications.ine.cic | Si | content.data.document.additionalNumber |
payload.identifications.ine.ocr | Si | content.data.personal.extra.ocr |
Para hacer la verificación mediante el servicio INTERPOL deberás enviar los siguientes datos:
Campo enviado en la llamada API | Requerido | Dato extraído del documento en el paso anterior |
---|---|---|
payload.person.name | Si | content.data.personal.name |
payload.person.secondName | No | content.data.personal.secondName |
payload.person.lastName | Si | content.data.personal.sureName |
payload.person.secondLastName | No | content.data.personal.motherSurname |
Para hacer la verificación mediante el servicio OFAC deberás enviar los siguientes datos:
Campo enviado en la llamada API | Requerido | Dato extraído del documento en el paso anterior |
---|---|---|
payload.person.name | Si | content.data.personal.name |
payload.person.secondName | No | content.data.personal.secondName |
payload.person.lastName | Si | content.data.personal.sureName |
payload.person.secondLastName | No | content.data.personal.motherSurname |
Para hacer la verificación mediante el servicio RENAPO deberás enviar los siguientes datos:
Campo enviado en la llamada API | Requerido | Dato extraído del documento en el paso anterior |
---|---|---|
payload.identifications.curp | Si | content.data.document.personalIdNumber |
Para hacer la verificación mediante el servicio SAT deberás enviar los siguientes datos:
Campo enviado en la llamada API | Requerido | Dato extraído del documento en el paso anterior |
---|---|---|
payload.person.name | Si | content.data.personal.name |
payload.person.secondName | No | content.data.personal.secondName |
payload.person.lastName | Si | content.data.personal.sureName |
payload.person.secondLastName | No | content.data.personal.motherSurname |
payload.identifications.rfc | Si | content.data.personal.extra.rfc |
A continuación, te mostraremos un ejemplo de una consulta al servicio de RENAPO
Request
{
"services": {
"ine": false,
"interpool": false,
"ofac": false,
"renapo": {
"curp": true
},
"sat": {
"sat69b": false
},
"cdc": {
"rccFico": false
}
},
"payload": {
"identifications": {
"curp": "aqui va la curp"
}
}
}
Response
{
"eventId": "749f88b9-ecce-4f4c-920a-f8ece706501a",
"responseId": "",
"processTime": 0.23123526573181152,
"organization": "curp",
"service": "validaCurp",
"result": null,
"state": {
"message": "Investigation completed successfully",
"foundInService": false,
"mustBeFound": true
}
}
Como se muestra en el ejemplo anterior, se realizó una consulta al servicio de RENAPO. En la solicitud, podemos observar que solo se activó un servicio, services.renapo.curp, y que únicamente se enviaron los campos necesarios para llevar a cabo la búsqueda de dicho servicio.
El campo state dentro del Response debería haber indicado que se encontró a una persona para que la búsqueda se considere exitosa. Sin embargo, el campo foundInService muestra que no se encontró a la persona.
En resumen, al contrastar los datos extraídos del documento con diversas listas negras, garantizamos la protección tanto de la empresa como de los usuarios. Este proceso asegura la integridad y el cumplimiento del sistema, ayudando a prevenir fraudes.
Una vez verificado los datos del documento en las listas negras continuemos con los siguientes pasos.
5. Verificación de vida
El objetivo de la verificación de vida o liveness es garantizar que la persona que está realizando el proceso de verificación de identidad es un ser humano real y no una imagen, un video o un intento de fraude. Esta etapa se realiza para prevenir el uso de fotografías o videos falsificados y proteger la integridad del proceso KYC.
Durante esta etapa, se lleva a cabo una validación biométrica para confirmar que la persona que está interactuando con el sistema es realmente quien dice ser. Utilizando tecnologías avanzadas de detección facial, esta verificación asegura que solo personas reales puedan completar el proceso de identificación.
Para utilizar este paso, se requiere un video de 5 segundos con el rostro de la persona a validar. Es esencial que la persona no lleve accesorios y que el video tenga buena iluminación.
Este video debe ser convertido a Base64 antes de ser enviado al servicio. Por lo tanto, es fundamental realizar el proceso de codificación de video para asegurar su correcta transmisión y procesamiento.
Contamos con un componente especializado en la captura de imágenes de verificación de la titularidad para su procesamiento en las siguientes API: Face-Detector SDK.
Puedes encontrar más información en la documentación oficial:
Realiza la siguiente llamada API para realizar el proceso de verificación de vida.
URL
POST https://sandbox.api.jaak.ai/api/v1/liveness/verify-and-bestframe
@POST("api/v1/liveness/verify-and-bestframe")
suspend fun livenessVerifyApi(@Header("Authorization") auth: String,
@Body request: LivenessVerifyRequest): Response<LivenessVerifyResponse>
Header
Authorization: Bearer token-session-kyc
Campo | Tipo | Requerido | Descripción |
---|---|---|---|
Authorization | string | Sí | En esta cabecera se debe enviar el Token de Sesión KYC que se ha obtenido del intercambiado con de Short Key |
Request
{
"video": String
}
data class LivenessVerifyBestframeRequest(
@SerializedName("video") val video: String, //base64
)
Campo | Tipo | Descipción |
---|---|---|
video | string | Video de la persona enviado en formato Base64 |
Response
{
"eventId": String,
"requestId": String,
"processTime": String,
"score": Double,
"bestFrame": String,
"state": {
"isRealPerson": Boolean,
"message": String
}
}
data class LivenessVerifyResponse(
@SerializedName("eventId") val eventId: String,
@SerializedName("requestId") val requestId: String,
@SerializedName("processTime") val processTime: Long,
@SerializedName("score") val score: Double,
@SerializedName("bestFrame") val bestFrame: String, // base64 encoded string
@SerializedName("state") val state: LivenessState
)
data class LivenessState(
@SerializedName("isRealPerson") val isRealPerson: Boolean,
@SerializedName("message") val message: String
)
Campo | Tipo | Descripción |
---|---|---|
eventId | string | ID para trazabilidad de los detalles del evento. |
requestId | string | ID para trazabilidad de la petición. |
processTime | string | Tiempo de procesamiento de la verificación en milisegundos. |
score | double | Resultados de diferentes verificaciones sobre el procesamiento del video. |
bestFrame | string | Detecta el mejor fotograma del video y genera una imagen en formato Base64 |
state | Objecto | Estado detallado del procesamiento de verificación de vida. |
Objeto state
Campo | Tipo | Descripción |
---|---|---|
isRealPerson | boolean | Esta bandera representa si la persona en el video es real y aprueba la verificación de vida. |
message | string | Proporciona detalles adicionales en caso de errores o discrepancias con los resultados esperados. |
El valor true de la propiedad state.isRealPerson, dentro del Response de la llamada de prueba de vida, confirma que la verificación fue exitosa y no hubo ningún ataque.
Además, la misma respuesta nos devuelve una propiedad llamada bestFrame, que se utiliza para obtener el mejor fotograma del video, es decir, en el que aparece la persona con mejor posición, iluminación, etc. Este fotograma se utilizará en el siguiente paso.
6. Verificación de la titularidad del documento
La etapa final requiere una comparación entre dos imágenes para verificar que la persona que realiza el proceso KYC es la titular del documento. Se comparará la imagen del obtenida (bestFrame) durante el paso de verificación de vida con la imagen del rostro extraída del documento (content.data.personal.face) durante el paso de extracción de datos.
Así como en pasos anteriores, las imágenes deben estar en Base64 antes de ser enviadas al servicio. Por lo tanto, es fundamental realizar la verificación de las imagenes para asegurar su correcta transmisión y procesamiento.
URL
POST https://sandbox.api.jaak.ai/api/v2/oto/verify
@POST("api/v2/oto/verify")
suspend fun otoVerifyApi(@Header("Authorization") auth: String,
@Body request: OtoVerifyRequest): Response<OtoVerifyResponse>
Request
{
"eventId": String,
"image1": String,
"image2": String
}
data class OtoVerifyV2Request(
@SerializedName("eventId") val eventId: String, //base64
@SerializedName("image1") val image1: String, //base64
@SerializedName("image2") val image2: String, //base64
)
Campo | Tipo | Descripción |
---|---|---|
eventId | string | (opcional) es la posibilidad de enviar un identificador unico para poder realizar un seguimiento de esta llamada |
image1 | string | La imagen 1 en Base64 para la comparación |
image2 | string | La imagen 2 en Base64 para la comparación |
Response
{
"eventId": String,
"requestId": String,
"score": Double,
"distance": Double,
"process_time": Integer,
"metadata": {
"image1": {
"accessories": {
"glass": Boolean,
"sunGlass": Boolean,
"hat": Boolean,
"mask": Boolean
},
"image_quality": {
"isCorrectBrightness": Boolean,
"isCorrectBlur": Boolean,
"isCorrectHeight": Boolean,
"isCorrectWidth": Boolean,
"isCorrectResolution": Boolean,
"isCorrectVerticalRotation": Boolean,
"isCorrectHorizontalRotation": Boolean,
"isCorrectRotation": Boolean,
"isCorrectSizeFace": Boolean,
"isCorrectNumberFaces": Boolean
}
},
"image2": {
"accessories": {
"glass": Boolean,
"sunGlass": Boolean,
"hat": Boolean,
"mask": Boolean
},
"image_quality": {
"isCorrectBrightness": Boolean,
"isCorrectBlur": Boolean,
"isCorrectHeight": Boolean,
"isCorrectWidth": Boolean,
"isCorrectResolution": Boolean,
"isCorrectVerticalRotation": Boolean,
"isCorrectHorizontalRotation": Boolean,
"isCorrectRotation": Boolean,
"isCorrectSizeFace": Boolean,
"isCorrectNumberFaces": Boolean
}
}
},
"codes": null,
"state": {
"rejectedBadQuality": Boolean,
"rejectedAccessories": Boolean,
"isSamePerson": Boolean,
"message": String
}
}
data class OtoVerifyV2Response(
@SerializedName("eventId") val eventId: String,
@SerializedName("requestId") val requestId: String,
@SerializedName("score") val score: Double,
@SerializedName("distance") val distance: Double,
@SerializedName("process_time") val processTime: Long,
@SerializedName("metadata") val metadata: Metadata,
@SerializedName("codes") val codes: List<Int>?,
@SerializedName("state") val state: State
)
data class Metadata(
@SerializedName("image1") val image1: ImageMetadata,
@SerializedName("image2") val image2: ImageMetadata
)
data class ImageMetadata(
@SerializedName("accessories") val accessories: Accessories,
@SerializedName("image_quality") val imageQuality: String?
)
data class Accessories(
@SerializedName("glass") val glass: Boolean,
@SerializedName("sunGlass") val sunGlass: Boolean,
@SerializedName("hat") val hat: Boolean,
@SerializedName("mask") val mask: Boolean
)
data class State(
@SerializedName("rejectedBadQuality") val rejectedBadQuality: Boolean,
@SerializedName("rejectedAccessories") val rejectedAccessories: Boolean,
@SerializedName("isSamePerson") val isSamePerson: Boolean,
@SerializedName("message") val message: String
)
Campo | Tipo | Descripción |
---|---|---|
eventId | string | ID para trazabilidad de los detalles del evento. |
requestId | string | ID para trazabilidad de la petición. |
score | double | Resultado de la evaluación del prueba de vida. |
distance | double | Resultado de la evaluación normalizada del prueba de vida. |
process_time | integer | Tiempo de procesamiento de la verificación en milisegundos. |
metadata | object | Datos adicionales sobre el análisis de la prueba de vida, como la búsqueda de accesorios y calidad de la imagen dentro de las imágenes comparadas |
codes | object | Lista de código de errores referente a calidad de imagen y accesorios encontrados dentro de las imagenes comparadas |
state | object | Resultados de diferentes verificaciones sobre la prueba de vida. |
Objeto state
Campo | Tipo | Descripción |
---|---|---|
rejectedBadQuality | boolean | Bandera que determina si las imagenes no cumple con los minimos estándares de calidad |
rejectedAccessories | boolean | Bandera que determina si las imagenes comparadas detectan accesorios |
isSamePerson | boolean | Bandera que determina si ambas personas en las imagenes son la misma |
message | string | Mensaje que proporciona información adicional en caso de discrepancia en la comparación. |
La propiedad state.isSamePerson nos permite asegurar que la persona que se está identificando en la verificación de vida sea la misma que la del documento proporcionado.
En resumen, si la propiedad state.isSamePerson es aceptada, entonces podemos considerar que la verificación de la titularidad del documento ha sido exitoso.
Es importante que finalices las sesiones KYC. Te explicaremos cómo hacerlo en el siguiente paso.
Finalización de la Sesión KYC
Una vez ejecutados los Servicios JAAK, se debe finalizar la Sesión KYC de manera adecuada mediante la API, esto garantiza la seguridad y un correcto funcionamiento del KYC JAAK.
El proceso de finalización se lleva a cabo mediante una llamada a la siguiente URL. Es esencial incluir el Token de Sesión KYC para informar la Sesión KYC que queremos finalizar.
URL
POST https://sandbox.api.jaak.ai/api/v1/kyc/session/finish
@POST("api/v1/kyc/session/finish")
suspend fun finishApi(@Header("Authorization") auth: String): Response<FinishResponse>
Header
Authorization: Bearer token-session-kyc
Campo | Tipo | Requerido | Descripción |
---|---|---|---|
Authorization | string | Sí | En esta cabecera se debe enviar el Token de Sesión KYC que se ha obtenido del intercambiado con de Short Key |
Request
{}
Response
{}
El estado HTTP 200 confirma que una sesión KYC se ha finalizado con éxito. Cualquier otro código de estado HTTP recibido de la llamada indicaría un error.
¡Excelente!
Si has llegado hasta aquí, ya tienes todo el conocimiento necesario para implementar correctamente un KYC JAAK.
Para poder ver el estado de tus sesiones y visualizarlas, continua con la guía en KYC JAAK donde te explicamos cómo hacerlo.
Updated 1 day ago