Ir para o conteúdo

7.1. Visão Geral

🧾 Descrição

O sistema envia notificações via WebHook para informar parceiros e integradores sobre mudanças de estado ocorridas em lotes e títulos.

A entrega é tentada com retry exponencial com jitter em caso de falhas transitórias e cada disparo carrega uma chave de idempotência que permite ao consumidor deduplicar entregas repetidas com segurança.


🔔 Tipos de Eventos

Id Slug (tipoEvento) Página Quando é disparado
4 lote.status_alterado 7.2 Coluna status da tabela lote é alterada (qualquer transição da máquina de estados)
5 titulo.cedido 7.3 LoteTitulo.Executado transiciona de falsetrue em uma operação de Cessão
6 titulo.recomprado 7.4 Ocorrência RecompraComAdiantamento ou RecompraComSubstituicao é executada
7 titulo.liquidado 7.5 Ocorrência LiquidacaoCedente ou LiquidacaoSacado é executada
8 titulo.repassado 7.6 Ocorrência Repasse é executada
9 titulo.resolvido 7.7 Ocorrência Resolucao é executada
10 titulo.rejeitado 7.8 LoteTitulo.Status transiciona para Rejeitado — independe da ocorrência

Uma mesma configuração de webhook pode estar inscrita em um ou mais tipos de evento (aba "Configuração" do webhook no painel). Todos os eventos inscritos são entregues na mesma URL — use o campo tipoEvento (ou o header X-Event-Type) para rotear o processamento.


📦 Envelope Padrão

Todos os eventos compartilham o mesmo envelope. O conteúdo específico do evento fica em dados.

{
  "idWebhook": "019e0892-8d99-778c-9fa5-47bd07cd9ffb",
  "tipoEvento": "lote.status_alterado",
  "dataHora": "2026-05-08T14:23:11.512Z",
  "grupoEconomico": "MeuGrupo",
  "dados": { /* payload específico do evento */ },
  "etiquetas": null
}

🧾 Detalhamento dos Campos do Envelope

Campo Tipo Descrição
idWebhook string (GUID v7) Identificador único do disparo. Use para deduplicação. Também enviado no header X-Idempotency-Key.
tipoEvento string Slug do evento (ex: lote.status_alterado). Sempre ASCII. Também no header X-Event-Type.
dataHora string (ISO 8601, UTC) Momento em que o evento foi gerado pelo VeHub.
grupoEconomico string Identificador do grupo econômico que originou o evento.
dados object Payload específico do tipo de evento (ver páginas 7.2 a 7.8).
etiquetas object (Dictionary) — null Pares chave/valor para extensão futura. Em disparos reais é enviado como null. Em disparos de teste vem preenchido com isTeste = "true" e origem = "painel-vhub".

🔐 Autenticação

O VeHub envia a requisição já autenticada conforme o modo de autenticação que você configurou no painel de webhooks. Os modos suportados são:

Tipo Valor Headers/Body adicionados Observações
SemAutenticacao 1 nenhum Use apenas em endpoints públicos protegidos por outra camada (ex: VPN)
ApiKey 2 X-Api-Key: <segredo> Chave fixa, simples e amplamente compatível
Basic 3 Authorization: Basic base64(clientId:clientSecret) Padrão HTTP Basic
BearerTokenFixo 4 Authorization: Bearer <segredo> Token estático configurado uma vez
OAuth2 5 Authorization: Bearer <token> — token obtido via client_credentials O VeHub busca o token na URL de autenticação configurada antes de enviar
ClientIdClientSecret 6 X-Client-Id: <id> e X-Client-Secret: <segredo> Modelo customizado com headers separados

A URL de envio precisa ser HTTPS. O VeHub recusa configurações com URL em http://.


✍️ Assinatura HMAC (opcional, recomendada)

Quando o webhook é configurado com um segredo HMAC, o VeHub assina cada disparo com HMAC-SHA256 sobre o corpo bruto da requisição e envia o hash no header:

X-Webhook-Signature: sha256=<hex_lowercase>

Validação no consumidor (exemplo Node.js)

const crypto = require('crypto');

function validarAssinatura(corpoBruto, headerAssinatura, segredo) {
  const esperado = crypto
    .createHmac('sha256', segredo)
    .update(corpoBruto, 'utf8')
    .digest('hex');

  const recebido = (headerAssinatura || '').replace(/^sha256=/, '');
  return crypto.timingSafeEqual(
    Buffer.from(esperado, 'hex'),
    Buffer.from(recebido, 'hex')
  );
}

