Visão geral
Esta página cobre a configuração do endpoint e o tratamento da requisição. Para o contrato do payload, veja Tipos de evento; para garantias de entrega, veja Reentregas.
Por que webhooks em vez de polling
| Aspecto | Polling GET /v1/verify/{id} | Webhooks |
|---|---|---|
| Latência do veredicto até o seu código | Seu intervalo de polling | Uma requisição, assim que o veredicto existe |
| Chamadas de API por verificação | 3–30 | 1 de entrada |
| Chave necessária | Chave secreta, somente no servidor | Nenhuma — em vez disso você verifica uma assinatura |
| Decisões de revisão humana | Você precisa continuar fazendo polling indefinidamente | Entregues como um segundo evento |
| Se o seu serviço estiver fora do ar | Você não perde nada, mas precisa continuar fazendo polling | Reentregue por ~12,6 minutos, depois pode ser recolocado na fila |
A última linha é a que mais importa. Um veredicto review é final até que uma pessoa o decida, o que pode levar horas. Fazer polling por ele significa ou fazer polling para sempre, ou desistir e deixar o usuário travado. O webhook da decisão do revisor chega quando chegar.
Configuração
Existe um webhook por tenant, configurado no painel em Settings → Webhook. Não há lista de endpoints, não há assinatura por evento e não há separação test/live para webhooks.
O formulário tem dois campos:
| Campo | Regras |
|---|---|
| URL | Precisa começar com https://. Caso contrário, é rejeitada antes de salvar. Sujeita também a uma proteção contra SSRF. |
| Secret | Você escolhe. Mínimo de 24 caracteres. Deixe o campo em branco para manter o segredo atual. |
O segredo não é gerado para você e nunca é exibido de volta — o campo é somente escrita. Gere um com entropia real e guarde-o onde sua aplicação possa lê-lo:
openssl rand -hex 32
Uma vez salvo, a próxima verificação que chegar a um veredicto fará POST na sua URL.
Salvar um novo segredo substitui o antigo na hora. Não existe janela de tolerância com dois segredos. Publique o novo segredo na sua aplicação primeiro, e só então salve-o no painel — veja rotação.
A requisição
POST /webhooks/veridia HTTP/1.1
Host: yourapp.com
Content-Type: application/json
Veridia-Signature: t=1753142348,v1=4f8a3b9c01ee5d2f...
Veridia-Event: verification.approved
Veridia-Event-Id: evt_9f2c41ab7d8e4c05b6a3e17f2d904c8b
{"id":"evt_9f2c41ab7d8e4c05b6a3e17f2d904c8b","type":"verification.approved", ...}
| Cabeçalho | Uso |
|---|---|
Veridia-Signature | t=<segundos unix>,v1=<hmac-sha256 em hex>. Verifique isto antes de confiar em qualquer coisa. |
Veridia-Event | O tipo do evento, espelhando o type do corpo. Conveniente para roteamento/métricas; não é autenticado por si só — o que o HMAC cobre é o corpo. |
Veridia-Event-Id | O mesmo valor do id do corpo. Permite consultar seu armazenamento de deduplicação antes de fazer o parse do corpo. |
A assinatura cobre os bytes brutos do corpo. Não re-serialize o JSON antes de calcular o HMAC — a ordem das chaves e os espaços em branco fazem parte do que foi assinado. Veja Verificação de assinatura.
Responda 2xx em até 10 segundos
O timeout de entrega é de 10 segundos (5 segundos para conectar). Qualquer coisa mais lenta conta como tentativa falha e é reentregue, o que significa que seu handler lento-mas-bem-sucedido será chamado a fazer o mesmo trabalho de novo.
Confirme primeiro, trabalhe depois:
app.post('/webhooks/veridia', express.raw({ type: 'application/json' }), (req, res) => {
// 1. Verifique a assinatura — rápido, e a única coisa que precisa acontecer inline.
if (!verifyVeridiaSignature(req.header('Veridia-Signature'), req.body, SECRET)) {
return res.status(401).send('invalid signature');
}
// 2. Confirme. Tudo daqui para frente é no seu próprio tempo.
res.status(200).send('ok');
// 3. Faça o trabalho. Falhas aqui são sua responsabilidade reprocessar — a
// entrega já foi confirmada e não será reenviada.
const payload = JSON.parse(req.body.toString('utf8'));
enqueue(payload).catch(err => logger.error({ err, eventId: payload.id }));
});
Esse último comentário é o trade-off que você está aceitando. Confirmar cedo significa que uma queda entre o passo 2 e o passo 3 perde o evento, então persista o payload de forma durável (uma linha, uma mensagem em fila) como a confirmação, e faça o processamento real a partir daí. Os exemplos seguem todos esse padrão.
Códigos de status sobre os quais agimos
| Sua resposta | O que acontece |
|---|---|
2xx | Entregue. Pronto. |
5xx, timeout, erro de conexão | Reentregue conforme o cronograma de backoff |
408, 429 | Reentregue |
Qualquer outro 4xx | Falha permanente. Não é reentregue — um 401 ou 404 significa que repetir a requisição idêntica não pode dar certo. |
Uma falha permanente ainda aparece no painel e pode ser recolocada na fila manualmente depois que você corrigir a causa.
status não é verdict
Isto se aplica à API de polling, e não aos webhooks, mas é o erro mais caro que a API deste produto torna possível, e handlers de webhook o herdam sempre que fazem verificação cruzada com GET /v1/verify/{id}:
statusé o estado do pipeline:queued,processing,completed,failed.verdicté o resultado:approved,review,rejected.
status === "completed" significa que o pipeline rodou, não que a pessoa passou. Ramificar por ele para conceder acesso admite todo candidato rejeitado. Payloads de webhook não têm campo status nenhum — eles carregam type e verdict, que são ambos resultados. Ramifique por esses.
Testando localmente
Webhooks não conseguem alcançar localhost. Isto não é uma configuração que você possa relaxar: a URL precisa ser https://, e a proteção contra SSRF rejeita endereços de loopback e privados no momento do envio. Um túnel é a única forma de receber entregas reais em desenvolvimento.
| Ferramenta | Observações |
|---|---|
| ngrok | Plano gratuito; a escolha habitual |
| localtunnel | Gratuito, código aberto |
| Cloudflare Tunnel | Gratuito; melhor para um ambiente de desenvolvimento de longa duração |
ngrok http 3000
# Forwarding https://abc123.ngrok.io -> http://localhost:3000
# Coloque https://abc123.ngrok.io/webhooks/veridia em Settings → Webhook
Para exercitar o handler sem rodar uma verificação, reenvie um corpo com uma assinatura nova — veja o script de replay. Assine exatamente os bytes que você envia.
Checklist
- Verifique
Veridia-Signatureantes de confiar no corpo. Sempre. - Rejeite se
|agora - t| > 300segundos. - Responda
2xxem até 10 segundos; persista primeiro, processe depois. - Deduplique por
id(ou pelo cabeçalhoVeridia-Event-Id). A entrega é ao menos uma vez. - Faça o
switchemtype. Não emevent— não existe campoevent. - Leia
scorescom chaves em snake_case, e faça verificação de null emscores.liveness. - Trate
fieldsExtractedcomo dado pessoal, inclusive nos seus logs. - Registre
ideverificationIdem cada entrega — são eles que o suporte vai pedir.
O que vem a seguir
- Verificação de assinatura — o algoritmo, com código
- Tipos de evento — o contrato completo do payload
- Reentregas — backoff, garantias, recuperação
- Exemplos — implementações completas de handlers