Ir al contenido
En producción, en modo shadow: FingerTrackID recomienda, su sistema decide.Estado →

Guía de integración

Esta página complementa la entrega de la implementación con las reglas operativas que importan una vez que su integración está en marcha: qué enviar, cómo mantenerse independiente de FingerTrackID, cómo valorar el período en modo shadow y cómo probar sin sandbox.

Su backend envía cada llamada firmada al origen de la API de evaluación de producción:

https://api.fingertrackid.com

Por ejemplo, POST https://api.fingertrackid.com/v1/assessments. Configure el origen exactamente como está escrito: https://, host en minúsculas, sin barra final y sin ruta. La consola muestra el mismo valor, con un botón de copiar, como Endereço da API de risco (dirección de la API de riesgo) en el paso Credencial servidor a servidor de Início, una vez que su credencial existe. El archivo OpenAPI descargable conserva un servidor de ejemplo histórico; no use ese.

Las integraciones nuevas envían la solicitud v1.1 (event-assessment-request.v1.1) y nombran cada momento evaluado con su propia etiqueta: login para un inicio de sesión, y una etiqueta por cada otro momento que integren (las plantillas de recorrido sugieren diez). Los ejemplos de esta página usan v1.1.

1. Envíe una evaluación por cada inicio de sesión completado

Sección titulada «1. Envíe una evaluación por cada inicio de sesión completado»

Envíe una evaluación con la etiqueta login (en v1: LOGIN, action: "LOGIN_ATTEMPT") cada vez que su backend complete un inicio de sesión, incluido el MFA requerido, independientemente de lo que haya producido el navegador. Haga lo mismo con cada otro momento que integre. Omitir eventos hace imposible valorar el período en modo shadow, porque los que faltan son exactamente los casos que usted no puede ver. Cada inicio de sesión lleva el subject_ref y el account_ref del usuario autenticado, construidos como seudónimos con clave estables (referencias de persona y de cuenta): sin account_ref, los ítems de cuenta de la consola quedan sin evaluar.

La evidencia del navegador para ese inicio de sesión tiene que ser reciente: bajo el perfil gestionado de producción, el contexto del evento debe usarse dentro de la ventana de evidencia de su aplicación después de la captura: 60 segundos para las aplicaciones aprovisionadas antes de que se introdujera la ventana de 300 segundos (octubre de 2026), 300 segundos para las aprovisionadas desde entonces. En el navegador, capture antes del envío del formulario (en el primer foco del formulario de inicio de sesión), mantenga esa captura reciente dentro de la ventana y envíe el contexto con la solicitud que su backend evalúa: el paso de MFA cuando su inicio de sesión lo tiene (el ciclo de vida del SDK).

Cuando el navegador no tiene contexto del evento

Sección titulada «Cuando el navegador no tiene contexto del evento»

event_context_token es un campo obligatorio que acepta null; el esquema de la solicitud (y su equivalente v1) define null como “ninguna captura proporcionada”. Envíe null siempre que el navegador no haya podido producir un token: SDK no cargado, bloqueado o con fallo, página en un origen que no está registrado, un tiempo de espera agotado, un token cuyo expiresAt deja demasiado poco tiempo para almacenar la evaluación, o un inicio de sesión que nunca pasó por su código del navegador. Nunca invente, reutilice ni decodifique un token.

Sin evidencia del navegador, el servidor almacena y devuelve una evaluación normal en lugar de un error. Bajo el perfil de producción actual, un inicio de sesión sin contexto vuelve como 201 (o 200 en un reintento de la misma operación) con:

Campo Valor
assessment_status INCONCLUSIVE
trust_score null (sin puntuación; ni 0 ni 100)
recommendation_basis INSUFFICIENT_EVIDENCE
recommended_action MONITOR, la contingencia para toda evaluación sin puntuación: toda etiqueta v1.1, y LOGIN, WITHDRAWAL y TRADE en v1
signals Los 13 indicadores en NOT_EVALUATED
limitation_codes El motivo por el que falta evidencia, por ejemplo COLLECTOR_NOT_LOADED
fallback_policy_version La contingencia que produjo la recomendación

Un extracto sintético de esa respuesta:

INCONCLUSIVE — sin contexto del navegador (extracto)
{
"assessment_id": "01996f2a-0000-7000-8000-000000000401",
"tag": "login",
"assessment_status": "INCONCLUSIVE",
"retryable": false,
"trust_score": null,
"evidence_coverage": 0,
"limitation_codes": [
"COLLECTOR_NOT_LOADED",
"REQUIRED_EVIDENCE_MISSING"
],
"recommended_action": "MONITOR",
"recommendation_basis": "INSUFFICIENT_EVIDENCE",
"fallback_policy_version": "production-observation-fallback-v2"
}

