--- title: 4.1. Visão Geral url: https://docs.vehub.com.br/Emiss%C3%A3o%20de%20Ativos/API/Bureau/4.%20Notifica%C3%A7%C3%B5es%20-%20WebHook/4.1.%20Vis%C3%A3o%20Geral/ --- # 4.1. Visão Geral ## Descrição Quando uma consulta ao SCR solicitada pela API termina, VeTrust envia uma notificação via **WebHook** para a URL configurada para o seu grupo econômico. Assim você não precisa ficar consultando o status em laço. A notificação é um **gatilho**: ela traz só o `idConsulta` e o status. O resultado é lido em [3.3. Consultar Resultado](../3.%20Consulta%20SCR/3.3.%20Consultar%20Resultado.md). !!! tip "O webhook acelera, mas não substitui a leitura" Uma entrega pode falhar de vez — o seu servidor fora do ar durante todas as tentativas, por exemplo. Mantenha o *polling* descrito em [1.3. Operações Assíncronas](../1.%20Início/1.3.%20Operações%20Assíncronas.md) como plano B para toda consulta que não recebeu evento. --- ## Tipos de Eventos | Slug (`tipoEvento`) | Página | Quando é disparado | |---|---|---| | `bureau.scr.concluida` | [4.2](4.2.%20Eventos%20de%20Consulta%20SCR.md) | A consulta terminou `Concluída` — pode ter meses em falha | | `bureau.scr.falhou` | [4.2](4.2.%20Eventos%20de%20Consulta%20SCR.md) | A consulta terminou em `Falha` | Só as consultas **solicitadas pela API** geram eventos. As feitas pela tela da plataforma não geram. > Uma mesma **configuração de webhook** pode estar inscrita em **um ou mais** tipos de evento — os > de consulta SCR e os de outros produtos que você contrate. 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 fica em `dados`. ```json { "idWebhook": "019e0892-8d99-778c-9fa5-47bd07cd9ffb", "tipoEvento": "bureau.scr.concluida", "dataHora": "2026-10-23T13:00:41.512Z", "grupoEconomico": "MeuGrupo", "dados": { }, "etiquetas": null } ``` ### Detalhamento dos Campos do Envelope | Campo | Tipo | Descrição | |-------|------|-----------| | `idWebhook` | string (GUID) | Identificador único do disparo. **Use para deduplicação.** Também enviado no header `X-Idempotency-Key` | | `tipoEvento` | string | Slug do evento. Sempre ASCII. Também no header `X-Event-Type` | | `dataHora` | string (ISO 8601, UTC) | Momento em que o evento foi gerado | | `grupoEconomico` | string | Grupo econômico da consulta | | `dados` | object | Payload específico do tipo de evento | | `etiquetas` | object / null | Pares chave/valor para extensão. Em disparos reais vem `null`; em disparos de **teste** vem `{ "isTeste": "true" }` | --- ## Autenticação da entrega A requisição de entrega chega ao seu servidor autenticada conforme o **modo** combinado na configuração: | Tipo | Valor | Headers adicionados | Observações | |---|---|---|---| | `SemAutenticacao` | 1 | nenhum | Use apenas em endpoints protegidos por outra camada | | `ApiKey` | 2 | `X-Api-Key: ` | Chave fixa, simples e amplamente compatível | | `Basic` | 3 | `Authorization: Basic base64(clientId:clientSecret)` | Padrão HTTP Basic | | `BearerTokenFixo` | 4 | `Authorization: Bearer ` | Token estático configurado uma vez | | `ClientIdClientSecret` | 6 | `X-Client-Id` e `X-Client-Secret` | Headers separados | > **OAuth2 não é suportado** como modo de entrega: não é possível configurar a obtenção de token por > `client_credentials` no seu provedor. Use um dos modos acima — de preferência combinado com a > assinatura HMAC. --- ## Assinatura HMAC (opcional, recomendada) Quando o webhook é configurado com um **segredo HMAC** (no mínimo 16 caracteres), cada disparo é assinado com **HMAC-SHA256** sobre o **corpo bruto da requisição**, e o hash chega no header: ```http X-Webhook-Signature: sha256= ``` ### Validação no consumidor (Node.js) ```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=/, ''); if (recebido.length !== esperado.length) { return false; } return crypto.timingSafeEqual( Buffer.from(esperado, 'hex'), Buffer.from(recebido, 'hex') ); } ``` ### Validação no consumidor (C#) ```csharp 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 o JSON antes da validação causam divergência. --- ## Headers Padrão | Header | Exemplo | Descrição | |---|---|---| | `Content-Type` | `application/json; charset=utf-8` | Sempre JSON UTF-8 | | `X-Idempotency-Key` | `019e0892-8d99-778c-9fa5-47bd07cd9ffb` | Mesmo valor de `idWebhook` no envelope | | `X-Event-Type` | `bureau.scr.concluida` | Slug do evento, útil para roteamento | | `X-Webhook-Signature` | `sha256=fa1be9...` | Presente apenas quando há segredo HMAC configurado | | `Authorization` ou afins | depende do modo | Ver a tabela de autenticação | --- ## Requisitos do seu endpoint - **HTTPS com certificado válido.** URLs `http://` são recusadas na configuração. - **Endereço público.** A entrega só é feita para IPs públicos: endereços de rede interna, *loopback* e similares são bloqueados, inclusive quando o nome resolve para eles depois. - **Sem redirecionamento.** Um `3xx` não é seguido: aponte a configuração para a URL final. - Se o seu firewall exige lista de origem, peça ao nosso time os **IPs de saída** do ambiente. --- ## Retry e Entrega A entrega é considerada bem-sucedida quando o seu servidor responde com **status HTTP 2xx**. Em falha transitória — **5xx**, **408**, **429**, tempo esgotado, DNS ou conexão recusada — há **retry exponencial com jitter**. Os demais erros **4xx não são retentados**. | Tentativa | Intervalo 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 é reenviado** — nem automaticamente, nem a pedido. A consulta continua disponível para leitura: recupere o resultado pelo *polling*. Cada tentativa espera a resposta por no máximo **10 segundos**. O que decide o resultado é o status HTTP; o corpo da sua resposta é guardado só como diagnóstico no log de disparo, até 4 KB. > **Responda 2xx assim que receber**, idealmente em poucos segundos, e processe de forma assíncrona. > Ler a consulta dentro do handler do webhook alonga a resposta e leva a *timeout* e *retry* > desnecessários. --- ## Deduplicação Como os *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, persista `idWebhook` em uma tabela com *unique constraint*. 2. Se o `INSERT` falhar com violação de unicidade, responda **200 OK** sem reprocessar. 3. Senão, processe normalmente. Cada consulta gera **no máximo um evento terminal** — `bureau.scr.concluida` ou `bureau.scr.falhou`. Por isso, `idConsulta` + `tipoEvento` também serve como chave de deduplicação no seu domínio. --- ## A ordem das entregas não é garantida Eventos de consultas diferentes podem chegar fora da ordem em que as consultas terminaram, e um evento pode chegar **antes** de você processar a resposta `202` da solicitação. Padrão recomendado: ao receber um evento, **leia a consulta** (`GET /bureaus/scr/consultas/{idConsulta}`) e use o estado devolvido. Use o evento como **gatilho**, não como fonte da verdade. --- ## Configuração A configuração do webhook — URL, eventos inscritos, modo de autenticação e segredo HMAC — é feita pelo **nosso time**, a seu pedido. Para cadastrar ou alterar, envie ao seu contato comercial a URL, os eventos desejados e o modo de autenticação; os segredos são combinados por canal seguro. A configuração vale para o **grupo econômico**: se o seu grupo usa mais de uma credencial, a mesma URL recebe os eventos das consultas de todas elas. Como o evento só leva o `idConsulta` e o status, o `GET` de uma consulta de outra credencial responde `404`. ### Endpoints de consulta A API Pública oferece rotas **somente de leitura** para você conferir a configuração e diagnosticar entregas: | Método | URL | Descrição | |--------|-----|-----------| | ![GET](https://img.shields.io/badge/GET-green) | `/webhooks/eventos` | Catálogo de eventos, com o `slug` de cada um e o campo `publicado` | | ![GET](https://img.shields.io/badge/GET-green) | `/webhooks` | Configurações do seu grupo econômico, sem os segredos | | ![GET](https://img.shields.io/badge/GET-green) | `/webhooks/{idWebhook}` | Uma configuração: URL, eventos inscritos, modo de autenticação e se há segredo HMAC (`temSegredoHmac`) | | ![GET](https://img.shields.io/badge/GET-green) | `/webhooks/{idWebhook}/disparos` | Tentativas de entrega, paginadas, com data, tentativa, duração, status HTTP, payload enviado e resposta recebida | Aqui, `{idWebhook}` é o identificador **numérico da configuração** devolvido em `GET /webhooks` — não o `idWebhook` (GUID) do envelope de um disparo. ### Logs de disparo ```bash curl -X GET "https://api.vehub.com.br/public/v1/webhooks/12/disparos?pagina=1&quantidade=50" \ -H "Authorization: Bearer {seu_token}" \ -H "GrupoEconomico: {seu_grupo_economico}" ``` Use esta rota quando um evento não chegou: cada tentativa é uma linha, e as cinco tentativas de um mesmo evento aparecem com o mesmo identificador de disparo. Os headers de autenticação e a assinatura vêm mascarados. ### Disparo de teste Durante a homologação, peça ao nosso time um **disparo de teste**: ele envia um payload realístico do evento escolhido, com identificadores **fictícios** e a etiqueta `isTeste = "true"` no envelope, numa tentativa só, sem *retry*. Prepare o seu consumidor para identificar `etiquetas.isTeste === "true"` e **não** tratar o evento como real: o `idConsulta` do teste é fictício — não o leia nem o processe. --- ## Observações - Todos os timestamps estão em **UTC** (ISO 8601 com `Z`). - Os payloads usam **camelCase**. - Os eventos de consulta SCR **não** transportam o documento, o período nem o resultado. Para isso, leia a consulta.