Antifraude

MODO PRUEBA

Cada confirm se evalúa contra las reglas de riesgo del tenant antes de crear el intento de cobro. Las reglas se configuran en el portal → Antifraude — no hay ningún modelo estadístico opaco: cada regla es una condición explícita (tope de monto, horario, método, velocidad) con una razón en texto claro que viaja hasta tu respuesta. Nunca es una caja negra: si un cobro se detiene, sabes exactamente por qué.

Los dos veredictos: revisar o bloquear

Cuando una regla dispara, su action configurada decide qué pasa con el confirm:

AcciónQué pasa con el cobroQué ve tu integración
reviewSe detiene antes de crear el attempt — no se llama a ningún conector. Entra a la cola de revisión del portal, en espera de que un operador la apruebe o la rechace.200, con un objeto review nuevo en la respuesta.
blockSe rechaza de inmediato — tampoco se crea attempt, y no hay cola: nadie lo va a aprobar después.409 payment_intent.blocked_by_risk.

Ambos casos quedan auditados (payment_intent.risk_blocked / la fila en la cola de revisión) para que puedas rastrear después por qué un cobro no avanzó — el detalle completo vive en el portal, no en la API pública.

Cuando la regla dice "revisar" — el objeto review

El intento no avanza: se queda en su estado previo a confirmar (típicamente requires_payment_method), attempts llega vacío, y aparece un objeto review con la razón exacta y cuándo expira la revisión.

bash
curl -s https://api.winal.com.mx/v1/payment_intents/30ecbf75-.../confirm \
  -H "Authorization: Bearer $SK" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{ "payment_token": "tok_sim_ok", "payment_method": "card" }'
200 · respuesta real (regla amount_cap con action: review)
{
  "id": "30ecbf75-1416-4ecc-bfa3-a9612f1c1302",
  "object": "payment_intent",
  "amount_minor": 5000,
  "tip_minor": 0,
  "total_minor": 5000,
  "currency": "MXN",
  "status": "requires_payment_method",
  "client_secret": "pi_secret_zSbwtrBEHhh-...",
  "receipt_code": "W-UM4EK",
  "livemode": false,
  "created_at": "2026-07-08T02:18:10.294838+00:00",
  "updated_at": "2026-07-08T02:18:10.294838+00:00",
  "attempts": [],
  "review": {
    "object": "risk_review",
    "id": "5baff0dd-b06c-4262-b7c3-6b68cd505d99",
    "status": "pending",
    "reasons": ["amount_cap: monto 50.00 MXN excede el tope 10.00 MXN"],
    "expires_at": "2026-07-09T02:18:10.342642+00:00"
  }
}

Nota que el HTTP sigue siendo 200 — la revisión no es un error, es un estado intermedio legítimo. Tu integración debe tratar la presencia de review como "en espera": muéstrale al pagador que su cobro está en validación, y vuelve a consultar el intent (o escucha el webhook) más tarde.

Qué pasa después: aprobar o rechazar (desde el portal)

Un operador resuelve la revisión desde portal → Antifraude → Cola de revisión, antes de que review.expires_at se cumpla (24 h por defecto desde que se creó):

200 · respuesta real tras rechazar la revisión anterior
{
  "id": "30ecbf75-1416-4ecc-bfa3-a9612f1c1302",
  "object": "payment_intent",
  "amount_minor": 5000,
  "tip_minor": 0,
  "total_minor": 5000,
  "currency": "MXN",
  "status": "canceled",
  "client_secret": "pi_secret_zSbwtrBEHhh-...",
  "receipt_code": "W-UM4EK",
  "livemode": false,
  "created_at": "2026-07-08T02:18:10.294838+00:00",
  "updated_at": "2026-07-08T02:18:25.948259+00:00"
}

Si nadie resuelve la revisión antes de expires_at, se marca expired y el intent queda estancado en el mismo estado en el que se quedó al pedir el confirm — trátalo como un cobro que no sucedió y deja que el pagador lo intente de nuevo.

Cuando la regla dice "bloquear" — 409 payment_intent.blocked_by_risk

Con la misma regla pero action: "block", el confirm nunca llega a crear un attempt: se rechaza en el acto, con la razón en el mensaje de error.

bash
curl -s https://api.winal.com.mx/v1/payment_intents/a3e26653-.../confirm \
  -H "Authorization: Bearer $SK" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{ "payment_token": "tok_sim_ok", "payment_method": "card" }'
409 · respuesta real (regla amount_cap con action: block)
{
  "error": {
    "type": "invalid_request_error",
    "code": "payment_intent.blocked_by_risk",
    "message": "Cobro bloqueado por antifraude: amount_cap: monto 50.00 MXN excede el tope 10.00 MXN.",
    "doc_url": "https://winal.com.mx/docs/errores.html#err-payment_intent.blocked_by_risk",
    "request_id": "0HNMSIOBNVS7R:00000001"
  }
}
No hay cola que revisar aquí
A diferencia de review, un block es definitivo — no queda pendiente en ningún lado esperando aprobación. Si el negocio quiere poder aprobar manualmente estos casos en vez de rechazarlos de plano, la regla equivalente es la misma condición con action: "review".

