Pular para o conteúdo principal

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:

TentativaEspera antes delaTempo decorrido desde a primeira tentativa
10 s
21 s1 s
35 s6 s
430 s36 s
52 min156 s
610 min756 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