Un inicio de sesión completado mediante Google sign-in u otro proveedor OAuth/OIDC sigue siendo un inicio de sesión (etiqueta login; en v1, LOGIN): evalúelo una vez que su backend haya aceptado el resultado del proveedor y terminado cualquier MFA propio.

  • subject_ref y account_ref son sus propios seudónimos de la persona y de la cuenta, los mismos que usa para los inicios de sesión con contraseña. No envíe el identificador de sujeto del proveedor ni el correo electrónico.
  • Con un flujo de redirección, la página del navegador que podría crear un contexto navega a otra página antes de que su callback se ejecute en el servidor, y el SDK mantiene su estado solo en la memoria de la página. Capture antes de la redirección o en una página posterior al callback, como se describe en inicio de sesión con Google, OAuth y otras redirecciones. Si ninguna solicitud suya lleva un contexto al backend después de completarse la autenticación, envíe event_context_token: null.
  • Nunca coloque un token en el state de OAuth, en una URL de redirección ni en una cadena de consulta. Si su flujo termina con una solicitud de su propia página a su backend (por ejemplo, una credencial one-tap enviada con fetch), esa solicitud puede llevar el token como cualquier otra solicitud de negocio.

2. Aplique fail-open y haga visible la configuración incorrecta

Sección titulada «2. Aplique fail-open y haga visible la configuración incorrecta»

El inicio de sesión de sus usuarios nunca debe depender de FingerTrackID. Al mismo tiempo, una integración que falla en silencio parece exactamente una que funciona. Ambas reglas valen a la vez:

  • Nunca bloquee la acción de negocio por causa de la integración. Todo fallo de FingerTrackID —tiempo de espera agotado, error de red, 429, 503, un error lanzado por el firmador, un feature flag desactivado— termina en el camino normal de su producto.
  • Nunca falle en silencio. Un error de configuración debe producir una entrada de log clara y fácil de buscar y, idealmente, una alerta de su lado.

Compruebe la configuración una vez, cuando su backend arranca, y registre en el log un error claro si es incorrecta, en lugar de omitir evaluaciones en silencio:

  • El origen de la API es exactamente https://api.fingertrackid.com.
  • El runtime tiene crypto.randomUUIDv7(): Node.js 24.16.0 o posterior en la serie 24 (o 26.1.0 o posterior), como explica el wrapper de más abajo.
  • key_id es un UUIDv7 en minúsculas.
  • El secreto se decodifica: decodeSecret(text, 'hex') del firmador de referencia lanza FTID_SECRET_ENCODING para un texto que no es el valor hexadecimal de 64 caracteres, y FTID_SECRET cuando la clave decodificada es demasiado corta.
  • Si su propio feature flag desactiva la integración, registre ese estado en el log una vez al arrancar, para que una integración desactivada sea una decisión visible y no un misterio.

Nunca registre en el log el secreto, el token, las cookies ni el cuerpo de la solicitud.

Resultado Significado Nivel de log Inicio de sesión
201 / 200 Evaluación almacenada info, con assessment_id y correlation_id Continúe
401 authentication-failed, 403 forbidden, 400 validation-failed, 415 Su configuración o su código son incorrectos; reintentar no servirá de nada error, alerta Continúe
400 event-context-invalid El token caducó o ya se había usado antes de que se almacenara la evaluación; no se almacenó nada warn; error si es frecuente (capture más cerca de la acción) Continúe; evalúe una vez más como una operación nueva con null
401 timestamp-out-of-window, 401 request-replayed Problema del reloj o del generador de request_id warn; error si se repite Continúe
409 binding-conflict Cambió un mapeo de referencias: un account_ref llegó con un subject_ref distinto del de la primera vez. No se almacenó nada, y reintentar no servirá de nada error, alerta Continúe
409 idempotency-conflict Los mismos IDs de operación llegaron con contenido distinto; no se almacenó nada nuevo error Continúe; corrija el código que reutiliza los IDs de operación
429, 503, error de red, tiempo de espera agotado, FTID_INDETERMINATE No disponible, o estado desconocido warn Continúe

Las reglas de reintento están en idempotencia y reintento; cada tipo se describe en errores.

