Infrações PIX (fluxo completo)
Visão end-to-end do ciclo de vida de uma infração PIX na Owem Pay — como ela nasce, como é processada automaticamente, quando bloqueia saldo, como você se defende e como reconciliar no seu sistema.
Resumo em uma frase
Uma infração PIX é uma contestação reportada pela contraparte contra um PIX que você recebeu. Ela pode gerar bloqueio cautelar do valor contestado até a resolução. Assine os webhooks pix.infraction.* para agir dentro do prazo.
1. O que é uma infração PIX?
Uma infração (do tipo REFUND_REQUEST no vocabulário BACEN) é um relato formal registrado no DICT pela instituição do pagador alegando que o PIX recebido por você apresenta indício de fraude, golpe, erro operacional ou solicitação de reembolso.
O canal oficial é o BACEN DICT — nenhum cliente pode abrir infração diretamente em você; ela sempre passa pela instituição pagadora e pelo BACEN antes de chegar na Owem.
| Campo técnico | Descrição |
|---|---|
infraction_type | Tipo da infração no BACEN. Valores: REFUND_REQUEST (pedido de devolução) e REFUND_CANCELLED (cancelamento de pedido anterior) |
status | Estado no BACEN. Valores em UPPERCASE: ACKNOWLEDGED (reconhecida, em análise), CLOSED (finalizada), CANCELLED (cancelada pela contraparte antes da análise) |
amount | Valor contestado em subcentavos (não necessariamente o valor total do PIX original) |
e2e_id | End-to-End ID do PIX original que foi contestado |
defense_deadline | Prazo BACEN para submissão de defesa (ISO 8601 UTC) |
counterpart_ispb | ISPB da instituição do pagador que abriu a infração |
analysis_result | Decisão final ao fechar (CLOSED): AGREED (devolução executada) ou DISAGREED (defesa aceita, sem devolução) |
Fontes: schemas/pix_compliance/pix_infraction.ex.
Não confunda com MED
MED (Mecanismo Especial de Devolução) é o mecanismo regulatório que executa a devolução quando uma infração com fraude confirmada precisa ser ressarcida — ele é consequência de uma infração aceita, não o evento inicial. Ver a seção Relação infração ↔ MED abaixo.
2. Como a Owem recebe infrações
A Owem não recebe infrações via push. O backend faz polling ativo no DICT OnZ/BACEN a cada 15 minutos via o worker Fluxiq.Workers.ComplianceSyncWorker.
Fluxo de descoberta:
BACEN DICT
▲
│ GET /v3/dict/infracao/list (paginado 1000/call)
│
ComplianceSyncWorker (*/15 * * * *)
│
├─ upsert_infraction/2 → persiste em pix_infractions
│ ├─ Se nova (is_new=true): dispatch "pix.infraction.created"
│ └─ Se atualização (status/analysis_result mudou): dispatch "pix.infraction.resolved"
│
└─ maybe_auto_deny_or_block/2 → roteia decisão (ver seção 3)Fontes: workers/compliance_sync_worker.ex, use_cases/pix_compliance/pix_compliance.ex.
Latência BACEN → Webhook
Uma infração aberta na contraparte chega na Owem em até 15 min (próximo ciclo do ComplianceSyncWorker). O webhook pix.infraction.created é disparado assim que a linha é inserida/atualizada em pix_infractions.
3. Classificação e ações automáticas
Assim que uma infração REFUND_REQUEST com status ACKNOWLEDGED ou OPEN é detectada, o backend classifica em três caminhos automáticos sem intervenção manual:
3.1 E2E inexistente nas suas transações → auto-deny
Se a infração referencia um e2e_id que não está na sua tabela transactions (nenhum PIX IN com aquele E2E chegou ao seu sistema), o backend assume que é um erro operacional da contraparte e faz auto-deny imediato.
- Ação OnZ:
POST /v3/dict/infracao/{id}comAnalysisResult=DISAGREED - Atualiza
pix_infractions→status=CLOSED,analysis_result=DISAGREED,resolved_at=now - Webhook
pix.infraction.resolvedé disparado - Nenhum bloqueio cautelar é criado
- Nenhuma devolução é executada
3.2 Valor ≤ R$ 1.000 (threshold configurável) → auto-deny
Infrações de baixo valor são automaticamente negadas. O limiar padrão é R$ 1.000,00 (10_000_000 subcentavos, constante @default_auto_deny_threshold), mas pode ser configurado por conta ou por merchant na tabela med_configurations (campo min_threshold_amount).
- A mesma ação de auto-deny do item 3.1 é aplicada
- A justificativa padronizada enviada ao BACEN é: "Verificado pelo time de compliance e sem evidencias concretas nao temos como fazer devolucao"
- Webhook
pix.infraction.resolvedé disparado - Nenhum bloqueio cautelar é criado
- O valor do PIX recebido continua disponível no saldo
Threshold customizado por conta
Contas que trabalham com tickets altos legítimos (ex: marketplaces de alto valor) podem solicitar aumento do threshold via admin Owem. Contas que preferem defesa manual para qualquer valor podem solicitar threshold=0 (nada é auto-negado).
3.3 Valor > R$ 1.000 E E2E existe → bloqueio cautelar MED
Este é o caminho que impacta seu saldo. Ao detectar uma infração elegível, o backend:
- Atualiza
pix_infractions.statusparaPROCESSING(evita re-entrada no próximo ciclo do sync) - Cria uma linha em
med_cautelar_blockscom o valor contestado - Cria um phantom hold no TigerBeetle — transferência pendente contra a wallet da conta afetada, que reduz o saldo disponível (
availableem Saldo) no valor contestado - Dispara o webhook
pix.refund.requested(ver payload) - Dispara o webhook
pix.infraction.created(ver payload)
O valor fica bloqueado até que:
- O prazo de defesa BACEN expire (ver seção 6)
- Você submeta uma defesa via portal (ver seção 5)
- O BACEN resolva a infração (seção 7)
- A contraparte cancele (
status=CANCELLED) antes da análise
Fontes: use_cases/pix_compliance/pix_compliance.ex:495-537, use_cases/pix_compliance/med/processor.ex.
4. Relação infração ↔ MED
A infração e o MED (Mecanismo Especial de Devolução) são camadas distintas do mesmo ciclo:
| Camada | O que é | Tabela | Webhook principal |
|---|---|---|---|
| Infração | Relato formal no BACEN DICT | pix_infractions | pix.infraction.created |
| Bloqueio cautelar MED | Reserva de saldo em TigerBeetle durante análise | med_cautelar_blocks | pix.refund.requested |
| Devolução MED | PACS.004 executado após decisão AGREED | transactions (type=pix_return) | pix.refund.completed + pix.payout.returned |
[Infração criada no BACEN]
│
▼
ComplianceSyncWorker detecta (15 min polling)
│
├─ pix.infraction.created (sempre, quando is_new=true)
│
│ Se > R$ 1.000 E E2E existe:
▼
MED Processor → cautelar block + TB phantom hold
│
├─ pix.refund.requested (bloqueio criado)
│
│ Aguarda decisão (merchant defende OU prazo vence OU auto-accept session 135)
▼
[Resolução]
│
├─ AGREED → PACS.004 dispatched
│ ├─ pix.refund.completed
│ └─ pix.payout.returned (prefixo D no E2E)
│
└─ DISAGREED → bloqueio liberado
└─ BlockRelease.release_for_e2e — void do TB phantom + release do saldo
│
▼
pix.infraction.resolved (sempre ao fechar — AGREED ou DISAGREED)A relação é 1-para-1 por E2E, não 1-para-1 por ID
Uma mesma transação PIX (mesmo e2e_id) não pode ter duas infrações ativas ao mesmo tempo — o ComplianceSyncWorker faz guard em cautelar_block_exists_for_infraction?/1 para evitar duplicação de bloqueios. Se a contraparte cancela e abre nova infração com o mesmo E2E, o novo bloqueio substitui o anterior.
Ver também: med-list e med-detail para consultar os bloqueios cautelares via External API.
5. Como o merchant defende
Defesa NÃO está disponível via External API
A submissão de defesa (upload de evidências + justificativa) só é possível via merchant portal (https://merchant.owem.com.br). Não há endpoint External API POST /api/external/infractions/:id/defense hoje. Esta é uma limitação conhecida — ver seção 8.
Fluxo de defesa no merchant portal:
- O operador autenticado acessa Compliance → Infrações no portal (
/compliance) - Vê a lista de infrações em
ACKNOWLEDGEDcomdefense_deadlineaproximando - Clica em uma infração e preenche:
- Texto da defesa (até ~5000 caracteres, livre — será enviado ao BACEN como
AnalysisDetails) - Anexos (até 5 arquivos, 10MB cada — evidências como prints, contratos, histórico de conversa)
- Texto da defesa (até ~5000 caracteres, livre — será enviado ao BACEN como
- Submete → backend:
- Faz upload dos anexos para storage GCS
- Chama OnZ
POST /v3/dict/infracao/{id}/defensecom o texto + URLs dos anexos - Atualiza
pix_infractions.status=defense_submittedlocalmente - Dispara webhook
pix.infraction.defense_submitted
Fontes: controllers/merchant/infractions/defense_controller.ex.
Quem pode defender
Qualquer usuário com permissão infractions:write no merchant portal. Subconta operators (sem ser primary holder) também podem submeter defesa desde que a conta afetada esteja nas suas account_ids. Primary holders defendem qualquer conta do merchant.
6. Prazos e auto-expiração
6.1 defense_deadline (prazo BACEN)
O BACEN define um prazo para o merchant responder. Ele vem no campo defense_deadline (ISO 8601 UTC) do webhook pix.infraction.created e é propagado para pix_infractions.defense_deadline.
Tipicamente, esse prazo é de 7 dias corridos a partir da criação da infração, mas pode variar conforme o tipo (REFUND_REQUEST vs fraude grave).
6.2 Auto-expiração pelo worker MedDefenseExpiration
Para evitar exposição regulatória da instituição (multa BACEN por não responder no prazo), a Owem roda o worker Fluxiq.Workers.MedDefenseExpiration a cada 5 minutos via Oban cron:
- Busca bloqueios cautelares com
deadlinenos próximos 30 minutos eanalysis_statusainda empendingoudefense_submitted - Para cada bloqueio, força o veredito
founded(aceita a contestação) e disparaMed.execute_return/1→ PACS.004 é enviado ao BACEN - Libera o saldo bloqueado (ver TB phantom void)
- O actor do audit log é
system, não um admin
Resultado: se você não defender a tempo, o sistema auto-aceita a contestação e devolve o valor antes do prazo BACEN expirar.
Fontes: workers/med_defense_expiration.ex (cron */5 * * * *).
Auto-aceite é conservador
O comportamento padrão é proteger a instituição do risco regulatório — por isso o default é aceitar a contestação e devolver. Se você precisa de lógica diferente (ex: sempre defender automaticamente), contate compliance@owem.com.br.
7. Resolução e webhook final
A infração sempre termina em um destes três estados terminais:
| Status final | Analysis result | O que aconteceu com o saldo | Webhook disparado |
|---|---|---|---|
CLOSED | AGREED | Valor devolvido ao counterparty via PACS.004 | pix.infraction.resolved + pix.refund.completed + pix.payout.returned |
CLOSED | DISAGREED | Bloqueio liberado, valor volta para o available | pix.infraction.resolved |
CANCELLED | null | Contraparte cancelou antes da análise — bloqueio liberado | pix.infraction.resolved |
Em todos os casos, o bloqueio cautelar TB é liberado via Fluxiq.UseCases.PixCompliance.Med.BlockRelease.release_for_e2e/2, que faz o void da pending transfer no TigerBeetle e atualiza o med_cautelar_blocks.status.
Fontes: use_cases/pix_compliance/med/block_release.ex.
Diferença entre AGREED e DISAGREED
- AGREED: você concordou (explicitamente ou via auto-accept) com a devolução. O PIX OUT (
pacs.004) é executado, o valor SAI da sua conta, você recebepix.payout.returnedcomstatus="returned". - DISAGREED: você defendeu e o BACEN acatou a defesa, OU a infração foi auto-negada (≤R$1k ou E2E inexistente). O valor fica com você, nenhum PIX OUT é executado.
8. Limitações atuais (External API)
Feature gap — operações via External API
Hoje, apenas consulta indireta via MED está disponível no namespace /api/external/*:
- Existe
GET /api/external/med(Listar MED) eGET /api/external/med/:id(Detalhe MED) — ambos retornam bloqueios MED, não infrações diretamente - NÃO existe
GET /api/external/infractionsnemPOST /api/external/infractions/:id/defense - A lista completa de infrações e o fluxo de defesa estão disponíveis apenas via merchant portal (JWT-based, não API Key)
- Webhooks
pix.infraction.*são a forma oficial de notificação automática
Se o seu caso de uso exige automação via External API (ex: submeter defesa programaticamente), contate compliance@owem.com.br para priorização.
9. Webhooks relacionados
Os três eventos abaixo são seus olhos e ouvidos no ciclo de infração. Assine todos no Cadastrar Webhook:
| Evento | Quando dispara | Payload completo |
|---|---|---|
pix.infraction.created | Nova infração detectada E bloqueio cautelar criado (>R$1k com E2E existente) | webhooks-payloads#pix-infraction-created |
pix.infraction.resolved | Status passou para CLOSED ou CANCELLED (AGREED, DISAGREED, auto-deny, auto-accept) | webhooks-payloads#pix-infraction-resolved |
pix.infraction.defense_submitted | Defesa submetida via portal (merchant ou admin) | webhooks-payloads#pix-infraction-defense-submitted |
Eventos correlatos (camada MED):
| Evento | Quando dispara |
|---|---|
pix.refund.requested | Bloqueio cautelar foi criado em med_cautelar_blocks (conta teve saldo reservado) |
pix.refund.completed | Devolução PACS.004 foi executada (AGREED ou auto-accept) |
pix.payout.returned | PIX devolvido com E2E prefixo D — seu saldo foi debitado para ressarcir o pagador |
Auto-deny silencioso em ≤R$1k
O webhook pix.infraction.created não é disparado no caminho de auto-deny ≤R$1k/E2E inexistente (caminhos 3.1 e 3.2 na seção 3). Apenas pix.infraction.resolved é disparado ao fechar. Isso evita ruído para o merchant em casos onde não há decisão a tomar.
Se você quer visibilidade total (incluindo auto-denies), consulte via merchant portal /compliance — lá aparecem todas as infrações independente do caminho.
10. Reconciliação no seu sistema
Recomendações para conciliar infrações no seu DRE / ERP:
10.1 Assine os webhooks
pix.infraction.created→ marca PIX IN original como "contestado" (flag interna)pix.refund.requested→ reserva o valor no seu ledger como "disputado"pix.infraction.resolved:- Se
analysis_result=DISAGREED→ libera reserva, PIX IN permanece definitivo - Se
analysis_result=AGREED→ marca PIX IN como "devolvido", cria lançamento de saída
- Se
pix.refund.completed→ confirma a saída do PIX OUT de devolução
10.2 Use o e2e_id como chave de correlação
Toda infração carrega e2e_id apontando para o PIX IN original. Use esse campo como FK no seu sistema para:
- Localizar o PIX IN via
GET /transactions/ref/:external_id(se você armazenouexternal_id) ou via E2E direto - Cruzar com
med_cautelar_blocks(campooriginal_end_to_end_idem med-detail) - Auditar o ciclo completo
10.3 Mantenha histórico do analysis_details
O campo analysis_details vem com a justificativa enviada ao BACEN. Em casos de auto-deny, vem a mensagem padronizada. Em casos de defesa submetida, vem o texto que o seu operador escreveu. Esses dados são auditáveis e devem ser preservados para rastreabilidade regulatória (LGPD Art. 46, BCB Resolução 4893).
Referências técnicas
backend/lib/fluxiq/use_cases/pix_compliance/pix_compliance.ex— orquestraçãobackend/lib/fluxiq/use_cases/pix_compliance/med/processor.ex— MED Processor (phantom hold + PACS.004)backend/lib/fluxiq/use_cases/pix_compliance/med/block_release.ex— release de bloqueiobackend/lib/fluxiq/workers/compliance_sync_worker.ex— polling DICT (15 min)backend/lib/fluxiq/workers/med_defense_expiration.ex— auto-expiration (5 min)backend/lib/fluxiq/schemas/pix_compliance/pix_infraction.ex— schemabackend/lib/fluxiq/schemas/pix_compliance/med/med_cautelar_block.ex— schema do bloqueio
FAQ
Posso abrir uma infração contra outro merchant via API Owem?
Não. Infrações são abertas apenas pela instituição do pagador, não pelo recebedor. Se você é o pagador de um PIX e quer contestar, use o seu próprio canal de atendimento (app do banco, ouvidoria). A Owem só atua no lado recebedor.
O que acontece se eu não assinar os webhooks pix.infraction.*?
O sistema continua funcionando — auto-deny e auto-accept ocorrem automaticamente. Mas você só descobrirá o bloqueio olhando o available em /balance (vai estar menor do que o balance seguro). Para listar bloqueios ativos, consulte GET /api/external/med.
Por que meu saldo caiu sem explicação?
Se você vê available menor que o balance, provavelmente há bloqueios cautelares MED ativos (> R$1k + E2E válido). Consulte GET /api/external/med para ver todos. Assine pix.refund.requested para ser notificado em tempo real quando um novo bloqueio é criado.
Posso aumentar ou desativar o threshold de R$1.000?
Sim. Contate compliance@owem.com.br. O threshold é armazenado em med_configurations.min_threshold_amount e pode ser configurado:
- Por merchant (aplica em todas as contas)
- Por conta específica (override do merchant)
- Default do sistema: R$1.000 (10.000.000 subcentavos)
Aumentar o threshold reduz o número de bloqueios cautelares (mais auto-denies). Zerar o threshold elimina auto-deny e faz com que qualquer valor disputado passe para análise manual.
A Owem já tomou decisões em meu nome?
Sim, em dois cenários explicitamente automáticos:
- Auto-deny ≤R$1k ou E2E inexistente — o sistema nega automaticamente em nome do merchant. A justificativa padrão enviada ao BACEN é "Verificado pelo time de compliance e sem evidencias concretas nao temos como fazer devolucao".
- Auto-accept perto do prazo BACEN — se o bloqueio cautelar estiver a <30 min do
defense_deadlinee o merchant não tiver submetido defesa, o workerMedDefenseExpirationaceita a contestação como verdictfoundede dispara a devolução.
Ambos os comportamentos são conservadores para proteger a instituição de multa regulatória BACEN.
Histórico do merchant é visível via API?
Parcialmente — via GET /api/external/med (bloqueios) e GET /api/external/transactions/ref/:external_id (transações originais e devoluções). O histórico completo de infrações com todos os campos do BACEN (incluindo analysisDetails, fraudType, situationType) hoje está disponível apenas no merchant portal (/compliance).