@dynamicore/jumio-sdk
v1.0.3
Published
Librería modular y tipada para la validación de identidad e INE con Jumio en aplicaciones DynamiCore.
Readme
@dynamicore/jumio-sdk
SDK modular, tipado y agnóstico para la validación de identidad mediante Jumio en el ecosistema DynamiCore. Soporta tanto el flujo de Hosted Webflow (Redirección Web) como el de Carga Directa de Documentos (INE API).
[!IMPORTANT] Para poder utilizar esta librería, la compañía debe tener habilitado el módulo de Jumio desde el backend. Si el módulo no está habilitado para la compañía, la integración no estará disponible aunque el SDK esté instalado y configurado correctamente.
🌟 Modalidades de Verificación Soportadas
| Característica | 🌐 1. Hosted Webflow (Redirección) | 📄 2. Carga Directa (INE API) |
| :--- | :--- | :--- |
| Experiencia de Usuario | El usuario es redirigido a la interfaz alojada oficial de Jumio para captura biométrica y de documentos. | El usuario permanece en tu app; tu interfaz captura/sube las imágenes de la INE. |
| Casos de Uso | Panel web, onboarding sin cámara nativa, verificación biométrica completa + Liveness. | Formularios web/móviles integrados con controles de archivo personalizados. |
| Entrada Requerida | clientId, successUrl, errorUrl | clientId, frontImage, backImage |
| Hooks de React | useJumioWebflow | useJumioVerification |
| Métodos de Core | startWebflow, getWebflowStatus, pollWebflowStatus, parseWebflowReturnParams, buildRedirectUrl | startIneVerification, getIneStatus, verifyIne, pollIneStatus |
📦 Instalación
Desde npm / registro privado:
npm install @dynamicore/jumio-sdk
# o con pnpm / yarn:
pnpm add @dynamicore/jumio-sdk
axiosya viene como dependencia del SDK; no necesitas instalarlo por separado.
En proyectos locales (Monorepo o enlace local):
npm install file:../jumio-sdk🚀 Guías y Ejemplos de Uso
clientId: global o por llamada
Configura un clientId global al crear JumioClient o un hook (junto a context y authToken) para reutilizarlo en todas las operaciones. También puedes pasarlo en cada llamada a startIneVerification, getIneStatus, pollIneStatus, verifyIne, startWebflow, getWebflowStatus, pollWebflowStatus o sendToPii.
El valor por llamada tiene prioridad sobre el global y solo aplica a esa operación; no modifica la configuración del cliente o hook.
const jumio = new JumioClient({ clientId: "global-123", authToken });
await jumio.startWebflow({
clientId: "solo-esta-sesion-456", // override temporal
successUrl,
errorUrl,
});🌐 Servicio 1: Hosted Webflow (Redirección Web)
En este flujo, la app solicita una sesión webflow, redirige al cliente a Jumio (href) y consulta el veredicto al regresar.
[!NOTE] Llegar a
successUrlsolo significa que el usuario completó los pasos en Jumio, no que fue aprobado. Llegar aerrorUrlsignifica que abandonó o falló el flujo. El veredicto real (PASSED/WARNING/REJECTED+extraction) solo se obtiene consultando el estado conaccountId+workflowId(getWebflowStatusuna vez, opollWebflowStatuscon sondeo). Por eso ambos suelen apuntar a la misma ruta/verify/return.
A. Ejemplo con React / Next.js (useJumioWebflow)
Paso 1: Iniciar la verificación (Pantalla de Inicio)
import React from "react";
import { useJumioWebflow } from "@dynamicore/jumio-sdk/react";
export function StartIdentityVerification() {
const { startWebflow, isStarting, error } = useJumioWebflow({
clientId: "usr_123", // default para este hook; por llamada hace override
context: process.env.NEXT_PUBLIC_DYNAMICORE_CONTEXT,
authToken: () => getAuthTokenFromSession(), // requerido: sin token hay 403
// La URL base, el endpoint y el proxy de redirección son constantes
// internas del SDK (ver JUMIO_BASE_URL, JUMIO_ENDPOINT,
// JUMIO_REDIRECT_PROXY_URL) y no se configuran aquí.
});
const handleStart = async () => {
try {
const baseUrl = window.location.origin;
await startWebflow({
successUrl: `${baseUrl}/verify/return`,
errorUrl: `${baseUrl}/verify/return`,
// Las URLs siempre viajan por el proxy interno (Base64 URL-safe).
// En navegador el SDK redirige a Jumio automáticamente (misma pestaña).
// clientId aquí haría override del default si se necesita
});
} catch (err) {
console.error("Error al iniciar verificación:", err);
}
};
return (
<div>
<h3>Verificación de Identidad</h3>
{error && <p style={{ color: "red" }}>{error.message}</p>}
<button onClick={handleStart} disabled={isStarting}>
{isStarting ? "Cargando..." : "Iniciar Verificación con Jumio"}
</button>
</div>
);
}Paso 2: Procesar el retorno (Pantalla /verify/return)
import React, { useEffect } from "react";
import { useJumioWebflow } from "@dynamicore/jumio-sdk/react";
export function IdentityReturnPage() {
const { parseReturnParams, checkResult, cancel, isChecking, result, isValid, isRejected, error } =
useJumioWebflow({
clientId: "usr_123",
context: process.env.NEXT_PUBLIC_DYNAMICORE_CONTEXT,
authToken: () => getAuthTokenFromSession(), // requerido: sin token hay 403
onResult: (res) => {
if (res.valid) {
console.log("¡Verificación aprobada!", res.extraction);
}
},
});
useEffect(() => {
// 1. Extraer accountId y workflowId de los query params de la URL
const { accountId, workflowId } = parseReturnParams();
if (accountId && workflowId) {
// 2. Sondear el resultado final (acepta { maxAttempts, pollingIntervalMs, signal })
// Para una sola consulta sin sondeo: await client.getWebflowStatus({ accountId, workflowId })
// Retorna null si aún está pendiente. clientId viene del default del hook
checkResult({ accountId, workflowId });
}
return () => cancel(); // Limpia el sondeo si el componente se desmonta (abort resetea isChecking)
}, []);
if (isChecking) {
return <p>Verificando identidad, por favor espera un momento...</p>;
}
if (isValid) {
return (
<div>
<h2>✅ Verificación Completada</h2>
<p>CURP Extraída: {String(result?.extraction?.curp || "N/A")}</p>
</div>
);
}
if (isRejected || error) {
return (
<div>
<h2>❌ Verificación No Aprobada</h2>
<p>{error?.message || "El documento fue rechazado."}</p>
</div>
);
}
return <p>Cargando información de la sesión...</p>;
}B. Ejemplo con TypeScript / Node.js (JumioClient)
import { JumioClient } from "@dynamicore/jumio-sdk";
const jumio = new JumioClient({
clientId: "usr_123",
context: "MI_CONTEXTO_NEGOCIO",
authToken: () => getAuthTokenFromSession(),
// baseUrl, endpoint y proxy de redirección son constantes internas del SDK.
});
// 1. Solicitar la URL de verificación
async function initVerificationSession() {
const { href, accountId, workflowId } = await jumio.startWebflow({
successUrl: "https://myapp.com/identity/callback",
errorUrl: "https://myapp.com/identity/callback",
// clientId aquí haría override del default si se necesita
});
console.log("Redirigir cliente a:", href);
return { href, accountId, workflowId };
}
// 2. Consultar o sonder el resultado al regresar
async function verifyReturnStatus(returnUrl: string) {
const { accountId, workflowId } = jumio.parseWebflowReturnParams(returnUrl);
if (!accountId || !workflowId) {
throw new Error("No se encontraron parámetros de seguimiento en la URL.");
}
const result = await jumio.pollWebflowStatus(
{ accountId, workflowId }, // usa el clientId global del cliente
{ maxAttempts: 15, pollingIntervalMs: 5000 }
);
if (result.valid) {
console.log("Veredicto APROBADO:", result.extraction);
} else {
console.warn("Veredicto RECHAZADO:", result.errorMessage);
}
return result;
}📄 Servicio 2: Carga Directa de INE (INE API)
En este flujo, la app captura las imágenes de la INE (frente y reverso) en su propia interfaz y las envía directamente al backend.
A. Ejemplo con React / Next.js (useJumioVerification)
import React, { useState } from "react";
import { useJumioVerification } from "@dynamicore/jumio-sdk/react";
export function DirectIneVerificationStep({ onNext }: { onNext: () => void }) {
const [frontFile, setFrontFile] = useState<File | null>(null);
const [backFile, setBackFile] = useState<File | null>(null);
const { verify, isSubmitting, isPolling, isLoading, stage, progress, error } =
useJumioVerification({
clientId: "usr_123", // default para todas las verificaciones de este hook
context: process.env.NEXT_PUBLIC_DYNAMICORE_CONTEXT,
authToken: () => getAuthTokenFromSession(), // requerido: sin token hay 403
onStatusResolved: (res) => {
if (res.valid) {
onNext();
}
},
});
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!frontFile || !backFile) return;
await verify({
frontImage: frontFile,
backImage: backFile,
awaitFinalStatus: false, // Sondeo en 2do plano sin bloquear la UI
// clientId aquí haría override del default si se necesita
});
};
return (
<form onSubmit={handleSubmit}>
<input type="file" accept="image/*" onChange={(e) => setFrontFile(e.target.files?.[0] || null)} />
<input type="file" accept="image/*" onChange={(e) => setBackFile(e.target.files?.[0] || null)} />
{isLoading && <p>Estado: {stage} ({progress}%)</p>}
{error && <p style={{ color: "red" }}>{error.message}</p>}
<button type="submit" disabled={isLoading || !frontFile || !backFile}>
Continuar
</button>
</form>
);
}B. Ejemplo con TypeScript / Node.js (JumioClient.verifyIne)
import { JumioClient, isJumioError } from "@dynamicore/jumio-sdk";
const jumio = new JumioClient({
clientId: "usr_987654",
context: "MI_CONTEXTO_NEGOCIO",
authToken: () => getAuthTokenFromSession(),
});
async function runDirectVerification() {
try {
const result = await jumio.verifyIne({
frontImage: "https://my-bucket.s3.amazonaws.com/uploads/ine_front.jpg",
backImage: "https://my-bucket.s3.amazonaws.com/uploads/ine_back.jpg",
awaitFinalStatus: true,
// clientId aquí haría override del default si se necesita
});
if (result.valid) {
console.log("INE validada con éxito:", result.data);
} else {
console.warn("INE rechazada:", result.errorMessage);
}
} catch (error) {
if (isJumioError(error)) {
console.error(`Error Jumio [${error.code}]:`, error.message);
}
}
}🛠️ Opciones de Configuración (JumioClientConfig)
| Parámetro | Tipo | Por defecto | Descripción |
| :--- | :--- | :--- | :--- |
| clientId | string | undefined | Identificador por defecto del cliente a verificar. Si se define aquí, verifyIne, startIneVerification, getIneStatus, pollIneStatus, startWebflow, getWebflowStatus, pollWebflowStatus y sendToPii pueden omitir clientId (fallback). Un clientId por llamada hace override. |
| context | string | undefined | Header de contexto enviado en las peticiones. |
| authToken | string \| Provider | — (requerido) | Token Bearer o función proveedora dinámica. Sin token el backend responde 403 (el SDK lanza JumioValidationError antes de la petición). |
| authTokenPrefix | string | "" | Prefijo del header Authorization (ej. "Bearer"). |
| requestTimeout | number | 180000 (3 min) | Timeout para peticiones POST. |
| statusTimeout | number | 120000 (2 min) | Timeout para peticiones GET de estado. |
| maxRetries | number | 3 | Número de reintentos ante caídas de red. |
| retryDelayMs | number | 800 | Delay base del backoff exponencial entre reintentos. |
| pollingIntervalMs | number | 10000 (10s) | Intervalo entre consultas de sondeo. |
| maxPollingAttempts | number | 20 | Máximo de consultas de sondeo antes de timeout. |
| s3Signer | S3SignerFunction | undefined | Función para firmar rutas privadas de S3. |
| customHeaders | Record<string,string> | undefined | Headers adicionales en cada petición. |
| axiosInstance | AxiosInstance | undefined | Instancia propia de Axios (opcional). |
🔒 Constantes internas (no configurables)
La URL base, el endpoint y el proxy de redirección siempre apuntan a la infraestructura de DynamiCore y no forman parte de JumioClientConfig a propósito. Se exportan solo como referencia:
import {
JUMIO_BASE_URL, // "https://front.dynamicore.io"
JUMIO_ENDPOINT, // "/marketplace/apps/jumio"
JUMIO_REDIRECT_PROXY_URL, // "https://inllhuznm2.execute-api.us-west-2.amazonaws.com/prod/jumio/redirect/"
} from "@dynamicore/jumio-sdk";startWebflow siempre envía las URLs de retorno por el proxy interno (JUMIO_REDIRECT_PROXY_URL, Base64 URL-safe).
🧰 Helpers y notas del flujo Webflow
client.buildRedirectUrl(returnUrl)/buildRedirectUrl(url, proxy)— construye la URL de retorno codificada en Base64 URL-safe contra el proxy interno (JUMIO_REDIRECT_PROXY_URL). La función pura acepta un proxy custom como 2do argumento.client.getWebflowStatus({ accountId, workflowId, clientId })— una sola consultaGET ?type=ine&accountId&workflowId&clientId; retornaWebflowResult | null(null= pendiente).client.pollWebflowStatus(params, { maxAttempts, pollingIntervalMs, signal, onAttempt })— sondeo hasta veredicto oJumioPollingTimeoutError.client.sendToPii({ accountId, workflowId, clientId, signal? })— OPCIONAL: tras un veredictovalid: true, envía los datos al PII del cliente. Replica el curl de producción:GETaJUMIO_ENDPOINTconaccountId/workflowId/clientIden query y headersAuthorization+context. Usa la misma infra constante (JUMIO_BASE_URL/JUMIO_ENDPOINT). LanzaJumioPiiError(code: "PII_SYNC_FAILED") distinguible de un rechazo de Jumio — el hook guarda el fallo enpiiErrorsin sobrescribir el veredicto válido.- Hooks exponen
cancel()/reset(); un abort (desmontaje,cancel, StrictMode) bajaisChecking/isStarting/isSendingPii/isLoadingsin marcar error.useJumioWebflowexponeisSendingPii,piiResult,piiErrorysendToPii(). - El backend puede responder con envelope
{ data }o{ values }y datos anidados; el SDK los desempaqueta.parseWebflowReturnParamsaceptaworkflowExecutionId/workflowIdy params anidados en?status=.
🧪 Pruebas y Construcción
# Instalar dependencias
npm install
# Ejecutar suite completa de pruebas unitarias (65 tests)
npm test
# Verificación de tipos TypeScript
npm run typecheck
# Compilar para producción (ESM, CJS y .d.ts)
npm run build📄 Licencia
MIT © DynamiCore