FingerTrackID no publica un SLA de latencia de extremo a extremo. En producción, el 2026-10-15, en una aplicación y con una muestra pequeña (no es un SLA), getEventContext sobre una captura aceptada tardó 0,15 s en la mediana y 0,17 s en el p95, mientras que una captura iniciada en el clic tardó 2,6 s en la mediana, 3,9 s en el p95 y como máximo 4,4 s desde la apertura de la sesión hasta la evaluación terminada (medido en producción): prepare en la primera intención, no en el clic. Estos son los presupuestos documentados con los que planificar su propio tiempo de espera:

Dónde Presupuesto Fuente
Navegador, SDK 0.2.x y 0.3.0 Apertura de sesión 3000 ms por intento, captura 4000 ms, contexto del evento 5000 ms por acción, incluida la renovación; su propio tope de envío (el waitMs del helper) acota lo que espera el usuario Tiempos y latencia
Evidencia del navegador y token 60 s o 300 s, según la aplicación, desde que la captura llega a FingerTrackID; almacene la evaluación antes del expiresAt del token La ventana de evidencia
Servidor, v1.1 (toda etiqueta) deadline_ms 5000 ms de forma predeterminada, 250–5000 ms permitidos Recorridos y etiquetas
Servidor, v1 LOGIN deadline_ms 5000 ms de forma predeterminada, 250–5000 ms permitidos Recorridos y etiquetas
Servidor, v1 TRADE deadline_ms 750 ms de forma predeterminada, 250–750 ms permitidos Recorridos y etiquetas
Servidor, v1 WITHDRAWAL deadline_ms 1000 ms de forma predeterminada, 500–1000 ms permitidos Recorridos y etiquetas
Su cliente HTTP El presupuesto del servidor más su latencia de red: por encima de unos 6 s para un cuerpo LOGIN o v1.1 con null; el ejemplo de referencia usa 6500 ms Autenticación

deadline_ms es un presupuesto interno que empieza cuando la solicitud llega al servicio; no es un SLA. Desde el 2026-10-14, el valor predeterminado de LOGIN y de v1.1 es 5000 ms (era 1000 ms), por lo que un tiempo de espera del cliente que no deba cortar un cuerpo con null tiene que superar unos 6 s. Un cuerpo que firma deadline_ms explícitamente puede enviar cualquier valor hasta 5000, o null; uno ya firmado con 750 o 1000 sigue siendo válido y conserva exactamente ese presupuesto. Elija un tiempo de espera HTTP que su inicio de sesión pueda permitirse y, cuando se agote, continúe el inicio de sesión y, si quiere el resultado, concilie más tarde con el mismo cuerpo (consulte tiempo de espera agotado sin respuesta).

Conciliar con el mismo cuerpo devuelve la evaluación almacenada (200) siempre que el primer intento se haya almacenado, incluso después de que caducara el token. Si no se almacenó y el cuerpo lleva un token, el mismo cuerpo solo tiene éxito antes del expiresAt de ese token; después, devuelve 400 event-context-invalid y esos IDs de operación ya no se pueden completar. En ese caso, evalúe el evento como una operación nueva (external_event_id e idempotency_key nuevos) con event_context_token: null, que una operación nueva solo acepta mientras su occurred_at esté dentro de ±300 s del reloj del servidor.

Un esbozo para un backend Node.js (por ejemplo, un route handler o una server action de Next.js), construido sobre el firmador de referencia. log representa su propio logger estructurado.

El firmador necesita crypto.randomUUIDv7(), que Node.js tiene a partir de 24.16.0 en la serie 24 y a partir de 26.1.0; Node.js 22 y 25 no lo tienen, y en ellos el firmador falla en cuanto se importa. Ejecute el backend en Node.js 24 (24.16.0 o posterior; en Vercel, configure la versión de Node.js del proyecto en 24.x) y compruebe process.version en su validación al arrancar. El wrapper carga el firmador con await import(...) dentro de su try, de modo que un runtime que no lo tenga se degrada a “sin evaluación” en lugar de romper la ruta de inicio de sesión.

