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 APIAGPKotlinCore KTXKSPKAPT (Migrado a KSP)HiltCoroutines CoreCoroutines AndroidCoroutines Play ServicesLifecycle Runtime KTXOkHttpRetrofitAppCompatMaterial ComponentsConstraintLayoutActivity KTXCore SplashScreenML Kit Face DetectionCameraXPlay Services LocationLight Compressor
307.1.31.7.201.8.01.7.20-1.0.29N/A2.391.6.21.6.21.6.22.5.04.9.32.9.01.5.11.6.12.1.11.5.11.0.016.6.01.2.119.3.00.9.2
317.2.01.8.01.11.01.8.0-1.0.29N/A2.401.6.41.6.41.6.42.5.14.9.32.9.01.5.21.6.22.1.21.5.21.0.016.7.01.2.119.4.00.9.3
327.2.21.8.01.12.01.8.0-1.0.29N/A2.361.6.41.6.41.6.42.5.04.9.32.9.01.5.01.7.02.1.21.5.01.0.016.4.01.3.020.2.00.9.0
338.0.21.9.01.13.01.9.0-1.0.30N/A2.401.7.01.7.01.7.02.6.05.0.02.9.01.6.01.9.02.2.01.6.01.0.017.0.01.3.020.3.01.0.0
348.5.02.0.01.14.02.0.0-1.0.31N/A2.501.7.51.7.51.7.52.7.05.0.02.9.01.6.11.10.02.2.11.7.01.0.017.0.11.4.021.0.01.1.0
358.7.32.1.101.15.02.1.10-1.0.31N/A2.51.11.8.01.8.01.8.02.8.75.0.02.9.01.7.01.12.02.2.11.10.11.0.117.1.01.4.221.3.01.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)

  1. Abrir Android Studio.

  2. Seleccionar Nuevo Proyecto y elegir una plantilla adecuada (por ejemplo, Empty Activity).


  3. Configurar el nombre de la aplicación y asegurarse de que el lenguaje sea Kotlin.


  1. 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)
}
  1. Hacer clic en Sync Now para aplicar los cambios.

Creación del Proyecto Android con configuración Groovy DSL

  1. Abrir Android Studio.

  2. Seleccionar Nuevo Proyecto y elegir una plantilla adecuada (por ejemplo, Empty Activity).


  3. Configurar el nombre de la aplicación y asegurarse de que el lenguaje sea Kotlin.


  4. 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
        }
    }
  5. 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.

  1. 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'
    }
  2. Configurar el Cliente HTTP:
    • Crear una interfaz con las definiciones de las peticiones.
    • Implementar una instancia de Retrofit con OkHttpClient.
  3. 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ámetroTipoRequeridoDescripción
Short-KeystringSiShort Key de la Sesión KYC de la cual queremos obtener un Token de Sesión KYC.
Origin-DevicestringSiIdentificador 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? 
)
CampoTipoDescripción
accessTokenstringToken de Sesión KYC creado a partir de Short Key proporcionado.
stepintegerNúmero entero que representa el paso actual del proceso.
sessionIdstringIdentificador único de la sesión.
assetsstringContiene información sobre los activos asociados al proceso.
documentstringCódigo del documento procesado (ejemplo: "MEX" para México).

Objeto assets

CampoTipoDescripción
documentstringInformación del documento asociado (puede ser null o un valor).
livenessstringInformació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.

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
CampoTipoRequeridoDescripción
AuthorizationstringSiEn 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
)
CampoTipoRequeridoDescripción
latitudedoubleSiLatitud de la posición obtenida por el dispositivo y que se registrara en la Sesión KYC.
longitudedoubleSiLongitud 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:

Document-Detector SDK


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
CampoTipoRequeridoDescripción
AuthorizationstringSiEn 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
)
CampoTipoRequeridoDescripción
imageFrontstringImagen del anverso del documento en formato Base64. Es obligatorio proporcionar esta imagen para la verificación.
imageBackstringOpcionalImagen del reverso del documento en formato Base64. Debe incluirse si el documento tiene una contraparte posterior.
dataVerificationbooleanOpcionalPor 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
)
CampoTipoDescripción
eventIdstringID para trazabilidad de los detalles del evento.
requestIdstringID para trazabilidad de la petición.
evaluationstringResultado de la evaluación del documento.
documentobjectInformación del documento analizado.
stateobjectResultados de diferentes verificaciones sobre el documento.
processTimeintegerTiempo de procesamiento de la verificación en milisegundos.

