Skip to content

Infracciones PIX (flujo completo)

Vision end-to-end del ciclo de vida de una infraccion PIX en Owem Pay — como nace, como es procesada automaticamente, cuando bloquea saldo, como te defiendes y como reconciliar en tu sistema.

Resumen en una frase

Una infraccion PIX es una impugnacion reportada por la contraparte contra un PIX que tu recibiste. Puede generar bloqueo cautelar del valor impugnado hasta la resolucion. Suscribe los webhooks pix.infraction.* para actuar dentro del plazo.


1. Que es una infraccion PIX?

Una infraccion (del tipo REFUND_REQUEST en el vocabulario BACEN) es un relato formal registrado en el DICT por la institucion del pagador alegando que el PIX recibido por ti presenta indicio de fraude, estafa, error operativo o solicitud de reembolso.

El canal oficial es el BACEN DICT — ningun cliente puede abrir infraccion directamente contra ti; ella siempre pasa por la institucion pagadora y por el BACEN antes de llegar a Owem.

Campo tecnicoDescripcion
infraction_typeTipo de la infraccion en BACEN. Valores: REFUND_REQUEST (solicitud de devolucion) y REFUND_CANCELLED (cancelacion de solicitud anterior)
statusEstado en BACEN. Valores en UPPERCASE: ACKNOWLEDGED (reconocida, en analisis), CLOSED (finalizada), CANCELLED (cancelada por la contraparte antes del analisis)
amountValor impugnado en subcentavos (no necesariamente el valor total del PIX original)
e2e_idEnd-to-End ID del PIX original que fue impugnado
defense_deadlinePlazo BACEN para sumision de defensa (ISO 8601 UTC)
counterpart_ispbISPB de la institucion del pagador que abrio la infraccion
analysis_resultDecision final al cerrar (CLOSED): AGREED (devolucion ejecutada) o DISAGREED (defensa aceptada, sin devolucion)

Fuentes: schemas/pix_compliance/pix_infraction.ex.

No confundas con MED

MED (Mecanismo Especial de Devolucion) es el mecanismo regulatorio que ejecuta la devolucion cuando una infraccion con fraude confirmado necesita ser resarcida — es consecuencia de una infraccion aceptada, no el evento inicial. Ver la seccion Relacion infraccion ↔ MED abajo.


2. Como Owem recibe infracciones

Owem no recibe infracciones via push. El backend hace polling activo en el DICT OnZ/BACEN cada 15 minutos via el worker Fluxiq.Workers.ComplianceSyncWorker.

Flujo de descubrimiento:

BACEN DICT

  │ GET /v3/dict/infracao/list (paginado 1000/call)

ComplianceSyncWorker (*/15 * * * *)

  ├─ upsert_infraction/2 → persiste en pix_infractions
  │     ├─ Si nueva (is_new=true): dispatch "pix.infraction.created"
  │     └─ Si actualizacion (status/analysis_result cambio): dispatch "pix.infraction.resolved"

  └─ maybe_auto_deny_or_block/2 → enruta decision (ver seccion 3)

Fuentes: workers/compliance_sync_worker.ex, use_cases/pix_compliance/pix_compliance.ex.

Latencia BACEN → Webhook

Una infraccion abierta en la contraparte llega a Owem en hasta 15 min (proximo ciclo del ComplianceSyncWorker). El webhook pix.infraction.created se dispara tan pronto la fila se inserta/actualiza en pix_infractions.


3. Clasificacion y acciones automaticas

Tan pronto una infraccion REFUND_REQUEST con status ACKNOWLEDGED u OPEN es detectada, el backend clasifica en tres caminos automaticos sin intervencion manual:

3.1 E2E inexistente en tus transacciones → auto-deny

Si la infraccion referencia un e2e_id que no esta en tu tabla transactions (ningun PIX IN con ese E2E llego a tu sistema), el backend asume que es un error operativo de la contraparte y realiza auto-deny inmediato.

  • Accion OnZ: POST /v3/dict/infracao/{id} con AnalysisResult=DISAGREED
  • Actualiza pix_infractionsstatus=CLOSED, analysis_result=DISAGREED, resolved_at=now
  • Webhook pix.infraction.resolved es disparado
  • Ningun bloqueo cautelar es creado
  • Ninguna devolucion es ejecutada

3.2 Valor ≤ R$ 1.000 (threshold configurable) → auto-deny

