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.
El origen de la API de producción
Sección titulada «El origen de la API de producción»Su backend envía cada llamada firmada al origen de la API de evaluación de producción:
https://api.fingertrackid.comPor 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.
El perfil de solicitud
Sección titulada «El perfil de solicitud»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:
{ "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"}Inicios de sesión sociales y OAuth
Sección titulada «Inicios de sesión sociales y OAuth»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_refyaccount_refson 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
statede 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 confetch), 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.
Valide la configuración al arrancar
Sección titulada «Valide la configuración al arrancar»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_ides un UUIDv7 en minúsculas.- El secreto se decodifica:
decodeSecret(text, 'hex')del firmador de referencia lanzaFTID_SECRET_ENCODINGpara un texto que no es el valor hexadecimal de 64 caracteres, yFTID_SECRETcuando 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.
Clasifique los resultados
Sección titulada «Clasifique los resultados»| 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.
Presupuesto de tiempo
Sección titulada «Presupuesto de tiempo»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 wrapper fail-open mínimo
Sección titulada «Un wrapper fail-open mínimo»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 + 1000const 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.
3. Valoración del período en modo shadow
Sección titulada «3. Valoración del período en modo shadow»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.
Registre, para cada evaluación
Sección titulada «Registre, para cada evaluación»- Sus propios IDs de operación:
external_event_ideidempotency_key. assessment_id,assessment_status,trust_score,recommended_action,recommendation_basis,limitation_codes,evidence_coveragey 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_tokenera un token onull). - Si el evento llevaba
subject_refyaccount_ref.
Informe acciones y resultados
Sección titulada «Informe acciones y resultados»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"conreason_code: "CLIENT_POLICY_RULE"; una decisión humana esMANUAL_REVIEW; una decisión tomada bajo su contingencia porque el resultado no estaba disponible a tiempo esFALLBACKconCLIENT_FALLBACK_APPLIED. UseASSESSMENTsolo cuando realmente siguió la recomendación. - Resultado: evidencia posterior, como
LEGITIMATEoFRAUD_CONFIRMEDcon su evidencia, oPENDINGmientras 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.
Qué mirar
Sección titulada «Qué mirar»- Tasa de concordancia: la proporción de
ALIGNEDentre las acciones comparables. En modo shadow,NOT_ALIGNEDsignifica 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,REVIEWoBLOCK_RECOMMENDEDentre 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
COMPLETEDfrente aINCONCLUSIVE, y loslimitation_codesque hay detrás. Analice por separado las evaluaciones sin puntuación: su recomendación es la contingencia fija, no una valoración.
4. Pruebas antes de producción
Sección titulada «4. Pruebas antes de producció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
UNAVAILABLEcon el motivoTRANSPORT_ERROR. Su helper continúa connull, 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.