Objeto document

CampoTipoDescripción
typestringTipo de documento <------(INCLUIR LISTA).------>
sidestringIndica si el documento tiene ambos lados o solo uno.
countrystringPaís donde se emite el documento.
icaoCodestringCódigo ICAO asociado al documento.( Descargar lista )

Objeto state

CampoTipoDescripción
documentCompleteSidesbooleanIndica si el documento ha sido capturado completamente por todos los lados requeridos.
dataConsistentbooleanValida si la información contenida en el documento es coherente y no presenta inconsistencias.
documentLivenessbooleanVerifica que el documento sea una identificación física y no una imagen impresa, fotocopia o una fotografía mostrada en una pantalla digital.
securityFeaturesbooleanDetermina si el documento cumple con los elementos de seguridad esperados, como sellos holográficos, logotipos, tipografía y otras características antifraude.
documentValiditybooleanIndica si el documento está vigente y no ha expirado.
imageQualitybooleanEvalúa la calidad de la imagen del documento para garantizar una verificación precisa.
handPresencebooleanDetecta si hay manos sosteniendo la identificación durante la captura de la imagen.
photoForgerybooleanIndica si la imagen del documento ha sido manipulada digitalmente con herramientas de edición.
validationsobjectValidaciones especificas de documentos mexicanos
messagestringProporciona detalles adicionales en caso de errores o discrepancias con los resultados esperados.

Objeto state.validations

CampoTipoDescripción
namesMatchbooleanVerifica 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.
lastNameMatchbooleanComprueba 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.
birthDateMatchbooleanIndica 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).
personalIdNumberMatchbooleanValida si el número de identificación personal extraído del documento coincide con el CURP en la base de datos de RENAPO.
messagestringMensaje 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:

Document-Detector SDK


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
CampoTipoRequeridoDescripción
AuthorizationstringEn 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,
)
CampoTipoRequeridoDescripción
imageFrontstringImagen del documento en formato Base64. Es obligatorio proporcionar esta imagen para la verificación.
imageBackstringopcionalImagen 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
)
CampoTipoDescripción
eventIdstringID para trazabilidad de la petición.
requestIdstringID para trazabilidad de la petición.
statusstringEstado del procesamiento
processingTimestringTiempo de procesamiento de la verificación en milisegundos.
stateObjectEstado detallado del procesamiento de extracción
contentObjectContiene la información del contenido extraído del documento

Objeto state

CampoTipoDescripción
documentCompleteSidesbooleanIndica si el documento ha sido capturado completamente por todos los lados requeridos.
messagestringProporciona detalles adicionales en caso de errores o discrepancias con los resultados esperados.

Objeto content

CampoTipoDescripción
dataObjectContiene la información del usuario extraída del documento (ver sub-tablas)

Objeto content.data

CampoTipoDescripción
personalObjetoContiene la información de la persona extraída del documento
addressObjetoContiene la información de la dirección extraída del documento
documentObjetoContiene la información del documento extraída

Objeto content.data.personal

CampoTipoDescripción
firstNamestringPrimer nombre del usuario Se usa para el servicio de listas negras
secondNamestringSegundo nombre del usuario (puede estar vacío).Se usa para el servicio de listas negras
surnamestringApellido paterno del usuario.Se usa para el servicio de listas negras
motherSurnamestringApellido materno del usuario.Se usa para el servicio de listas negras
fullNamestringNombre completo del usuario.
sexstringGénero del usuario (H para hombre, M para mujer).
dateOfBirthstringFecha de nacimiento en formato DD/MM/AAAA.
facestringImagen de la cara del usuario en formato base64. Usa esta imagen para el servicio de One To One
placeOfBirthstringLugar de nacimiento (puede estar vacío).
nationalitystringNacionalidad del usuario.Se usa para el servicio de listas negras
maritalStatusstringEstado civil (puede estar vacío).
extraObjectInformación adicional obtenida del documento
extra.ocrstringNúmero OCR del documento(puede estar vacío).Se usa para el servicio de listas negras
extra.registerYearstringAño de registro del documento(puede estar vacío).
extra.rfcstringRegistro Federal de Contribuyentes (RFC) del usuario(puede estar vacío)(Documentos mexicanos)Se usa para el servicio de listas negras

Objeto content.data.address

