Webhooks
Webhooks são notificações enviadas pela nossa API para a URL cadastrada sempre que um evento relevante ocorre (por exemplo, um PIX é recebido ou um pagamento é concluído).
Configuração de Webhooks
O jeito de criar e manter assinaturas depende do seu produto:
BaaS (tenant)
Internet Banking (conta)
Você tem dois caminhos, equivalentes e sincronizados:
- API —
POST /v1/webhookse demais rotas descritas nesta página. É o caminho para automatizar a configuração junto com o resto do seu deploy. - Portal do Integrador — além de criar e editar assinaturas, é onde você gerencia o dia a dia: reenvia entregas que falharam, depura erros (status HTTP e corpo devolvidos pelo seu endpoint), consulta o histórico de envios de cada evento e troca a chave HMAC sem interromper o fluxo.
Use a API para provisionar e o Portal para operar e investigar.
Listar Eventos Disponíveis
Resposta (array de { event, description }; trecho ilustrativo):
A lista completa inclui também PIX out/refund/MED, QR Code, boleto, TED, transferências internas e tarifas. Eventos de onboarding estão documentados em Webhooks de Onboarding.
Fluxo de Recebimento
- Um evento ocorre na nossa plataforma.
- Enviamos uma requisição
POSTpara as URLs cadastradas. - Sua aplicação deve processar a notificação e retornar um status
2xx.
Flexibilidade de Configuração
Nossa infraestrutura de webhooks suporta diversos métodos de entrega:
- Agrupamento: Você pode receber múltiplos tipos de evento (por exemplo,
pix.in.completedepix.out.completed) na mesma URL. - Segregação: Você pode configurar URLs diferentes para cada tipo de evento.
- Redundância: Podemos enviar o mesmo evento para múltiplas URLs independentes simultaneamente.
- Segmentação por conta: uma assinatura pode receber só os eventos de uma conta — ver abaixo.
Assinatura por conta (accountId)
Por padrão uma assinatura é do tenant: ela recebe os eventos de todas as suas contas. Informando accountId na criação, ela passa a receber apenas os eventos daquela conta:
O campo volta no GET /v1/webhooks (accountId: null para as assinaturas do tenant).
Regras que valem a pena conhecer antes de usar:
- Filtro, não roteamento adicional. Uma assinatura com
accountIddeixa de receber os eventos das outras contas. Quem quer tudo em um lugar e um recorte em outro precisa de duas assinaturas. - Não é editável.
accountIdsó entra na criação. Trocar a conta de uma assinatura viva redirecionaria silenciosamente o fluxo de eventos de uma conta para o endpoint configurado para outra — criar uma assinatura nova é explícito e deixa a antiga auditável. - Credencial restrita a contas é obrigada a informar. Se a credencial só alcança um conjunto de contas (é o caso das credenciais delegadas), omitir
accountIddevolve 422account_id_required, e informar uma conta de fora do conjunto devolve 403forbidden. Sem isso, uma credencial de uma conta criaria uma assinatura para o movimento de outra e leria tudo pelo webhook, contornando o controle que as rotas de leitura aplicam. - Conta de outro tenant devolve 404
account_not_found. - Eventos sem conta (por exemplo os de accreditation, antes de a conta existir) só chegam nas assinaturas do tenant.
Segurança (Autenticação de Destino)
Ao criar ou atualizar uma assinatura de webhook, você pode escolher como nossa infraestrutura de entrega autentica as requisições enviadas ao seu endpoint. O método de autenticação é configurado por assinatura via API ou dashboard.
Métodos de Autenticação Disponíveis
Você configura o método ao criar ou atualizar uma assinatura, pela API ou pelo Portal do Integrador (Webhooks > Editar Assinatura).
Trocar ou remover a chave
O segredo nunca volta em nenhuma resposta — a assinatura só informa
hmacSecretSet: true|false. Por isso:
- Omitir
secretem umPUTmantém a chave atual. É o que permite trocar só a URL sem perder a assinatura. - Enviar
secretsubstitui a chave (rotação). - Enviar
authType: "NONE"apaga a chave e para de assinar com ela. Este é o único jeito de desligar.
Qualquer outro valor em authType é recusado com 400 unsupported_auth_type.
Verificação de Assinatura HMAC
Quando authType está definido como HMAC, cada requisição inclui um header X-Signature contendo o hash HMAC-SHA256 codificado em Base64 do corpo bruto da requisição, calculado usando seu secret como chave.
Fórmula:
Compare o valor calculado com o header X-Signature. Se forem iguais, a requisição é autêntica.
Sempre utilize os bytes brutos do corpo da requisição para verificação, não uma versão re-parseada/re-serializada. Re-serializar JSON pode alterar a ordem dos campos ou espaços em branco, o que invalidará a assinatura.
Exemplos de Verificação
Node.js:
Python:
Go:
Headers Informativos
Além dos headers de autenticação acima, nossa infraestrutura de entrega adiciona os seguintes headers informativos em todas as requisições:
Esses headers são informativos e não precisam ser validados.
IP Whitelist
Para aumentar a segurança da sua integração, recomendamos que seu servidor de destino valide o endereço IP de origem das requisições. Aceite notificações apenas dos seguintes IPs da nossa infraestrutura:
34.138.140.22334.138.161.10035.231.250.19335.196.71.2934.138.56.192
Sugerimos adicionar esses endereços a uma whitelist no seu firewall ou servidor web.
Retentativas
Se sua aplicação retornar um erro (status diferente de 2xx) ou ocorrer um timeout, nosso sistema tentará reenviar a notificação seguindo uma estratégia de backoff exponencial:
- Tentativas: Até 6 vezes.
- Intervalos: Progressivamente crescentes.
Após esgotar todas as tentativas, a entrega é marcada como falha. Você pode solicitar um reenvio manual daquela entrega específica.
Reenvio de Entrega (Retry)
Para reenviar uma entrega que falhou, use o endpoint de retry por entrega,
informando a subscriptionId e a deliveryId (ambas visíveis na listagem de
entregas da assinatura):
Exemplo de Requisição:
A API enfileira uma nova tentativa de entrega para aquela deliveryId.
Formato das Notificações
Todas as notificações seguem um formato de envelope padrão. O conteúdo específico de cada evento está no objeto data.
Envelope Padrão
Todos os valores monetários (amount, paidAmount, etc.) são sempre em BRL (Reais). Decimais usam ponto (150.50), nunca vírgula. Os eventos de PIX out (pix.out.completed, pix.out.failed, pix.out.timeout e pix.refund.* do mesmo fluxo) incluem data.currency: "BRL". Outros eventos podem omitir o campo — o valor continua em reais.
O POST na sua URL é o envelope completo. O JSON de “webhook entregue” no painel mostra só o objeto data (às vezes rotulado deliveredData).
Campos comuns dentro de data
Alguns campos aparecem em todos os tipos de evento que representam um lançamento no extrato (PIX IN/OUT, refund, QR paid, transferência interna, tarifa). Eles são a forma recomendada de amarrar o webhook com outros endpoints da API:
Os três primeiros campos podem coexistir no mesmo payload. Se só um estiver presente, é porque o outro não existe para aquele tipo de evento (por exemplo: transferências internas têm transactionId + coreId mas não têm endToEnd).
Valores de data.status
O campo status usa um vocabulário padronizado em todos os eventos:
Para eventos de MED (disputa), status usa um vocabulário próprio:
Eventos em que aparece: pix.med.opened, pix.med.updated.
Esse status é o estado no instante do evento, e nisso é confiável: é a
própria notificação do liquidante. É por isso que ele não existe no GET /v1/accounts/{id}/pix/med — lá seria um valor guardado, sem consulta que o
confirme. Se você precisa acompanhar o estado de uma disputa, acumule estes
eventos: eles são a única fonte.
Garantia do status
O status é sempre presente em eventos que representam lançamento de extrato. Se você recebeu um webhook que não tem o campo (ou está vazio), é porque é um evento puramente informativo (ex.: account.balance_updated) — nesses casos use o próprio type para determinar a semântica.
Formato de datas e horários
Os campos de data/hora seguem ISO 8601 / RFC 3339 com fuso explícito — leia o offset, não assuma. Webhooks (envelope occurredAt e campos dentro de data como receivedAt, completedAt, initiatedAt, chargedAt, openedAt) são sempre em UTC (Z). Nas respostas REST, os campos “do nosso lado” (createdAt, updatedAt, reconciledAt, fetchedAt, saldo) também são UTC (Z); já o horário da transação no extrato e nos lookups — campo timestamp (/statement, /pix/transactions) e occurredAt (/pix/payments/lookup, boleto, fee.occurredAt) — sai em horário de Brasília (-03:00).
Exemplos:
Nunca emitimos timestamps no fuso local brasileiro (BRT) nem timestamps “naive” (sem indicador de fuso). Caso você receba um campo de data sem o Z no final, considere bug e nos avise — converteremos sempre que detectarmos a fonte.
Tipos de Eventos e Conteúdos (data)
1. pix.in.completed
Enviado quando um PIX é recebido com sucesso (entrada) na conta. O id do envelope é pix-in-{endToEnd}.
Campos sempre presentes em data:
transactionId: ID da transação no nosso sistema.endToEnd: ID E2E do BACEN.accountId/tenantIdamount: valor em reais.description: descrição (pode ser vazia).method:STATIC_QR_CODE,DYNAMIC_QR_CODEouDICT.receivedAt: RFC 3339 UTC (Z).status:"SUCCESS".
Campos condicionais:
identifier: txid do QR quando o crédito veio de um QR Code.payer/payee: só entram chaves com valor (name,document,bankCode,bankIspb,branch,account,accountNumber,pixKey). Quando a conta existe,accounteaccountNumberrepetem o mesmo valor.
Este evento não envia reconciliationId nem currency.
Exemplo Completo:
2. qrcode.paid
Enviado quando um QR Code gerado por você é pago. No mesmo crédito também sai pix.in.completed.
Campos sempre presentes em data: qrcodeId, identifier, accountId, tenantId, type (static ou dynamic), amount, endToEnd, transactionId, status ("SUCCESS"), receivedAt.
No QR dinâmico, payer traz só name e document. No estático podem vir dados bancários e payee. Este evento não envia reconciliationId.
Exemplo Completo (QR dinâmico):
3. pix.out.completed
Enviado quando uma transferência PIX de saída é concluída com sucesso.
O POST na sua URL é o envelope (id, type, occurredAt, schemaVersion, environment, tenantId, accountId, data). No painel do integrador, o JSON de “webhook entregue” mostra só o conteúdo de data (às vezes rotulado deliveredData) — não é o body HTTP.
Campos sempre presentes em data:
paymentId: ID da ordem (o mesmo da resposta202do POST async). Oiddo envelope épix-out-{paymentId}.tenantId,accountId: repetidos dentro dedata(também estão no envelope).status:"SUCCESS".endToEnd: ID E2E do BACEN (string vazia se o liquidante ainda não emitiu).transactionId: ID da transação no liquidante, quando houver.amount: valor em reais.currency: sempre"BRL".description: descrição enviada no POST (pode ser string vazia).identifier: sua chave de conciliação.originalTransactionId: ID da transação original em devoluções; string vazia no PIX out comum.initiatedAt/completedAt: RFC 3339 UTC (Z), com fração de segundo (até nanossegundos).
Campos condicionais:
key:{ "type", "key" }quando o envio foi por chave PIX.payee: destinatário. Só entram chaves com valor:name,document,bankCode,bankIspb,branch,accountNumber,pixKey,accountType.payer: conta origem. Só entra quando o webhook do banco liquidante trouxe esses dados — não presuma que sempre vem.late:trueapenas quando a confirmação chegou depois de umpix.out.timeoutpara o mesmopaymentId. Ausente no caso normal. Ver Confirmação tardia.
Este evento não envia reconciliationId, method nem um objeto payment.
Exemplo Completo:
4. pix.out.failed
Enviado quando uma transferência PIX de saída falha de forma definitiva — o parceiro bancário recusou a operação (saldo insuficiente, chave inválida, antifraude, recusa do BACEN etc.) e o saldo não foi alterado (ou foi devolvido).
pix.out.failed é terminal: uma vez recebido, não haverá pix.out.completed posterior para a mesma ordem. Cenários de incerteza (ex.: timeout de comunicação com o parceiro, sem confirmação do desfecho) emitem pix.out.timeout — nunca pix.out.failed.
O data usa os mesmos campos de pix.out.completed, com:
status:"FAILED".error/errorCode/errorReason: motivo da recusa (erroré o alias amigável).partner: erro cru do liquidante, quando houver.late:trueapenas quando a recusa chegou depois de umpix.out.timeoutpara o mesmopaymentId. Ver Confirmação tardia.
Este evento não envia reconciliationId.
Exemplo Completo:
5. payment.sent (removido)
Este evento foi marcado como deprecated na v1.15.0 (2026-02-20) e foi permanentemente desativado na v1.28.0 (2026-04-17). Use pix.out.completed com o campo method (PAYMENT) e o objeto payment no payload.
6. payment.refunded (removido)
Este evento foi marcado como deprecated na v1.15.0 (2026-02-20) e foi permanentemente desativado na v1.28.0 (2026-04-17). Use pix.refund.completed. O payload é o mesmo de pix.out.completed (endToEnd desta devolução, originalTransactionId da transação original). Não envia originalEndToEnd nem refundEndToEnd.
7. pix.refund.completed
Enviado quando uma devolução PIX que nós disparamos é concluída. O payload é o mesmo de pix.out.completed. O id do envelope é pix-refund-{paymentId}.
endToEnd: E2E desta devolução.originalTransactionId: ID da transação original (não vem vazio).
Este evento não envia originalEndToEnd, refundEndToEnd nem reconciliationId. Esses E2Es existem em pix.refund.received.
Exemplo Completo:
8. pix.refund.failed
Enviado quando a devolução que nós disparamos falha. Mesmo shape de pix.out.failed, com type: "pix.refund.failed" e id pix-refund-{paymentId}.
Exemplo Completo:
9. fee.charged
Enviado quando uma tarifa bancária é cobrada na conta (por exemplo, tarifa por transação PIX).
Estrutura do data:
transactionId: ID determinístico da row de tarifa no nosso ledger (fee-{coreId}).accountId/tenantId: Conta cobrada e tenant.amount: Valor da tarifa, sempre positivo em reais.description: Descrição da tarifa.feeServiceType: Tipo de serviço que originou a tarifa (PIX,BOLETO, etc.) quando o banco informa.originalRef/transactionRef: Referência cruzada com a movimentação que gerou a tarifa, quando disponível.occurredAt: Timestamp da cobrança no banco.status: Sempre"SUCCESS".kind:"CHARGED".
Exemplo Completo:
9.1. fee.refunded
Enviado quando o banco estorna uma tarifa que havia sido cobrada (ex.: cashback, ajuste). Mesmo formato de fee.charged, com kind="REFUNDED". O sinal é informacional — o crédito já foi aplicado na conta.
Exemplo Completo:
10. pix.med.opened
Enviado quando uma nova disputa MED (Mecanismo Especial de Devolução) é aberta contra uma transação creditada em uma das suas contas. A partir dele começa a correr o prazo de 48h para a sua resposta — ver Guia de Disputas.
Estrutura do data:
medId: Identificador da disputa (infraction report). É o{medId}das rotas REST.originalEndToEnd:endToEndIdda transação PIX em disputa.amount: Valor disputado.reasonCode: Motivo reportado pelo arranjo, cru (ex.:scam-or-fraud,unauthorized-transaction).claimMessage: Detalhes textuais informados pelo reclamante (quando presente).openedAtIso: Data de abertura do MED (ISO 8601).clientAnswerDeadlineIso: Prazo da sua resposta —openedAtIso+ 48h.status:OPENna abertura.
Exemplo Completo:
10. pix.med.updated
Enviado quando o status de uma disputa MED muda. É por aqui que você fica sabendo do desfecho, e é a única fonte de estado: o POST .../answer só registra a sua defesa (que segue para análise fora da API), e a disputa também se move por ação do BACEN e do banco do reclamante.
Reentrega do mesmo status não gera evento novo, e evento atrasado (carimbo do liquidante anterior ao que já registramos) é descartado — o status nunca regride.
Estrutura do data:
medId: Identificador da disputa (mesmo dopix.med.opened).originalEndToEnd:endToEndIdda transação PIX em disputa.amount: Valor disputado.reasonCode: Motivo reportado.clientAnswerDeadlineIso: Prazo da sua resposta (o mesmo da abertura).status: Status atual do MED — vocabulário próprio (OPEN,PENDING_DECISION,ACCEPTED,REJECTED,CANCELED; ver tabela de status acima).
Mapeamento do status do liquidante — o valor cru nunca sai no evento:
Status desconhecido vira OPEN, não desfecho: perder a abertura de uma disputa custa o seu prazo de resposta, receber uma a mais só custa uma consulta.
Exemplo Completo:
11. transfer.internal.in
Enviado à conta que recebe uma transferência interna entre contas do mesmo ecossistema bancário. Transferências internas são instantâneas e liquidadas no mesmo banco — não trafegam pelo SPI do PIX, portanto não possuem endToEndId.
Estrutura do data:
transactionId: ID da transação no nosso sistema (formatointernal-{uuid}). Use noGET /v1/accounts/{accountId}/statement.amount: Valor recebido.status: SempreSUCCESS(transferências internas são atômicas).completedAt: Data/hora da liquidação.identifier: Identificador fornecido pelo pagador noPOST /transfers/internal*(se informado). O mesmo valor chega nas duas pernas (transfer.internal.outetransfer.internal.in).payerDescription/description: Descrição informada pelo pagador.source: Dados do pagador —name,taxId,providerAccountIde, quando a conta é gerenciada por nós,accountIdetenantId.destination: Dados do recebedor, nos mesmos campos.payer/receiver: as mesmas duas pontas, comdocumentno lugar detaxId. Só aparecem quando a transferência foi iniciada fora da API (app ou console do liquidante). Mantidos por compatibilidade — prefirasource/destination, que chegam nas duas origens.
Quando o recebedor é conta de outro banco, destination traz name e taxId (os que você informou na requisição) sem accountId/tenantId. Nesse caso só existe o transfer.internal.out.
Exemplo Completo:
12. transfer.internal.out
Enviado à conta que envia uma transferência interna. Mesmo formato do transfer.internal.in, com duas diferenças:
- O campo
identifieré preenchido nas duas pernas quando o integrador passouidentifierno corpo da requisição original (POST /v1/accounts/{accountId}/transfers/internal*). Use-o para reconciliar a transferência no seu lado. description— se o integrador passoudescriptionna requisição, esse mesmo texto volta aqui (e não o texto padrãoTransferência Interna).
Exemplo:
Ao chamar POST /v1/accounts/{accountId}/transfers/internal (ou as variantes by-document / by-bank-account), envie um identifier único seu. A resposta da API traz esse identifier junto com o transactionId. O webhook transfer.internal.out chega depois trazendo o mesmo identifier + transactionId, permitindo que você feche a transação no seu sistema sem consultar o extrato.
13. pix.out.timeout
Enviado quando o desfecho da ordem é indeterminado ao fim da janela de confirmação: a chamada ao parceiro foi enviada mas a resposta/confirmação final não chegou a tempo (inclui timeout de comunicação na própria chamada de envio). Não é uma falha definitiva — um pix.out.completed ou pix.out.failed tardio ainda pode chegar depois. Recomendamos verificar o extrato antes de tentar novamente com nova chave de idempotência (re-tentativa com a mesma chave é segura).
Estrutura do data:
paymentId,transactionId,accountId,tenantId: Identificação da operação.endToEnd: BACEN E2E quando o partner já tinha emitido.amount/currency: Valor enviado (BRL).status: Sempre"TIMEOUT".errorCode/errorReason/warning/error: Orientação do que fazer (confirmation_timeout).identifier,description,key,payer/payee: quando disponíveis.hold: Presente quando a ordem ficou retida no banco liquidante. Trazowner: "partner"ereason(partner_authorization= fila de autorização interna do liquidante;partner_risk_analysis= análise de risco;partner_unspecified= retido, sem detalhe da fila). Quando este objeto vem, não há nada a aprovar do seu lado nem do nosso — a decisão é do liquidante.partnerStatus/partnerStatusId: Retrato bruto do estado no liquidante, para você anexar em ticket. São valores do liquidante, não do nosso vocabulário canônico: não use em comparação de status.
Exemplo:
Confirmação tardia (late: true)
TIMEOUT é estado indeterminado, não final. Quando a ordem fica retida no liquidante, seguimos consultando por até 7 dias e, se ele liquidar (ou recusar) nesse período, você recebe o evento terminal correspondente:
pix.out.completedcomlate: true— o dinheiro saiu.pix.out.failedcomlate: true— o liquidante recusou.
O evento tardio traz o mesmo paymentId do pix.out.timeout que você já recebeu: é a correção daquele desfecho, não um segundo pagamento. Trate-o como o desfecho definitivo da ordem.
Passados os 7 dias sem notícia, nenhum evento novo é enviado e a ordem permanece TIMEOUT — o que significa, literalmente, “não sabemos”. Confira o extrato (GET /v1/accounts/{accountId}/statement) antes de reemitir. Reemissão com a mesma Idempotency-Key é segura; com chave nova pode gerar pagamento em duplicidade.
14. pix.refund.received
Enviado quando o recebedor de um PIX out devolve o valor por iniciativa própria (sem que tenhamos pedido). Difere de pix.refund.completed, que confirma um refund que nós disparamos.
Estrutura do data:
transactionId: ID determinístico da row do refund recebido (pix-refund-received-{refundEndToEnd}).refundEndToEnd: D-code do refund (BACEN).originalEndToEnd: E-code do PIX out original que está sendo revertido.originalIdentifier: Identifier do PIX out original (eco do que foi enviado na criação do pagamento), quando disponível.accountId/tenantId: A conta que recebeu o refund.amount: Valor revertido.description: Descrição vinda do partner.receivedAt: Timestamp do refund.payer: Quem devolveu o valor (contraparte / recebedor original do PIX out): nome, documento, banco, agência, conta.payee: Quem recebeu o crédito (nossa conta / pagador original do PIX out): nome, documento, banco, agência, conta.status: Sempre"SUCCESS".
Exemplo:
15. qrcode.expired
Enviado quando um QR Code dinâmico expira sem ter sido pago. O id do envelope é qrcode-expired-{txid}.
Campos em data: qrcodeId, identifier, accountId, tenantId, type ("dynamic"), amount, status ("EXPIRED"). Não enviamos txid nem expiredAt.
16. qrcode.cancelled
Enviado quando o integrador cancela um QR Code dinâmico via DELETE /v1/accounts/{accountId}/pix/qr-code?identifier={txid} antes do pagamento. O id do envelope é qrcode-cancelled-{txid}.
Campos em data: qrcodeId, identifier, accountId, tenantId, type, status ("CANCELLED"), reason, cancelledBy. Não enviamos txid, amount nem cancelledAt.
17. boleto.paid
Enviado quando um boleto pago via POST /v1/accounts/{accountId}/boleto/pay é confirmado pelo banco emissor.
Estrutura do data:
paymentId: id canônico do boleto (bol_{uuid}) — mesmo valor que oPOST /boleto/paydevolveu. Use este para correlacionar.boletoId: o mesmo valor depaymentId. Não enviamostransactionId.partnerId: referência no parceiro (operationReferenceId), para suporte/reconciliação.accountId/tenantId: Conta pagadora.amount: Valor pago.line: linha digitável / código de barras.status: Sempre"SUCCESS".
18. boleto.failed
Enviado quando o banco rejeita o pagamento. data.error e data.errorReason são a mesma string (não um objeto).
19. ted.out.requested
Enviado imediatamente após o POST /v1/accounts/{id}/ted/out ser aceito. Status PROCESSING — TED registrada no liquidante, aguardando janela BACEN para liquidar. Ver Guia TED.
20. ted.out.confirmed
Liquidação confirmada pelo liquidante (terminal — sucesso).
Alias deprecado: o evento
ted.paymenté disparado em paralelo comted.out.confirmedcom o mesmo payload, apenas para compatibilidade com assinaturas legadas. Novos integradores devem assinar somenteted.out.confirmed.ted.paymentserá removido na v3.0.
21. ted.out.failed
Liquidação rejeitada/expirada (terminal — falha). data.errorReason traz o motivo canônico.
Possíveis valores de errorReason: invalid_bank_code, insufficient_funds, outside_banking_hours, bank_unreachable, limit_exceeded, ou timeout aguardando confirmação do parceiro (>48h) (polling defensivo esgotou).
22. ted.in.received
Uma TED foi recebida na sua conta (originada por terceiro). Inclui data.payer (nome, documento, banco, agência, conta) quando o liquidante informar esses dados — use para conciliar com sua contabilidade.
Lembre-se: para que terceiros enviem TED para a sua conta, eles devem usar banco 681 (MT Instituição de Pagamentos) — ver Guia TED.
23. policy.violation
Uma regra de política configurada para a sua conta ou tenant foi violada. O evento cobre todas as seções de política — PIX out, PIX in, QR Code, chaves e estorno — e é enviado tanto quando a operação é recusada (action: "BLOCK") quanto quando ela apenas gera aviso (NOTIFY_ONLY no modo monitor, ALLOW_AND_NOTIFY e AUTO_REFUND em PIX recebido). Use blocked para distinguir sem interpretar a ação.
Quando blocked é true em um PIX out, você também recebe o pix.out.failed correspondente, com errorCode: policy_denied. São dois eventos com ids distintos: este é o aviso da regra, aquele é o desfecho do pagamento.
Em modo monitor (NOTIFY_ONLY) o pagamento segue normalmente e terá o desfecho dele (pix.out.completed, por exemplo) — este evento é apenas um aviso de que a transação teria sido recusada se a regra estivesse em BLOCK.
Violação em PIX recebido (phase: "pix_in")
Um PIX recebido não pode ser recusado: o liquidante credita a conta e só depois nos avisa. A violação é sempre posterior ao crédito, e o payload troca paymentId por transactionId e endToEnd, acrescentando refunded:
O pix.in.completed é entregue de qualquer forma, e antes deste evento. Com refunded: true, a devolução gera em seguida os eventos normais de PIX out (pix.out.completed ou pix.out.failed).
Detalhes das regras e da configuração no guia de Políticas e Regras.
Boas Práticas
- Idempotência: Sua aplicação deve estar preparada para receber o mesmo webhook mais de uma vez. Utilize o
iddo envelope para evitar processamento duplicado. - Resposta Rápida: Retorne um status
200 OKassim que receber o webhook e processe a lógica de negócio de forma assíncrona para evitar timeouts. - Validação de Timestamp: Verifique se o
occurredAtnão é muito antigo (recomendamos uma tolerância de 5 minutos) para prevenir ataques de replay.