Validação no consumidor (exemplo C#)

using System.Security.Cryptography;
using System.Text;

bool ValidarAssinatura(string corpoBruto, string headerAssinatura, string segredo)
{
    using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(segredo));
    var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(corpoBruto));
    var esperado = Convert.ToHexString(hash).ToLowerInvariant();
    var recebido = (headerAssinatura ?? string.Empty).Replace("sha256=", string.Empty);
    return CryptographicOperations.FixedTimeEquals(
        Encoding.UTF8.GetBytes(esperado),
        Encoding.UTF8.GetBytes(recebido));
}

Importante: valide a assinatura sobre o corpo bruto recebido, antes de qualquer parsing JSON. Frameworks que reformatam JSON antes da validação podem causar mismatch.


📨 Headers Padrão

Toda requisição enviada pelo VeHub inclui:

Header Exemplo Descrição
Content-Type application/json; charset=utf-8 Sempre JSON UTF-8
X-Idempotency-Key 019e0892-8d99-778c-9fa5-47bd07cd9ffb UUID v7 — mesmo valor de idWebhook no envelope
X-Event-Type lote.status_alterado Slug do evento — útil para roteamento
X-Webhook-Signature sha256=fa1be9... Presente apenas quando há segredo HMAC configurado
Authorization ou afins depende do modo de autenticação Ver tabela acima

🔁 Retry e Entrega

O VeHub considera entrega bem-sucedida quando o consumidor responde com status HTTP 2xx.

Em caso de falha transitória (5xx, 408, 429, timeout, DNS, conexão recusada), há retry exponencial com jitter. Os demais erros 4xx não são retentados.

Tentativa Delay aproximado
1 imediato
2 ~1s + jitter
3 ~2s + jitter
4 ~4s + jitter
5 ~8s + jitter

Após 5 tentativas sem sucesso, o disparo é registrado como falho e não é mais retentado automaticamente.

Para garantir entregas duráveis, o consumidor deve responder com 2xx assim que receber a mensagem (idealmente em menos de 5s) e processar de forma assíncrona.


🪞 Idempotência

Como retries podem entregar a mesma notificação mais de uma vez, o consumidor deve deduplicar usando o idWebhook (ou o header X-Idempotency-Key).

Padrão recomendado:

  1. Ao receber o webhook, persista idWebhook em uma tabela com unique constraint.
  2. Se o INSERT falhar com violação de unicidade, retorne 200 OK sem reprocessar.
  3. Senão, processe normalmente.

🧪 Disparo de Teste

Pelo painel Cadastros → Webhooks → editar webhook → botão "Testar agora", é possível disparar um payload realístico do tipo de evento configurado, com IDs fictícios negativos (ex: idLote = -1001) e a etiqueta isTeste = "true".

Características do disparo de teste:

  • Single-shot (sem retry).
  • Persiste em log_webhook_disparo com EhTeste = true (visível na aba "Logs de disparo" com a tag Teste).
  • Mesmos headers de autenticação e HMAC do disparo real.
  • Envelope com etiquetas preenchidas: isTeste = "true" e origem = "painel-vhub".

Recomendado deixar o consumidor preparado para identificar etiquetas.isTeste === "true" e ignorar/ramificar o processamento durante validações.


🛠️ Configuração no Painel

A configuração de webhooks está em Cadastros → Webhooks (requer permissão cadastros:webhook:escrita).

Crie uma configuração com:

  • Tipos de evento que deseja receber (um ou mais dos 7 listados acima)
  • URL de envio (HTTPS obrigatório)
  • Tipo de autenticação + credenciais conforme o modo escolhido
  • Segredo HMAC (opcional, mas recomendado — mínimo 16 caracteres)

📜 Logs de Disparo

Cada tentativa de envio fica registrada em log_webhook_disparo com:

  • Data, número da tentativa, duração, status HTTP, sucesso/falha
  • Headers e payload enviados
  • Headers e body da resposta recebida
  • Identificador idempotente (webhookId)
  • Flag ehTeste indicando se foi disparo manual de teste

Os últimos disparos podem ser consultados na aba Logs de disparo dentro da edição do webhook, com drawer de detalhe mostrando payload e response completos.


🕒 Observações

  • Todos os timestamps estão em UTC (formato ISO 8601 com Z no final).
  • Os payloads usam camelCase.
  • O VeHub não envia campos sensíveis (CPF/CNPJ completos só aparecem onde já são públicos — ex: documento do sacado de uma duplicata mercantil).
  • A ordem das entregas não é garantida: use dataHora e o estado atual do recurso na sua base para resolver conflitos.