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ón | Qué pasa con el cobro | Qué ve tu integración |
|---|---|---|
review | Se 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. |
block | Se 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.
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" }'
{
"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ó):
- Aprobar retoma el
confirmdonde se quedó: se crea elattempty el cobro sigue su curso normal (processing→ evidencia del proveedor). - Rechazar cancela el intent directamente — nunca se llega a intentar el cobro con el conector.
{
"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.
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" }'
{
"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"
}
}
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.
| Tipo | Qué evalúa | Razón real observada |
|---|---|---|
amount_cap | Tope de monto por cobro individual. | "amount_cap: monto 50.00 MXN excede el tope 10.00 MXN" |
daily_amount_cap | Tope de monto acumulado del día (todas las transacciones del tenant). | Mismo formato que amount_cap, sobre el acumulado del día. |
velocity | Demasiados 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_window | Cobros fuera (o dentro, según cómo se configure) de un horario esperado. | Describe la hora del intento contra la ventana configurada. |
method_block | Bloquea o marca para revisión uno o más métodos de pago específicos. | "method_block: método 'oxxo' bloqueado" |
network_reputation | Reputació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_ds | No 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.
/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.
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:
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.