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 false → true 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:
- Ao receber o webhook, persista
idWebhookem uma tabela com unique constraint. - Se o
INSERTfalhar com violação de unicidade, retorne 200 OK sem reprocessar. - 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_disparocomEhTeste = 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
ehTesteindicando 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
Zno 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
dataHorae o estado atual do recurso na sua base para resolver conflitos.