CampoTipoDescripción
fullAddressstringDirección completa del usuario en formato texto.
postalCodestringCódigo postal del domicilio.
extra.streetstringCalle del domicilio.
extra.externalNumberstringNúmero exterior del domicilio.
extra.internalNumberstringNúmero interior del domicilio (puede estar vacío).
extra.neighborhoodstringGénero del usuario (H para hombre, M para mujer).
extra.citystringCiudad de residencia.
extra.statestringEntidad estatal de residencia.

Objeto content.data.document

CampoTipoDescripción
typestringPrimer nombre del usuario
sidestringLados del documento procesados (BOTH para ambos lados).
countryObjectInformación del país emisor
numberstringNúmero del documento de identidad.
personalIdNumberstringNúmero de identificación personal asociado al usuario.Se usa para el servicio de listas negras
additionalNumberstringNúmero de identificación adicional del documento(puede estar vacío).Se usa para el servicio de listas negras
dateOfIssuestringFecha de emisión del documento.
expirationObjectDatos de expiración del documento
issuingAuthoritystringAutoridad emisora del documento (puede estar vacío).
extraObjectInformación adicional obtenida del documento(puede estar vacía).

Objeto content.data.document.country

CampoTipoDescripción
namestringNombre del país emisor.
isoAlpha3CodestringCódigo ISO Alpha-3 del país
isoAlpha2CodestringCódigo ISO Alpha-2 del país
icaoCodestringCódigo ICAO del documento

Objeto content.data.document.expiration

CampoTipoDescripción
datestringFecha de vencimiento del documento.
isPermanentbooleanIndica 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
CampoTipoRequeridoDescripción
AuthorizationstringEn 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
)
CampoTipoDescripción
servicesObjetoContiene los distintos servicios de listas negras en los que es posible realizar una verificación (ver sub-tablas)
payloadObjetoContiene la información a buscar del servicio elegido (ver sub-tablas)

Objeto services

CampotipoDescripción
inebooleanRealiza verificación en Instituto Nacional Electoral (INE).
interpolbooleanRealiza verificación en Interpol.
ofacbooleanRealiza verificación en Oficina de Control de Activos Extranjeros (OFAC).
renapo.curpbooleanRealiza verificación en Registro Nacional de Población (RENAPO).
sat.sat69bbooleanRealiza verificación en Servicio de Administración Tributaria (SAT).
cdc.rccFicobooleanRealiza verificación en Centro de Datos de Crédito (CDC).

Objeto payload

CampoTipoDescripción
payload.personObjetoContiene la información de respuesta (ver sub-tablas)
payload.addressObjetoContiene la información de respuesta (ver sub-tablas)
payload.identificationsObjetoContiene la información de respuesta (ver sub-tablas)
payload.extrasObjetoContiene la información de respuesta (ver sub-tablas)

Objeto payload.person

CampotipoDescripción
namestringPrimer nombre de la persona a buscar.
secondNamestringSegundo nombre de la persona a buscar.
lastNamestringPrimer apellido de la persona a buscar.
secondLastNamestringSegundo apellido de la persona a buscar.
birthDatestringFecha de nacimiento de la persona a buscar (Formato: YYYY-MM-DD).
nationalitystringNacionalidad del la persona a buscar.
additionalLastNamestringApellido adicional de la persona a buscar.

Objeto payload.address

CampotipoDescripción
addressstringCalle y número de la dirección de la persona a buscar.
neighborhoodstringColonia o barrio del usuario de la persona a buscar.
municipalitystringMunicipio o delegación de la persona a buscar.
citystringCiudad de residencia de la persona a buscar.
statestringEstado de residencia de la persona a buscar.
postalCodestringCódigo postal de la dirección de la persona a buscar.

Objeto payload.identifications

CampotipoDescripción
curpstringClave Única de Registro de Población (CURP) de la persona a buscar.
rfcstringRegistro Federal de Contribuyentes (RFC) de la persona a buscar.
socialSecurityNumberstringNúmero de Seguridad Social (NSS) de la persona a buscar.
electorKeystringClave de Elector del INE de la persona a buscar.
ineObjectObjeto con datos de la credencial del INE de la persona a buscar. (ver sub-tablas)

Objeto payload.identifications.ine

CampoTipoDescripción
cicstringCódigo de Identificación de la Credencial INE del documento a buscar.
ocrstringNúmero Oficial de Identificación (OCR) del documento a buscar.

