Webhooks -- Visão Geral
Webhooks permitem que sua aplicação receba notificações em tempo real sobre eventos na plataforma Minha Konta. Quando um evento ocorre, a Minha Konta envia um HTTP POST para a URL cadastrada.
Como Funciona
- Cadastre uma URL de webhook na sua conta
- Quando um evento ocorrer (ex: PIX recebido), a Minha Konta envia um HTTP POST para sua URL
- Sua aplicação processa a notificação e responde com status
2xx(200, 201 ou 204)
Eventos Disponíveis
A Minha Konta entrega eventos de PIX, TEF entre contas Minha Konta e teste operacional de webhook. Outros produtos (boleto, contas e STA) não estão em escopo. Qualquer tentativa de assinar eventos fora da tabela abaixo é rejeitada com events: contains invalid events: ....
| Evento | Status body | Descrição | Disparo |
|---|---|---|---|
pix.charge.created | created | QR code gerado ou cash-in iniciado | Ativo |
pix.charge.paid | paid | PIX recebido e liquidado | Ativo |
pix.charge.expired | expired | QR code expirou sem pagamento | Ativo |
pix.charge.cancelled | cancelled | QR code cancelado antes de pagamento | Registrado, ainda não disparado |
pix.payout.queued | queued | PIX enviado enfileirado por limite operacional. Retry automático com TTL máximo de 2h | Ativo |
pix.payout.processing | processing | PIX enviado, aguardando confirmação BACEN | Ativo |
pix.payout.held | processing | PIX enviado retido para análise no agente de liquidação (alçada/anti-fraude). Liquida ou rejeita em seguida — não reenviar | Ativo |
pix.payout.confirmed | settled | PIX enviado e confirmado (terminal) | Ativo |
pix.payout.failed | rejected | PIX enviado rejeitado pelo SPI (terminal) | Ativo |
pix.payout.returned | returned | PIX enviado devolvido | Ativo |
pix.refund.requested | requested | Pedido de devolução recebido (infração BACEN); bloqueio cautelar criado no saldo do cliente | Ativo |
pix.refund.completed | settled / completed | Análise da defesa finalizada e devolução executada (ou liberada) | Ativo |
pix.return.received | settled | Devolução PIX recebida (crédito) | Ativo |
pix.infraction.created | ACKNOWLEDGED | Infração PIX reportada pela contraparte via BACEN DICT; pode exigir análise e defesa MED | Ativo |
pix.infraction.resolved | CLOSED / CANCELLED | Infração resolvida por decisão final ou cancelamento da contraparte | Ativo |
pix.infraction.defense_submitted | defense_submitted | Defesa submetida pelo merchant (portal ou API); aguarda análise BACEN | Ativo |
tef.transfer.sent | settled | TEF entre contas Minha Konta liquidada para a conta de origem | Ativo |
tef.transfer.received | settled | TEF entre contas Minha Konta liquidada para a conta de destino | Ativo |
tef.transfer.failed | failed | TEF entre contas Minha Konta rejeitada ou não liquidada | Ativo |
webhook.test | test | Teste manual. Disponível apenas via Admin/Merchant portal - a External API não expõe endpoint para disparar teste | Disparo manual (não External API) |
pix.charge.cancelled ainda não é disparado
O evento está no enum e pode ser assinado, mas o sistema não possui fluxo de cancelamento de QR code hoje. Se você assinar, o POST /webhooks responde 201 normalmente - porém nenhuma notificação chegará. Continue monitorando pix.charge.expired para o ciclo natural de vida do QR.
Segurança
Cada notificação inclui headers de segurança e identificação para validação:
| Header | Descrição |
|---|---|
X-Minha Konta-Signature | Assinatura HMAC-SHA256 do payload (prefixo sha256=). Em casos raros (webhook cadastrado sem secret), o valor literal é unsigned - veja nota abaixo |
X-Minha Konta-Timestamp | Unix timestamp em segundos do envio |
X-Minha Konta-Event-Id | UUID único da delivery (para deduplicação) |
X-Minha Konta-Event-Type | Tipo do evento (ex: pix.charge.paid) |
Content-Type | Sempre application/json |
User-Agent | Sempre Minha Konta-Webhook/1.0 - use para whitelisting em firewalls/WAF. Evolução futura seguirá o padrão Minha Konta-Webhook/{version}; filtre por prefixo Minha Konta-Webhook/ se quiser ficar imune a novas versões |
Signature unsigned quando webhook não tem secret
Se o webhook foi cadastrado sem campo secret em um registro legado, o header X-Minha Konta-Signature pode vir como unsigned. Isso desabilita a validação HMAC do seu lado. Se você receber unsigned, cadastre um novo webhook com secret explícito e remova o antigo.
SHA256 nos webhooks vs SHA512 na API
A API usa HMAC-SHA512 para autenticar requisições que você envia. Os webhooks enviados pela Minha Konta usam HMAC-SHA256 na assinatura X-Minha Konta-Signature. São algoritmos diferentes -- cada um no seu contexto.
Validando a Assinatura
Valide a assinatura para garantir que a notificação foi enviada pela Minha Konta:
const crypto = require('crypto');
function validateWebhook(rawBody, timestamp, signature, secret) {
// rawBody is the RAW request body string (before any JSON parse)
// timestamp is unix seconds (e.g., 1712160000)
const message = `${timestamp}.${rawBody}`;
const expected = 'sha256=' + crypto
.createHmac('sha256', secret)
.update(message)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(signature)
);
}Use o body RAW, não re-serializado
Você deve usar o corpo exato da requisição HTTP como os bytes chegaram na sua aplicação. Se fizer JSON.parse e depois JSON.stringify, os bytes resultantes não serão idênticos ao que a Minha Konta usou para assinar, e a validação falhará.
Em Express/Node: use express.raw({ type: 'application/json' }) ou guarde o body antes de qualquer middleware de parse.
Em outras frameworks: configure para capturar o raw body antes do middleware JSON.
Ordenação de chaves em WEBHOOKS: NÃO é necessária
Para validação de webhooks (HMAC-SHA256) você NÃO precisa ordenar as chaves - use o body raw como recebido no HTTP request do Minha Konta.
⚠️ Atenção - diferença vs envio de requisições: Na assinatura HMAC-SHA512 de REQUISIÇÕES que você envia, a ordenação alfabética das chaves É obrigatória (o servidor Minha Konta reordena antes de validar). Não confunda os dois cenários:
- Webhook recebido (HMAC-SHA256): valide o body raw sem reordenar
- Request enviada (HMAC-SHA512): ordene suas chaves alfabeticamente antes de assinar
Valide sempre
Nunca processe um webhook sem validar a assinatura. Isso protege contra requisições falsificadas.
Adicionalmente, valide que o X-Minha Konta-Timestamp está dentro de ± 5 minutos da hora atual (proteção anti-replay - o servidor não rejeita webhooks "antigos" por padrão; essa verificação cabe ao seu endpoint como defense-in-depth) e deduplique eventos por X-Minha Konta-Event-Id (proteção contra retries).
Retry Policy
Se sua URL retornar um status diferente de 2xx (ou timeout após 30 s), a Minha Konta realiza até 8 tentativas com backoff exponencial. O total de tempo entre a primeira e a oitava tentativa é aproximadamente 7h45min:
| Tentativa | Delay desde tentativa anterior | Tempo acumulado |
|---|---|---|
| 1a | - (imediato) | ~50-200 ms |
| 2a | 30 segundos | ~30 s |
| 3a | 2 minutos | ~2,5 min |
| 4a | 10 minutos | ~12,5 min |
| 5a | 30 minutos | ~42,5 min |
| 6a | 1 hora | ~1,75 h |
| 7a | 2 horas | ~3,75 h |
| 8a | 4 horas | ~7,75 h |
Após 8 tentativas sem sucesso, o webhook_delivery é marcado com status failed e não é reenviado automaticamente. Você pode solicitar replay manual ao suporte Minha Konta fornecendo o X-Minha Konta-Event-Id (ou ter um operador com acesso admin replayar pelo portal).
Status de uma delivery
Cada delivery passa pelos status: pending (criada, aguardando entrega) → delivered (2xx recebido) OU failed (8 tentativas exauridas) OU expired (replay protection).
Sobre expired: quando uma tentativa de entrega fica antiga demais antes do envio, ela pode ser descartada e marcada como expired. Isso impede que reprocessamentos tardios disparem notificações antigas. Replays manuais solicitados ao suporte Minha Konta seguem um fluxo controlado e podem reenviar o evento ao cliente.
Durabilidade
Antes de tentar a primeira entrega, o evento é persistido para retry. Se houver falha durante a entrega, o sistema retoma automaticamente no próximo retry - nenhum evento é perdido.
Idempotência
Sua aplicação deve ser idempotente: se receber o mesmo evento mais de uma vez (identificado pelo X-Minha Konta-Event-Id), deve processá-lo sem duplicar efeitos.
Replay manual via admin
Se uma delivery falhou e você precisa re-enviar, o time Minha Konta pode executar replay manual via admin dashboard. Contate o suporte com o event_id da delivery.
Entregas duplicadas (race condition conhecida)
O sistema pode usar mais de um caminho de entrega para acelerar a primeira notificação e manter retry durável. Em cenários de alta concorrência, você pode receber o mesmo payload 2 vezes via HTTP, mas com o mesmo X-Minha Konta-Event-Id - é o mesmo event, não um retry.
Para evitar impacto duplicado:
- Dedupe por
X-Minha Konta-Event-Id(recomendado - UUID único por delivery, estável em retries e na race condition acima) - Ou alternativamente dedupe por
end_to_end_id+event_typequando fizer sentido para o evento
Isso é comportamento esperado, não erro. Retries legítimos (após 5xx/timeout) também reusam o mesmo X-Minha Konta-Event-Id.
External ID nos Webhooks
Quando uma transação foi criada com external_id, esse campo é incluído no payload do webhook dentro do objeto data. Use-o para correlacionar o evento com o pedido no seu sistema sem precisar fazer uma consulta adicional.
Requisitos do Endpoint
- A URL deve usar HTTPS (a menos que
allow_insecure: trueno cadastro) - Deve responder com status
2xxem até 30 segundos - O body da resposta é ignorado
- Recomendado responder rápido (
200 OKimediato) e processar o evento de forma assíncrona no seu lado; delays longos reduzem o throughput e aumentam chance de retries
Próximos Passos
- Cadastrar Webhook -- criar, listar e remover webhooks
- Payloads dos Eventos -- exemplos de cada tipo de evento
