Ir para o conteúdo

7.1. Visão Geral

🧾 Descrição

O VHub envia notificações via WebHook para informar parceiros e integradores sobre mudanças de estado ocorridas em lotes e títulos. Essas notificações são assíncronas e disparadas a partir do mecanismo de CDC (Change Data Capture) do banco de dados, garantindo que toda transição relevante seja entregue ao endpoint configurado, mesmo que tenha sido provocada por automações internas, retornos de administradoras ou ações manuais.

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

Cada configuração de webhook escuta exatamente um tipo de evento. Para receber múltiplos eventos no mesmo endpoint, basta criar uma configuração por tipo apontando para a mesma URL.


📦 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": {
    "isTeste": "false",
    "origem": "vhub"
  }
}

🧾 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 VHub.
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) Pares chave/valor para extensão futura. Quando isTeste = "true", indica disparo de teste vindo do painel.

🔐 Autenticação

O VHub 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 VHub 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 VHub recusa configurações com URL em http://.


✍️ Assinatura HMAC (opcional, recomendada)

Quando o webhook é configurado com um segredo HMAC, o VHub 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 VHub 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 VHub considera entrega bem-sucedida quando o consumidor responde com status HTTP 2xx.

Em caso de falha (4xx exceto 408/429, 5xx, timeout, DNS, conexão recusada), há retry exponencial com jitter:

Tentativa Delay aproximado
1 imediato
2 ~2s + jitter
3 ~6s + jitter
4 ~14s + jitter
5 ~30s + 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.

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).

Para cada evento que deseja receber, crie uma configuração com:

  • Tipo de evento (um 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 VHub 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.