const API_ORIGIN = 'https://api.fingertrackid.com'
// Above the 5000 ms default budget of a `null` LOGIN/v1.1 body plus the network.
const HTTP_TIMEOUT_MS = 6500
// Send the token only if it stays valid longer than this: your HTTP timeout plus an
// allowance for clock skew between your backend and FingerTrackID. Choose your own value, and
// keep the browser helper's minRemainingMs (15000 ms by default) above it plus the trip from
// the browser to this backend.
const CONTEXT_MARGIN_MS = HTTP_TIMEOUT_MS + 1000
const EVENT_CONTEXT_INVALID = 'https://fingertrackid.dev/problems/event-context-invalid'
// Problems that mean your configuration or integration code is wrong: logged as errors.
const CONFIG_PROBLEMS = new Set([
'https://fingertrackid.dev/problems/authentication-failed',
'https://fingertrackid.dev/problems/forbidden',
'https://fingertrackid.dev/problems/validation-failed',
'https://fingertrackid.dev/problems/unsupported-media-type',
'https://fingertrackid.dev/problems/binding-conflict',
'https://fingertrackid.dev/problems/idempotency-conflict',
])
// Signer codes that mean the configuration itself is wrong.
const CONFIG_CODES = new Set([
'FTID_API_ORIGIN',
'FTID_KEY_ID',
'FTID_SECRET',
'FTID_SECRET_ENCODING',
'FTID_TIMEOUT',
])
// Returns the assessment or null. It never throws, so the login cannot depend on it.
// `request` is the first send of a new operation, with the browser's token or null.
// `contextExpiresAt` is the expiresAt the browser sent with the token (untrusted: it only
// decides whether to drop the token). `newOperationIds()` returns a fresh
// { external_event_id, idempotency_key } and records it with your operation.
// `isAssessment(body)` is your validator for the response schema of the profile you send
// (event-assessment.v1.1 for a v1.1 request, event-assessment.v1 for v1), for example compiled
// once at startup with a JSON Schema library; it returns true only for a valid body.
export async function assessLoginFailOpen({
enabled,
secret,
keyId,
request,
contextExpiresAt,
newOperationIds,
isAssessment,
log,
}) {
if (!enabled) return null // The disabled state was logged once at startup.
try {
// Inside the try: without crypto.randomUUIDv7 the import fails and the login continues.
const { postAttempt, prepareAttempt } = await import('./ftid-hmac-v1.mjs')
const send = async (operation) => {
const body = Buffer.from(JSON.stringify(operation), 'utf8') // Keep for reconciliation.
const attempt = prepareAttempt({ secret, keyId, body })
const result = await postAttempt(API_ORIGIN, attempt, HTTP_TIMEOUT_MS)
return { result, parsed: JSON.parse(result.body) }
}
let operation = request
const remainingMs = Date.parse(contextExpiresAt) - Date.now()
if (operation.event_context_token !== null && !(remainingMs > CONTEXT_MARGIN_MS)) {
operation = { ...operation, event_context_token: null } // Expired or about to expire.
}
let { result, parsed } = await send(operation)
if (
result.status === 400 &&
parsed.type === EVENT_CONTEXT_INVALID &&
operation.event_context_token !== null
) {
// Expired or already used: nothing was stored, but these operation IDs stay bound to
// that token. Assess once more as a new operation, without a context.
log.warn('ftid.event_context_invalid', { correlation_id: result.correlationId })
operation = { ...operation, ...newOperationIds(), event_context_token: null }
;({ result, parsed } = await send(operation))
}
if (result.status === 201 || result.status === 200) {
if (!isAssessment(parsed)) {
// A 2xx whose body breaks the response schema is not an assessment: continue the login
// and keep the stored request bytes to reconcile later.
log.warn('ftid.assessment_invalid_body', {
http_status: result.status,
correlation_id: result.correlationId,
})
return null
}
log.info('ftid.assessment', {
assessment_id: parsed.assessment_id,
status: parsed.assessment_status,
recommended_action: parsed.recommended_action,
correlation_id: result.correlationId,
})
return parsed
}
log[CONFIG_PROBLEMS.has(parsed.type) ? 'error' : 'warn']('ftid.assessment_problem', {
http_status: result.status,
type: parsed.type,
correlation_id: result.correlationId,
})
return null
} catch (error) {
// FTID_INDETERMINATE (timeout, network, unreadable response), a signer error, a failed
// signer import or a parse failure: continue the login. Log only the fixed signer code,
// never an arbitrary message.
const message = error instanceof Error ? error.message : ''
const code = /^FTID_[A-Z_]+$/.test(message) ? message : 'UNEXPECTED'
log[CONFIG_CODES.has(code) ? 'error' : 'warn']('ftid.assessment_unavailable', { code })
return null
}
}

La comprobación del token se aplica solo antes del primer envío de una operación. Un reintento o una conciliación posterior de una operación ya enviada vuelve a enviar sus bytes almacenados sin cambios, token incluido.

El wrapper valida todo cuerpo 200/201 contra el esquema de respuesta del perfil que envió (v1.1 o v1) antes de leer un campo. Valide los cuerpos Problem contra el esquema Problem de la misma forma antes de basarse en algo más allá de type, y adapte la política de reintentos al plazo de su inicio de sesión.

