kroz docs

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:

  1. Supabase configurado (platform.configured)
  2. payment_operation encontrada
  3. Evento ∈ PAYMENT_RECEIVED, PAYMENT_CONFIRMED
  4. |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_operations ref SWEEP-{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 (Secure cookie em prod)
  • [ ] Token diferente sandbox vs produção
  • [ ] Monitorar 401 no 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