Tipos de regla disponibles

Se configuran en el portal (Antifraude → Reglas), cada una con su propia action (review o block) y un interruptor de habilitada/deshabilitada. Todas producen una razón en español, lista para mostrar en tu panel de operaciones.

TipoQué evalúaRazón real observada
amount_capTope de monto por cobro individual."amount_cap: monto 50.00 MXN excede el tope 10.00 MXN"
daily_amount_capTope de monto acumulado del día (todas las transacciones del tenant).Mismo formato que amount_cap, sobre el acumulado del día.
velocityDemasiados intentos en una ventana de tiempo, agrupados por IP, correo o huella del token (token_fingerprint).Describe cuántos intentos y en qué ventana se excedió el máximo configurado.
hour_windowCobros fuera (o dentro, según cómo se configure) de un horario esperado.Describe la hora del intento contra la ventana configurada.
method_blockBloquea o marca para revisión uno o más métodos de pago específicos."method_block: método 'oxxo' bloqueado"
network_reputationReputación agregada de la red de consorcio (disputas/bloqueos de OTROS comercios sobre la misma huella).Dispara cuando el número de comercios distintos que reportaron la llave supera el mínimo configurado.
three_dsNo bloquea: calcula una recomendación de autenticación (challenge 3DS o exención) — ver abajo.No aplica (no produce reasons de bloqueo/revisión).

Cuando disparan varias reglas a la vez, reasons trae todas las razones en una sola lista — nunca se trunca a la primera.

Listas de bloqueo/permiso

Además de las reglas, el portal permite mantener listas explícitas de valores siempre bloqueados o siempre permitidos — por IP, correo del pagador, o huella del token (nunca el token en claro: se guarda su huella SHA-256, jamás el dato sensible). Una entrada en la lista de permiso gana sobre cualquier regla que hubiera disparado para ese valor.

La cola de revisión vive en el portal, no en la API pública
Aprobar, rechazar y ver el detalle completo de cada revisión pendiente (monto, método, razones, tiempo restante) es una operación de portal → Antifraude → Cola de revisión — no hay un endpoint público de /v1/* para listarlas o resolverlas; solo ves el objeto review puntual de tu propio intent al confirmarlo.

Red de consorcio: reputación compartida entre comercios

Cada comercio que usa Winal alimenta una reputación agregada y global por huella (tarjeta, dispositivo, correo, IP) — el efecto de red: entre más comercios, mejor detecta el antifraude de TODOS, sin que nadie vea los datos crudos de otro. Es el mismo principio de las listas de bloqueo de arriba, pero cruzando la frontera de tenant a propósito.

Modelo de privacidad: nunca datos crudos entre comercios
Lo único que cruza la frontera de un comercio a otro es un hash SHA-256 no reversible de la llave, más contadores enteros (cuántos cargos, cuántas disputas, cuántos comercios distintos la bloquearon). La tabla de reputación global no tiene tenant_id ni ninguna columna con un valor crudo — solo el hash y los contadores. Un comercio que consulta la reputación de una huella aporta el hash que ya conoce de su propio pagador y recibe el agregado; nunca aprende qué otro comercio la vio, ni el dato original de nadie.

Huella de dispositivo: winal-fingerprint.js

<script src="/js/winal-fingerprint.js"></script> calcula, en el navegador del pagador, un hash SHA-256 estable a partir de señales de bajo riesgo (user-agent, idioma, zona horaria, resolución, un hash de canvas) — nunca manda las señales crudas, solo el hash:

js
const fp = await WinalFingerprint.compute();
// Inclúyela en la metadata del intent al crearlo:
//   metadata: { device_fingerprint: fp }

El backend la lee de metadata.device_fingerprint — el mismo canal de siempre, sin ningún campo nuevo en payment_intents — y la usa como señal de velocidad y de reputación de red por dispositivo al evaluar el riesgo del confirm.

Recomendación de 3DS / exención — auditada, no forzada

Con una regla three_ds configurada, cada decisión de riesgo calcula (y audita) una recomendación: challenge_3ds (riesgo medio: conviene pedir 3DS en vez de bloquear) o exempt_tra (monto bajo y buena reputación: exención de autenticación para subir aprobación). Es una recomendación, no un enforcement — hoy queda registrada en la bitácora de riesgo para que la revises, pero el confirm no la aplica automáticamente todavía (por ejemplo, forzando 3DS en el conector). Documentarlo así de claro es intencional: no prometemos un comportamiento que el código no ejecuta aún.

Gated: el valor de un consorcio depende de la red, no solo del mecanismo
El hashing, la deduplicación y las reglas de reputación ya están construidos y funcionando en producción. Lo que falta para que el "consorcio" tenga el efecto de red completo es más comercios reales contribuyendo señales — un problema de adopción, no una pieza de código pendiente.