Webhooks
Receber eventos de liquidação do BaaS: processamento, idempotência e gatilho de conciliação.
Visão geral
A kroz expõe um endpoint inbound para webhooks do parceiro BaaS. Eventos de pagamento atualizam payment_operations, disparam settlement do Rule Engine e, se houver divergência de valor, enfileiram sugestão de conciliação.
BaaS → POST /api/baas-webhook → processWebhookPayload → Supabase + audit
Esta rota é pública (sem cookie de sessão). Autenticação via header
x-baas-webhook-token.
Configurar no parceiro BaaS
1. URL do webhook
https://app.kroz.co/api/baas-webhook
Sandbox: mesma URL com BAAS_ENV=sandbox no backend.
2. Token de autenticação
Configure no painel do BaaS o header:
x-baas-webhook-token: <BAAS_WEBHOOK_TOKEN>
Env no kroz-app:
BAAS_WEBHOOK_TOKEN=<mesmo valor>
Comparação timing-safe: mismatch → 401.
3. Eventos recomendados
| Evento | Quando | Ação kroz |
|---|---|---|
PAYMENT_CREATED |
Cobrança criada | aguardando_pagamento |
PAYMENT_RECEIVED |
PIX/boleto creditado | Settlement + conciliação se divergir |
PAYMENT_CONFIRMED |
Confirmação final | Idem |
PAYMENT_OVERDUE |
Vencido | vencido |
PAYMENT_REFUNDED |
Estorno | falhou |
PAYMENT_SPLIT_DONE |
Split aplicado | splitado |
PAYMENT_SPLIT_APPLIED |
Split confirmado | splitado |
PAYMENT_SPLIT_DIVERGENCE_BLOCK |
Divergência split | divergencia |
PAYMENT_SPLIT_CANCELLED |
Split cancelado | divergencia |
Nomes exatos variam por provedor: o adaptador normaliza para o vocabulário acima.
Formato do payload (inbound)
{
"id": "evt_a1b2c3d4",
"event": "PAYMENT_RECEIVED",
"payment": {
"id": "pay_xyz",
"externalReference": "KROZ-PS-0421-P12",
"value": 51250.00,
"netValue": 51250.00,
"status": "RECEIVED",
"billingType": "PIX",
"installmentNumber": 12,
"invoiceUrl": "https://...",
"bankSlipUrl": null,
"pixQrCodeId": "...",
"encodedImage": "...",
"payload": "..."
}
}
Transferências usam chave transfer em vez de payment.
Campos lidos pelo processor:
| Campo | Uso |
|---|---|
id |
Dedup em webhook_events.event_id |
event |
Mapeamento de status |
payment.id |
Lookup baas_payment_id |
payment.externalReference |
Fallback lookup |
payment.value / netValue |
Conciliação |
payment.installmentNumber |
Rule Engine context |
Autenticação e respostas
Request
curl -s -X POST https://app.kroz.co/api/baas-webhook \
-H "Content-Type: application/json" \
-H "x-baas-webhook-token: $BAAS_WEBHOOK_TOKEN" \
-d @webhook-payment-received.json
Respostas
| Status | Body | Quando |
|---|---|---|
| 200 | { "received": true, ... } |
Processado ou duplicata |
| 401 | { "received": false, "message": "Webhook token inválido." } |
Token errado |
| 400 | { "received": false, "message": "JSON inválido." } |
Body malformado |
| 405 | { "received": false, "message": "Use POST para webhooks BaaS." } |
Método errado |
| 503 | { "received": false, "message": "BAAS_WEBHOOK_TOKEN não configurado." } |
Env ausente |
200 (sucesso):
{
"received": true,
"duplicate": false,
"event": "PAYMENT_RECEIVED",
"eventId": "evt_a1b2c3d4",
"status": "recebido",
"operationId": "uuid-da-operacao",
"sweepQueued": false
}
200 (duplicata (retry do BaaS)):
{
"received": true,
"duplicate": true,
"eventId": "evt_a1b2c3d4"
}
Responda 200 em < 5 segundos. Processamento pesado é idempotente: retries são seguros.
Fluxo de processamento
flowchart TD
W[POST /api/baas-webhook] --> V{Token válido?}
V -->|Não| E401[401]
V -->|Sim| D{Dedup event_id?}
D -->|Duplicata| R200[200 duplicate]
D -->|Novo| L[Lookup payment_operation]
L --> S[resolveStatusFromWebhook]
S --> DIV{Divergência valor?}
DIV -->|Sim| CONC[Conciliação agent]
DIV -->|Não| SETTLE[payment.settled → Rule Engine]
CONC --> AUD[audit_events]
SETTLE --> AUD
AUD --> R200
Mapeamento evento → status
| Evento BaaS | payment_operations.status |
|---|---|
PAYMENT_CREATED, PAYMENT_AWAITING_RISK_ANALYSIS |
aguardando_pagamento |
PAYMENT_RECEIVED |
recebido |
PAYMENT_CONFIRMED |
confirmado |
PAYMENT_OVERDUE |
vencido |
PAYMENT_DELETED, PAYMENT_REFUNDED |
falhou |
PAYMENT_SPLIT_DONE, PAYMENT_SPLIT_APPLIED |
splitado |
PAYMENT_SPLIT_DIVERGENCE_BLOCK, PAYMENT_SPLIT_CANCELLED |
divergencia |
| Divergência de valor detectada | pendente_conciliacao |
Idempotência e retry
| Mecanismo | Tabela/campo | Comportamento |
|---|---|---|
| Dedup webhook | webhook_events.event_id UNIQUE |
Retry BaaS → duplicate: true, sem reprocessar |
| Domain events | payment.settled:{eventId} |
Settlement idempotente |
| API writes | idempotency_keys (7 dias TTL) |
Escritas outbound |
Não há fila de retry interna: confia no redelivery do BaaS + dedup.
O que dispara conciliação
Condições simultâneas:
- Supabase configurado (
platform.configured) payment_operationencontrada- Evento ∈
PAYMENT_RECEIVED,PAYMENT_CONFIRMED |valorPago - valorEsperado| > 0.01(tolerância 1 centavo) -valorPago=payment.value ?? payment.netValue-valorEsperado=operation.amount
Com agente habilitado
RECONCILIATION_AGENT_ENABLED=true:
- Resolve
contractReference+tenantSlug runReconciliationAgent()→reconciliation_suggestions- UI Conciliação → aprovação DF
Sem agente
- Status
pendente_conciliacao metadata.reconciliationBlockedReason: "AGENT_DISABLED"
Erros de contexto
| Código | Significado |
|---|---|
MISSING_CONTRACT_REFERENCE |
Operação sem referência |
MISSING_TENANT_SLUG |
Tenant não identificado |
Audit: conciliacao_contexto_insuficiente
Auto-sweep (opcional)
BAAS_AUTO_SWEEP_ENABLED=true em eventos de liquidação/split:
- Cria
transfer_operationsrefSWEEP-{externalReference} - Valor =
payment.value × spe_percent / 100 - Status pagamento →
sweep_pendente
Ver Adaptadores BaaS: Transferências.
Tabelas persistidas
webhook_events
| Coluna | Tipo | Notas |
|---|---|---|
event_id |
text UNIQUE | ID do evento no payload |
event_type |
text | Tipo normalizado |
payment_id |
text | |
payload |
jsonb | Body completo |
status |
text | Default processado |
domain_events
Eventos normalizados: ex.: payment.settled para Rule Engine.
Ver Rule Engine.
Segurança
⚠️ Não há HMAC de body: apenas header
x-baas-webhook-token. Use token longo, aleatório, rotacionável.
Checklist:
- [ ]
BAAS_WEBHOOK_TOKEN≥ 32 chars aleatórios - [ ] HTTPS only (
Securecookie em prod) - [ ] Token diferente sandbox vs produção
- [ ] Monitorar
401no endpoint (tentativas inválidas)
Troubleshooting
| Problema | Causa | Solução |
|---|---|---|
| 401 constante | Token mismatch | Alinhar painel BaaS ↔ env |
| 200 mas sem operação | operationId: null |
Cobrança sem match por baas_payment_id ou externalReference |
| Conciliação não dispara | Valor bate (±1¢) | Normal: baixa direta |
| Duplicatas no log | Retry BaaS | Esperado: dedup OK |
Webhook sem eventId |
Body sem id |
Configurar eventos no painel do parceiro |
Relacionado
- Adaptadores BaaS: outbound BaaS
- Conciliação: aprovação DF pós-webhook
- Rule Engine: settlement path
- Referência de API