Infracciones de bajo valor son automaticamente negadas. El limite por defecto es R$ 1.000,00 (10_000_000 subcentavos, constante @default_auto_deny_threshold), pero puede ser configurado por cuenta o por merchant en la tabla med_configurations (campo min_threshold_amount).

  • La misma accion de auto-deny del item 3.1 es aplicada
  • La justificativa estandarizada enviada al BACEN es: "Verificado pelo time de compliance e sem evidencias concretas nao temos como fazer devolucao"
  • Webhook pix.infraction.resolved es disparado
  • Ningun bloqueo cautelar es creado
  • El valor del PIX recibido continua disponible en el saldo

Threshold personalizado por cuenta

Cuentas que trabajan con tickets altos legitimos (ej: marketplaces de alto valor) pueden solicitar aumento del threshold via admin Owem. Cuentas que prefieren defensa manual para cualquier valor pueden solicitar threshold=0 (nada es auto-negado).

3.3 Valor > R$ 1.000 Y E2E existe → bloqueo cautelar MED

Este es el camino que impacta tu saldo. Al detectar una infraccion elegible, el backend:

  1. Actualiza pix_infractions.status a PROCESSING (evita re-entrada en el proximo ciclo del sync)
  2. Crea una fila en med_cautelar_blocks con el valor impugnado
  3. Crea un phantom hold en TigerBeetle — transferencia pendiente contra la wallet de la cuenta afectada, que reduce el saldo disponible (available en Saldo) en el valor impugnado
  4. Dispara el webhook pix.refund.requested (ver payload)
  5. Dispara el webhook pix.infraction.created (ver payload)

El valor queda bloqueado hasta que:

  • El plazo de defensa BACEN expire (ver seccion 6)
  • Tu envies una defensa via portal (ver seccion 5)
  • El BACEN resuelva la infraccion (seccion 7)
  • La contraparte cancele (status=CANCELLED) antes del analisis

Fuentes: use_cases/pix_compliance/pix_compliance.ex:495-537, use_cases/pix_compliance/med/processor.ex.


4. Relacion infraccion ↔ MED

La infraccion y el MED (Mecanismo Especial de Devolucion) son capas distintas del mismo ciclo:

CapaQue esTablaWebhook principal
InfraccionRelato formal en el BACEN DICTpix_infractionspix.infraction.created
Bloqueo cautelar MEDReserva de saldo en TigerBeetle durante analisismed_cautelar_blockspix.refund.requested
Devolucion MEDPACS.004 ejecutado despues de decision AGREEDtransactions (type=pix_return)pix.refund.completed + pix.payout.returned
[Infraccion creada en BACEN]


  ComplianceSyncWorker detecta (15 min polling)

          ├─ pix.infraction.created (siempre, cuando is_new=true)

          │ Si > R$ 1.000 Y E2E existe:

  MED Processor → cautelar block + TB phantom hold

          ├─ pix.refund.requested (bloqueo creado)

          │ Aguarda decision (merchant defiende O plazo vence O auto-accept session 135)

  [Resolucion]

          ├─ AGREED → PACS.004 dispatched
          │     ├─ pix.refund.completed
          │     └─ pix.payout.returned (prefijo D en el E2E)

          └─ DISAGREED → bloqueo liberado
                └─ BlockRelease.release_for_e2e — void del TB phantom + release del saldo


  pix.infraction.resolved (siempre al cerrar — AGREED o DISAGREED)

La relacion es 1-a-1 por E2E, no 1-a-1 por ID

Una misma transaccion PIX (mismo e2e_id) no puede tener dos infracciones activas al mismo tiempo — el ComplianceSyncWorker hace guard en cautelar_block_exists_for_infraction?/1 para evitar duplicacion de bloqueos. Si la contraparte cancela y abre nueva infraccion con el mismo E2E, el nuevo bloqueo sustituye al anterior.

Ver tambien: med-list y med-detail para consultar los bloqueos cautelares via External API.


5. Como el merchant se defiende

La defensa NO esta disponible via External API

