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 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 |
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 headerX-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:
- 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.
- Envelope com
etiquetaspreenchidas:isTeste = "true"eorigem = "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
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 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
dataHorae o estado atual do recurso na sua base para resolver conflitos.