En SHADOW, FingerTrackID evalúa y recomienda; no se impone nada y su producto sigue tomando todas las decisiones. El objetivo del período es comparar las recomendaciones con lo que su producto decidió y con lo que después resultó ser cierto.

  • Sus propios IDs de operación: external_event_id e idempotency_key.
  • assessment_id, assessment_status, trust_score, recommended_action, recommendation_basis, limitation_codes, evidence_coverage y los campos *_version, exactamente como se recibieron.
  • La decisión que su producto tomó realmente, y por qué (su propia regla, una revisión manual, una contingencia).
  • Si el evento tenía un contexto del navegador (event_context_token era un token o null).
  • Si el evento llevaba subject_ref y account_ref.

Envíe lo que ocurrió mediante la API de feedback, sobre el assessment_id. Necesita los permisos de credencial action:write y outcome:write.

  • Acción del cliente: la acción que su producto ejecutó. En modo shadow decidió su propia política, por lo que el par habitual es basis: "CLIENT_POLICY" con reason_code: "CLIENT_POLICY_RULE"; una decisión humana es MANUAL_REVIEW; una decisión tomada bajo su contingencia porque el resultado no estaba disponible a tiempo es FALLBACK con CLIENT_FALLBACK_APPLIED. Use ASSESSMENT solo cuando realmente siguió la recomendación.
  • Resultado: evidencia posterior, como LEGITIMATE o FRAUD_CONFIRMED con su evidencia, o PENDING mientras todavía no lo sepa. Las correcciones se añaden; nunca sobrescriben.

Cada comprobante de acción del cliente lleva recommendation_alignment (ALIGNED, NOT_ALIGNED o NOT_COMPARABLE), derivado por FingerTrackID. Conserve los comprobantes: la consola todavía no muestra la acción del cliente ni el resultado en un acceso.

  • Tasa de concordancia: la proporción de ALIGNED entre las acciones comparables. En modo shadow, NOT_ALIGNED significa que su política y la recomendación difieren; no dice cuál de las dos tiene razón.
  • Cuentas conocidas como legítimas: las recomendaciones para cuentas en las que confía, como personal interno o clientes antiguos sin incidentes. La presencia frecuente de STEP_UP, REVIEW o BLOCK_RECOMMENDED entre las evaluaciones completadas de esas cuentas es una señal de falsos positivos.
  • Casos conocidos como fraudulentos: las recomendaciones sobre eventos que después confirmó como fraude.
  • Cobertura: la proporción de COMPLETED frente a INCONCLUSIVE, y los limitation_codes que hay detrás. Analice por separado las evaluaciones sin puntuación: su recomendación es la contingencia fija, no una valoración.

No hay sandbox ni entorno de pruebas: usted prueba en producción, en modo shadow. Como no se impone nada, una evaluación de prueba no puede bloquear a nadie, pero son datos reales de producción. Se almacena bajo su empresa, aparece en la consola y puede contar en las estimaciones de uso.

  • Use solo sus entradas de producción. La autocomprobación de firma pública y los ejemplos v1.1 sintéticos (y los ejemplos v1) le permiten probar la firma y el parseo sin ninguna credencial.
  • Condicione la integración a su propio feature flag y actívelo primero para cuentas internas o de prueba, después para una pequeña parte de los inicios de sesión y después para todos. Mantenga el flag como su interruptor de apagado de emergencia.
  • Solo los orígenes permitidos bajo el dominio de captura. La captura a través de su dominio de captura se acepta desde los orígenes permitidos de la aplicación cuyo host sea ese dominio o uno de sus subdominios (HTTPS exacto, sin ruta, puerto ni comodín; hasta 10, listados en Integração e saúde › Origens das páginas). Cualquier otro origen, incluidos los hosts de desarrollo local y los despliegues de vista previa, se rechaza sin aprobación CORS, por lo que el navegador oculta la respuesta y el SDK devuelve UNAVAILABLE con el motivo TRANSPORT_ERROR. Su helper continúa con null, y la evaluación en el backend sigue funcionando y devuelve un resultado sin puntuación. Pruebe la captura en el navegador en un origen permitido. Un origen quitado deja de capturar de inmediato (un navegador puede conservar todavía durante poco tiempo un preflight de CORS en caché).
  • Mantenga el secreto en el servidor. Almacénelo solo en el entorno del lado del servidor del despliegue que llama a la API, nunca en una variable expuesta al navegador (en Next.js, nunca en una variable NEXT_PUBLIC_), y solo en entornos desde los que esté dispuesto a enviar datos de producción.

Preguntas: contato@fingertrackid.com.