Webhooks -- Vision General
Los webhooks permiten que su aplicacion reciba notificaciones en tiempo real sobre eventos en la plataforma Minha Konta. Cuando ocurre un evento, Minha Konta envia un HTTP POST a la URL registrada.
Como Funciona
- Registre una URL de webhook en su cuenta
- Cuando ocurra un evento (ej: PIX recibido), Minha Konta envia un HTTP POST a su URL
- Su aplicacion procesa la notificacion y responde con estado
2xx(200, 201 o 204)
Eventos Disponibles
Minha Konta entrega eventos de PIX, TEF entre cuentas Minha Konta y prueba operacional de webhook. Otros productos (boleto, cuentas y STA) no estan en alcance. Cualquier intento de suscribir eventos fuera de la tabla abajo es rechazado con events: contains invalid events: ....
| Evento | Status body | Descripcion | Disparo |
|---|---|---|---|
pix.charge.created | created | QR code generado o cash-in iniciado | Activo |
pix.charge.paid | paid | PIX recibido y liquidado | Activo |
pix.charge.expired | expired | QR code expiro sin pago | Activo |
pix.charge.cancelled | cancelled | QR code cancelado antes del pago | Registrado, aun no disparado |
pix.payout.queued | queued | PIX enviado encolado por limite operacional. Retry automatico con TTL maximo de 2h | Activo |
pix.payout.processing | processing | PIX enviado, aguardando confirmacion BACEN | Activo |
pix.payout.held | processing | PIX enviado retenido para analisis en el agente de liquidacion (autorizacion/anti-fraude). Liquida o rechaza despues — no reenviar | Activo |
pix.payout.confirmed | settled | PIX enviado y confirmado (terminal) | Activo |
pix.payout.failed | rejected | PIX enviado rechazado por el SPI (terminal) | Activo |
pix.payout.returned | returned | PIX enviado devuelto | Activo |
pix.refund.requested | requested | Solicitud de devolucion recibida (infraccion BACEN); bloqueo cautelar creado en el saldo del cliente | Activo |
pix.refund.completed | settled / completed | Analisis de la defensa finalizado y devolucion ejecutada (o liberada) | Activo |
pix.return.received | settled | Devolucion PIX recibida (credito) | Activo |
pix.infraction.created | ACKNOWLEDGED | Infraccion PIX reportada por la contraparte via BACEN DICT; puede exigir analisis y defensa MED | Activo |
pix.infraction.resolved | CLOSED / CANCELLED | Infraccion resuelta por decision final o cancelacion de la contraparte | Activo |
pix.infraction.defense_submitted | defense_submitted | Defensa enviada por el merchant (portal o API); aguarda analisis BACEN | Activo |
tef.transfer.sent | settled | TEF entre cuentas Minha Konta liquidada para la cuenta de origen | Activo |
tef.transfer.received | settled | TEF entre cuentas Minha Konta liquidada para la cuenta de destino | Activo |
tef.transfer.failed | failed | TEF entre cuentas Minha Konta rechazada o no liquidada | Activo |
webhook.test | test | Prueba manual. Disponible solo via Admin/Merchant portal - la External API no expone endpoint para disparar prueba | Disparo manual (no External API) |
pix.charge.cancelled aun no es disparado
El evento esta en el enum y puede ser suscrito, pero el sistema no posee flujo de cancelacion de QR code hoy. Si lo suscribe, el POST /webhooks responde 201 normalmente - pero ninguna notificacion llegara. Continue monitoreando pix.charge.expired para el ciclo natural de vida del QR.
Seguridad
Cada notificacion incluye headers de seguridad e identificacion para validacion:
| Header | Descripcion |
|---|---|
X-Minha Konta-Signature | Firma HMAC-SHA256 del payload (prefijo sha256=). En casos raros (webhook registrado sin secret), el valor literal es unsigned - vea nota abajo |
X-Minha Konta-Timestamp | Unix timestamp en segundos del envio |
X-Minha Konta-Event-Id | UUID unico de la delivery (para deduplicacion) |
X-Minha Konta-Event-Type | Tipo del evento (ej: pix.charge.paid) |
Content-Type | Siempre application/json |
User-Agent | Siempre Minha Konta-Webhook/1.0 - use para whitelisting en firewalls/WAF |
Signature unsigned cuando webhook no tiene secret
Si el webhook fue registrado sin campo secret en un registro legacy, el header X-Minha Konta-Signature puede venir como unsigned. Esto deshabilita la validacion HMAC de su lado. Si recibe unsigned, registre un nuevo webhook con secret explicito y remueva el antiguo.
SHA256 en webhooks vs SHA512 en la API
La API usa HMAC-SHA512 para autenticar solicitudes que usted envia. Los webhooks enviados por Minha Konta usan HMAC-SHA256 en la firma X-Minha Konta-Signature. Son algoritmos diferentes -- cada uno en su contexto.
Validando la Firma
Valide la firma para garantizar que la notificacion fue enviada por 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 el body RAW, no re-serializado
Usted debe usar el cuerpo exacto de la solicitud HTTP como los bytes llegaron a su aplicacion. Si hace JSON.parse y despues JSON.stringify, los bytes resultantes no seran identicos a los que Minha Konta uso para firmar, y la validacion fallara.
En Express/Node: use express.raw({ type: 'application/json' }) o guarde el body antes de cualquier middleware de parse.
En otros frameworks: configure para capturar el raw body antes del middleware JSON.
Ordenacion de claves en WEBHOOKS: NO es necesaria
Para validacion de webhooks (HMAC-SHA256) usted NO necesita ordenar las claves - use el body raw como fue recibido en el HTTP request de Minha Konta.
⚠️ Atencion - diferencia vs envio de solicitudes: En la firma HMAC-SHA512 de SOLICITUDES que usted envia, la ordenacion alfabetica de las claves ES obligatoria (el servidor Minha Konta reordena antes de validar). No confunda los dos escenarios:
- Webhook recibido (HMAC-SHA256): valide el body raw sin reordenar
- Request enviada (HMAC-SHA512): ordene sus claves alfabeticamente antes de firmar
Valide siempre
Nunca procese un webhook sin validar la firma. Esto protege contra solicitudes falsificadas.
Adicionalmente, valide que el X-Minha Konta-Timestamp este dentro de ± 5 minutos de la hora actual (proteccion anti-replay) y deduplique eventos por X-Minha Konta-Event-Id (proteccion contra retries).
Retry Policy
Si su URL retorna un estado diferente de 2xx (o timeout despues de 30 s), Minha Konta realiza hasta 8 intentos con backoff exponencial. El tiempo total entre el primer y el octavo intento es aproximadamente 7h45min:
| Intento | Delay desde intento anterior | Tiempo acumulado |
|---|---|---|
| 1o | - (inmediato) | ~50-200 ms |
| 2o | 30 segundos | ~30 s |
| 3o | 2 minutos | ~2,5 min |
| 4o | 10 minutos | ~12,5 min |
| 5o | 30 minutos | ~42,5 min |
| 6o | 1 hora | ~1,75 h |
| 7o | 2 horas | ~3,75 h |
| 8o | 4 horas | ~7,75 h |
Despues de 8 intentos sin exito, el webhook_delivery es marcado con estado failed y no es reenviado automaticamente. Usted puede solicitar replay manual al soporte Minha Konta proporcionando el X-Minha Konta-Event-Id (o tener un operador con acceso admin replayar por el portal).
Estado de una delivery
Cada delivery pasa por los estados: pending (creada, aguardando entrega) → delivered (2xx recibido) O failed (8 intentos agotados) O expired (replay protection).
Sobre expired: cuando un intento de entrega queda demasiado antiguo antes del envio, puede ser descartado y marcado como expired. Esto impide que reprocesamientos tardios disparen notificaciones antiguas. Replays manuales solicitados al soporte Minha Konta siguen un flujo controlado y pueden reenviar el evento al cliente.
Durabilidad
Antes de intentar la primera entrega, el evento se persiste para retry. Si ocurre una falla durante la entrega, el sistema retoma automaticamente en el proximo retry - ningun evento es perdido.
Idempotencia
Su aplicacion debe ser idempotente: si recibe el mismo evento mas de una vez (identificado por el X-Minha Konta-Event-Id), debe procesarlo sin duplicar efectos.
Replay manual via admin
Si una delivery fallo y usted necesita re-enviar, el equipo Minha Konta puede ejecutar replay manual via admin dashboard. Contacte al soporte con el event_id de la delivery.
Entregas duplicadas (race condition conocida)
El sistema puede usar mas de un camino de entrega para acelerar la primera notificacion y mantener retry durable. En escenarios de alta concurrencia, puede recibir el mismo payload 2 veces via HTTP, pero con el mismo X-Minha Konta-Event-Id - es el mismo event, no un retry.
Para evitar impacto duplicado:
- Dedupe por
X-Minha Konta-Event-Id(recomendado - UUID unico por delivery, estable en retries y en la race condition arriba) - O alternativamente dedupe por
end_to_end_id+event_typecuando tenga sentido para el evento
Esto es comportamiento esperado, no error. Retries legitimos (despues de 5xx/timeout) tambien reusan el mismo X-Minha Konta-Event-Id.
External ID en los Webhooks
Cuando una transaccion fue creada con external_id, ese campo se incluye en el payload del webhook dentro del objeto data. Uselo para correlacionar el evento con el pedido en su sistema sin necesidad de hacer una consulta adicional.
Requisitos del Endpoint
- La URL debe usar HTTPS (a menos que
allow_insecure: trueen el registro) - Debe responder con estado
2xxen hasta 30 segundos - El body de la respuesta es ignorado
- Recomendado responder rapido (
200 OKinmediato) y procesar el evento de forma asincronica en su lado; delays largos reducen el throughput y aumentan chance de retries
Proximos Pasos
- Registrar Webhook -- crear, listar y remover webhooks
- Payloads de los Eventos -- ejemplos de cada tipo de evento