El envio de defensa (subida de evidencias + justificativa) solo es posible via merchant portal (https://merchant.owem.com.br). No hay endpoint External API POST /api/external/infractions/:id/defense hoy. Esta es una limitacion conocida — ver seccion 8.

Flujo de defensa en el merchant portal:

  1. El operador autenticado accede a Compliance → Infracciones en el portal (/compliance)
  2. Ve la lista de infracciones en ACKNOWLEDGED con defense_deadline aproximandose
  3. Hace clic en una infraccion y rellena:
    • Texto de la defensa (hasta ~5000 caracteres, libre — sera enviado al BACEN como AnalysisDetails)
    • Adjuntos (hasta 5 archivos, 10MB cada uno — evidencias como capturas, contratos, historial de conversacion)
  4. Envia → backend:
    • Sube los adjuntos al storage GCS
    • Llama OnZ POST /v3/dict/infracao/{id}/defense con el texto + URLs de los adjuntos
    • Actualiza pix_infractions.status=defense_submitted localmente
    • Dispara webhook pix.infraction.defense_submitted

Fuentes: controllers/merchant/infractions/defense_controller.ex.

Quien puede defender

Cualquier usuario con permiso infractions:write en el merchant portal. Subcuenta operators (sin ser primary holder) tambien pueden enviar defensa siempre que la cuenta afectada este en sus account_ids. Primary holders defienden cualquier cuenta del merchant.


6. Plazos y auto-expiracion

6.1 defense_deadline (plazo BACEN)

El BACEN define un plazo para que el merchant responda. Viene en el campo defense_deadline (ISO 8601 UTC) del webhook pix.infraction.created y se propaga a pix_infractions.defense_deadline.

Tipicamente, ese plazo es de 7 dias corridos a partir de la creacion de la infraccion, pero puede variar segun el tipo (REFUND_REQUEST vs fraude grave).

6.2 Auto-expiracion por el worker MedDefenseExpiration

Para evitar exposicion regulatoria de la institucion (multa BACEN por no responder en plazo), Owem ejecuta el worker Fluxiq.Workers.MedDefenseExpiration cada 5 minutos via Oban cron:

  • Busca bloqueos cautelares con deadline en los proximos 30 minutos y analysis_status aun en pending o defense_submitted
  • Para cada bloqueo, fuerza el veredicto founded (acepta la impugnacion) y dispara Med.execute_return/1 → PACS.004 es enviado al BACEN
  • Libera el saldo bloqueado (ver TB phantom void)
  • El actor del audit log es system, no un admin

Resultado: si no defiendes a tiempo, el sistema auto-acepta la impugnacion y devuelve el valor antes que expire el plazo BACEN.

Fuentes: workers/med_defense_expiration.ex (cron */5 * * * *).

Auto-aceptacion es conservadora

El comportamiento por defecto es proteger a la institucion del riesgo regulatorio — por eso el default es aceptar la impugnacion y devolver. Si necesitas logica diferente (ej: siempre defender automaticamente), contacta compliance@owem.com.br.


7. Resolucion y webhook final

La infraccion siempre termina en uno de estos tres estados terminales:

Status finalAnalysis resultQue sucedio con el saldoWebhook disparado
CLOSEDAGREEDValor devuelto a la contraparte via PACS.004pix.infraction.resolved + pix.refund.completed + pix.payout.returned
CLOSEDDISAGREEDBloqueo liberado, valor regresa al availablepix.infraction.resolved
CANCELLEDnullContraparte cancelo antes del analisis — bloqueo liberadopix.infraction.resolved

En todos los casos, el bloqueo cautelar TB es liberado via Fluxiq.UseCases.PixCompliance.Med.BlockRelease.release_for_e2e/2, que hace el void de la pending transfer en TigerBeetle y actualiza el med_cautelar_blocks.status.

Fuentes: use_cases/pix_compliance/med/block_release.ex.

Diferencia entre AGREED y DISAGREED

  • AGREED: concordaste (explicitamente o via auto-accept) con la devolucion. El PIX OUT (pacs.004) es ejecutado, el valor SALE de tu cuenta, recibes pix.payout.returned con status="returned".
  • DISAGREED: defendiste y el BACEN acato la defensa, O la infraccion fue auto-negada (≤R$1k o E2E inexistente). El valor queda contigo, ningun PIX OUT es ejecutado.

8. Limitaciones actuales (External API)

Feature gap — operaciones via External API

Hoy, solo consulta indirecta via MED esta disponible en el namespace /api/external/*:

  • Existe GET /api/external/med (Listar MED) y GET /api/external/med/:id (Detalle MED) — ambos retornan bloqueos MED, no infracciones directamente
  • NO existe GET /api/external/infractions ni POST /api/external/infractions/:id/defense
  • La lista completa de infracciones y el flujo de defensa estan disponibles solo via merchant portal (JWT-based, no API Key)
  • Webhooks pix.infraction.* son la forma oficial de notificacion automatica

Si tu caso de uso exige automatizacion via External API (ej: enviar defensa programaticamente), contacta compliance@owem.com.br para priorizacion.


9. Webhooks relacionados

Los tres eventos abajo son tus ojos y oidos en el ciclo de infraccion. Suscribe todos en Registrar Webhook:

EventoCuando disparaPayload completo
pix.infraction.createdNueva infraccion detectada Y bloqueo cautelar creado (>R$1k con E2E existente)webhooks-payloads#pix-infraction-created
pix.infraction.resolvedStatus paso a CLOSED o CANCELLED (AGREED, DISAGREED, auto-deny, auto-accept)webhooks-payloads#pix-infraction-resolved
pix.infraction.defense_submittedDefensa enviada via portal (merchant o admin)webhooks-payloads#pix-infraction-defense-submitted

Eventos correlatos (capa MED):

EventoCuando dispara
pix.refund.requestedBloqueo cautelar fue creado en med_cautelar_blocks (cuenta tuvo saldo reservado)
pix.refund.completedDevolucion PACS.004 fue ejecutada (AGREED o auto-accept)
pix.payout.returnedPIX devuelto con E2E prefijo D — tu saldo fue debitado para resarcir al pagador

Auto-deny silencioso en ≤R$1k

El webhook pix.infraction.created no es disparado en el camino de auto-deny ≤R$1k/E2E inexistente (caminos 3.1 y 3.2 en la seccion 3). Solo pix.infraction.resolved es disparado al cerrar. Esto evita ruido para el merchant en casos donde no hay decision que tomar.

Si quieres visibilidad total (incluyendo auto-denies), consulta via merchant portal /compliance — alli aparecen todas las infracciones independiente del camino.


10. Reconciliacion en tu sistema

Recomendaciones para conciliar infracciones en tu DRE / ERP:

10.1 Suscribe los webhooks

  • pix.infraction.created → marca PIX IN original como "impugnado" (flag interno)
  • pix.refund.requested → reserva el valor en tu ledger como "disputado"
  • pix.infraction.resolved:
    • Si analysis_result=DISAGREED → libera reserva, PIX IN permanece definitivo
    • Si analysis_result=AGREED → marca PIX IN como "devuelto", crea asiento de salida
  • pix.refund.completed → confirma la salida del PIX OUT de devolucion

10.2 Usa el e2e_id como clave de correlacion

Toda infraccion lleva e2e_id apuntando al PIX IN original. Usa ese campo como FK en tu sistema para:

10.3 Manten historial del analysis_details

El campo analysis_details viene con la justificativa enviada al BACEN. En casos de auto-deny, viene el mensaje estandarizado. En casos de defensa enviada, viene el texto que tu operador escribio. Esos datos son auditables y deben ser preservados para rastreabilidad regulatoria (LGPD Art. 46, BCB Resolucion 4893).


Referencias tecnicas


FAQ

Puedo abrir una infraccion contra otro merchant via API Owem?

No. Infracciones son abiertas solo por la institucion del pagador, no por el receptor. Si eres el pagador de un PIX y quieres impugnar, usa tu propio canal de atencion (app del banco, defensoria). Owem solo actua en el lado receptor.

Que sucede si no suscribo los webhooks pix.infraction.*?

El sistema continua funcionando — auto-deny y auto-accept ocurren automaticamente. Pero solo descubriras el bloqueo mirando el available en /balance (estara menor que el balance seguro). Para listar bloqueos activos, consulta GET /api/external/med.

Por que mi saldo cayo sin explicacion?

Si ves available menor que el balance, probablemente hay bloqueos cautelares MED activos (> R$1k + E2E valido). Consulta GET /api/external/med para ver todos. Suscribe pix.refund.requested para ser notificado en tiempo real cuando un nuevo bloqueo es creado.

Puedo aumentar o desactivar el threshold de R$1.000?

Si. Contacta compliance@owem.com.br. El threshold es almacenado en med_configurations.min_threshold_amount y puede ser configurado:

  • Por merchant (aplica en todas las cuentas)
  • Por cuenta especifica (override del merchant)
  • Default del sistema: R$1.000 (10.000.000 subcentavos)

Aumentar el threshold reduce el numero de bloqueos cautelares (mas auto-denies). Cerar el threshold elimina auto-deny y hace que cualquier valor disputado pase a analisis manual.

Owem ya tomo decisiones en mi nombre?

Si, en dos escenarios explicitamente automaticos:

  1. Auto-deny ≤R$1k o E2E inexistente — el sistema niega automaticamente en nombre del merchant. La justificativa por defecto enviada al BACEN es "Verificado pelo time de compliance e sem evidencias concretas nao temos como fazer devolucao".
  2. Auto-accept cerca del plazo BACEN — si el bloqueo cautelar esta a <30 min del defense_deadline y el merchant no ha enviado defensa, el worker MedDefenseExpiration acepta la impugnacion como verdict founded y dispara la devolucion.

Ambos comportamientos son conservadores para proteger a la institucion de multa regulatoria BACEN.

El historial del merchant es visible via API?

Parcialmente — via GET /api/external/med (bloqueos) y GET /api/external/transactions/ref/:external_id (transacciones originales y devoluciones). El historial completo de infracciones con todos los campos del BACEN (incluyendo analysisDetails, fraudType, situationType) hoy esta disponible solo en el merchant portal (/compliance).

Owem Pay Instituição de Pagamento — ISPB 37839059