Objeto payload.extras

CampotipoDescripción
commonIdstringIdentificador común del usuario (puede ser ID interno del sistema) de la persona a buscar.
wantedInstringCó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
)
CampoTipoDescripción
eventIdstringID para trazabilidad de los detalles del evento.
responseIdstringID de la respuesta.
organizationstringLa entidad gubernamental en la cual se apoya la consulta
servicestringEs el servicio de la entidad en el cual se realiza la consulta
processTimedoubleTiempo de procesamiento de la consulta del servicio
resultobjectObjeto con los datos de la persona encontrada si fuera el caso
stateobjectSon 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 APIRequeridoDato extraído del documento en el paso anterior
payload.identifications.ine.cicSicontent.data.document.additionalNumber
payload.identifications.ine.ocrSicontent.data.personal.extra.ocr

Para hacer la verificación mediante el servicio INTERPOL deberás enviar los siguientes datos:

Campo enviado en la llamada APIRequeridoDato extraído del documento en el paso anterior
payload.person.nameSicontent.data.personal.name
payload.person.secondNameNocontent.data.personal.secondName
payload.person.lastNameSicontent.data.personal.sureName
payload.person.secondLastNameNocontent.data.personal.motherSurname

Para hacer la verificación mediante el servicio OFAC deberás enviar los siguientes datos:

Campo enviado en la llamada APIRequeridoDato extraído del documento en el paso anterior
payload.person.nameSicontent.data.personal.name
payload.person.secondNameNocontent.data.personal.secondName
payload.person.lastNameSicontent.data.personal.sureName
payload.person.secondLastNameNocontent.data.personal.motherSurname

Para hacer la verificación mediante el servicio RENAPO deberás enviar los siguientes datos:

Campo enviado en la llamada APIRequeridoDato extraído del documento en el paso anterior
payload.identifications.curpSicontent.data.document.personalIdNumber

Para hacer la verificación mediante el servicio SAT deberás enviar los siguientes datos:

Campo enviado en la llamada APIRequeridoDato extraído del documento en el paso anterior
payload.person.nameSicontent.data.personal.name
payload.person.secondNameNocontent.data.personal.secondName
payload.person.lastNameSicontent.data.personal.sureName
payload.person.secondLastNameNocontent.data.personal.motherSurname
payload.identifications.rfcSicontent.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:

Face-Detector SDK


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
CampoTipoRequeridoDescripción
AuthorizationstringEn 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
)
CampoTipoDescipción
videostringVideo 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
  )
CampoTipoDescripción
eventIdstringID para trazabilidad de los detalles del evento.
requestIdstringID para trazabilidad de la petición.
processTimestringTiempo de procesamiento de la verificación en milisegundos.
scoredoubleResultados de diferentes verificaciones sobre el procesamiento del video.
bestFramestringDetecta el mejor fotograma del video y genera una imagen en formato Base64
stateObjectoEstado detallado del procesamiento de verificación de vida.

Objeto state

CampoTipoDescripción
isRealPersonbooleanEsta bandera representa si la persona en el video es real y aprueba la verificación de vida.
messagestringProporciona 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
)
CampoTipoDescripción
eventIdstring(opcional) es la posibilidad de enviar un identificador unico para poder realizar un seguimiento de esta llamada
image1stringLa imagen 1 en Base64 para la comparación
image2stringLa 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
)
CampoTipoDescripción
eventIdstringID para trazabilidad de los detalles del evento.
requestIdstringID para trazabilidad de la petición.
scoredoubleResultado de la evaluación del prueba de vida.
distancedoubleResultado de la evaluación normalizada del prueba de vida.
process_timeintegerTiempo de procesamiento de la verificación en milisegundos.
metadataobjectDatos 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
codesobjectLista de código de errores referente a calidad de imagen y accesorios encontrados dentro de las imagenes comparadas
stateobjectResultados de diferentes verificaciones sobre la prueba de vida.

Objeto state

CampoTipoDescripción
rejectedBadQualitybooleanBandera que determina si las imagenes no cumple con los minimos estándares de calidad
rejectedAccessoriesbooleanBandera que determina si las imagenes comparadas detectan accesorios
isSamePersonbooleanBandera que determina si ambas personas en las imagenes son la misma
messagestringMensaje 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
CampoTipoRequeridoDescripción
AuthorizationstringEn 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.