Reentregas
A entrega é ao menos uma vez. Planeje para duplicatas, não para exatamente-uma-vez.
Como uma entrega é produzida
Quando um veredicto é alcançado — pelo pipeline ou por um revisor humano no painel —, o evento é gravado em uma caixa de saída de entregas na mesma transação de banco de dados que o próprio veredicto. Um serviço separado, veridia-webhooks, lê essa caixa de saída e faz as chamadas HTTP.
É essa estrutura que dá a você a garantia que importa: um veredicto não pode existir sem o seu evento, e um evento não pode existir para um veredicto que sofreu rollback. É também por isso que a requisição não chega de dentro do processo que calculou o veredicto — um despachante pode reentregar e aplicar backoff; um handler de requisição esperando pelo seu endpoint não pode.
O cronograma
Seis tentativas. Uma imediata, depois cinco reentregas:
| Tentativa | Espera antes dela | Tempo decorrido desde a primeira tentativa |
|---|---|---|
| 1 | — | 0 s |
| 2 | 1 s | 1 s |
| 3 | 5 s | 6 s |
| 4 | 30 s | 36 s |
| 5 | 2 min | 156 s |
| 6 | 10 min | 756 s |
Janela total: 756 segundos, cerca de 12,6 minutos. É esse o tempo que seu endpoint pode ficar fora do ar antes que um evento pare de ser reentregue por conta própria. Dimensione janelas de manutenção contra 12,6 minutos, não contra o número bruto de tentativas.
Uma tentativa é reentregue em caso de 5xx, timeout (10 s no total, 5 s para conectar), falha de conexão ou 408/429. Qualquer outro 4xx é falha permanente e interrompe o cronograma imediatamente — um 401 ou um 404 não vai virar 200 na quarta tentativa, e insistir só atrasa a descoberta da configuração errada.
Toda tentativa é assinada de novo
O despachante recalcula o HMAC a cada tentativa, então o t em Veridia-Signature é o horário daquela tentativa, não da primeira.
Isso responde à pergunta que o cronograma acima naturalmente levanta: se a última reentrega pode chegar 12,6 minutos depois de o evento ter sido criado, e a tolerância de replay recomendada é de 300 segundos, as reentregas tardias são rejeitadas como replays?
Não. A sexta tentativa chega com um timestamp de poucos segundos. Mantenha a tolerância em 300 segundos. Ampliá-la para cobrir a janela de reentrega não traz benefício algum e triplica o intervalo em que uma requisição capturada pode ser reenviada contra você.
Duplicatas são normais
A duplicata comum não é uma falha. É um handler que processou o evento corretamente e depois demorou demais para responder — o trabalho foi feito, a confirmação perdeu o timeout, e o despachante, sem forma de distinguir isso de um endpoint morto, tenta de novo.
Deduplique por id:
const eventId = request.headers.get('Veridia-Event-Id'); // === payload.id
if (await alreadyProcessed(eventId)) return ok();
id é o valor evt_*. Ele é estável nas seis tentativas de um evento e distinto entre eventos — inclusive entre dois eventos da mesma verificação, que é exatamente o caso em que uma chave baseada em verificationId erra. Veja Idempotência.
Registre o id como parte da mesma transação que aplica o efeito. Marcá-lo como processado antes do trabalho arrisca perder o evento; marcá-lo depois arrisca fazer o trabalho duas vezes.
Depois da última tentativa
A entrega é marcada como failed e para. Ela não é descartada: permanece na caixa de saída com seu status, o último código de status HTTP e o último erro, visível no painel em Webhooks.
Um operador pode recolocar na fila uma entrega que falhou a partir dali. Recolocar na fila zera o contador de tentativas, então o evento ganha de novo a janela completa de seis tentativas. Somente entregas em estado failed podem ser recolocadas na fila — um evento ainda percorrendo seu cronograma não pode ser jogado de volta ao início por um clique impaciente.
Um evento recolocado na fila carrega o mesmo id. Se o seu handler de fato o processou antes de falhar em confirmar, sua checagem de deduplicação vai reconhecê-lo e descartá-lo. Esse é o comportamento pretendido.
O painel também mostra a própria caixa de saída: uma linha por evento com seu estado atual, que é a visão que você quer quando a pergunta é "esse veredicto chegou ao meu cliente e, se não chegou, por quê".
A proteção da URL
A URL do endpoint é validada duas vezes, e ambas as checagens existem porque um corpo de webhook carrega fieldsExtracted — nome completo, número do documento, data de nascimento.
No momento de salvar, o painel rejeita qualquer URL que não comece com https://. Não há modo HTTP, não há exceção para localhost, não há flexibilização em modo de teste. Dados de identidade não trafegam pela rede em texto claro.
No momento do envio, o endereço resolvido é checado contra uma proteção contra SSRF, que rejeita:
- faixas privadas (
10.0.0.0/8,172.16.0.0/12,192.168.0.0/16) - loopback (
127.0.0.0/8,::1) - link-local, incluindo endpoints de metadados de nuvem (
169.254.0.0/16) - carrier-grade NAT (
100.64.0.0/10)
A resolução é rechecada a cada envio, e não uma única vez no momento de salvar. Um hostname que resolvia para um endereço público quando você o configurou pode resolver para 169.254.169.254 depois — DNS rebinding — e uma proteção que só rodasse no momento da configuração não perceberia.
Se você está se perguntando como desenvolver com isso: você usa um túnel. Veja testando localmente.
Recuperando eventos que você perdeu
Se o seu endpoint ficou fora do ar por mais de 12,6 minutos e você não recolocou na fila a tempo, os veredictos ainda existem. Reconcilie pela API:
curl https://api.xxuxe.online/v1/verify/vf_AG07CDWRRFQV4T05ZXG2 \
-H "Authorization: Bearer qv_sec_YOUR_SECRET_KEY"
Isto exige uma chave secreta (qv_sec_ em produção, qv_sect_ em teste). Uma chave publicável retorna 401 secret_key_required.
Ler um veredicto dessa forma retorna status e verdict como campos separados. status: "completed" significa que o pipeline terminou — não significa que a pessoa passou. Ramifique por verdict.
Para um job de reconciliação você precisa da lista de IDs de verificação que está faltando, o que significa registrar cada verificationId no momento do /v1/verify/init, antes de qualquer webhook existir. Se você não está armazenando esse mapeamento hoje, é essa a lacuna a fechar primeiro: sem ele não há como enumerar o que você perdeu.
O que vem a seguir
- Verificação de assinatura — por que a tolerância continua em 300 segundos
- Exemplos — handlers com idempotência já implementada
- Tipos de evento — o contrato do payload
- Webhooks — de volta ao índice da seção