Voltar ao Blog

Fundamentos

Webhook de KYC: como tratar aprovado, reprovado e pendente

A maioria dos bugs de integração de KYC está nos estados intermediários. Veja a máquina de estados completa, idempotência e verificação de assinatura.

Notebook com log de servidor e celular recebendo notificação

Um webhook de KYC não entrega só dois estados. Na API da Shiel ID, a sessão de verificação pode assumir cinco status: created, started, approved, rejected e pending. Aprovado e reprovado são os fáceis. A maioria dos bugs de integração mora nos estados intermediários, que muita integração deixa sem tratamento.

Verificação de identidade é operação assíncrona por natureza: leitura de documento, comparação biométrica, consulta a base externa e, em parte dos casos, análise humana. Nada disso cabe no tempo de uma requisição HTTP. O erro que se repete em integração é tratar o retorno da chamada de criação como se fosse a decisão. Ele não é. Ele é um protocolo de recebimento.

A máquina de estados completa

A tabela abaixo usa os status que a API da Shiel ID envia pelo webhook, descritos na documentação da API da Shiel ID. Outros fornecedores usam nomes diferentes, e alguns acrescentam estados de expiração ou cancelamento: confirme a nomenclatura exata na documentação antes de escrever código.

  • created. O que significa: A sessão de onboarding foi criada e já tem hash e link da jornada. É final?: Não. O que seu sistema deve fazer: Persistir a hash e encaminhar o usuário para o link da jornada.
  • started. O que significa: A jornada de verificação foi iniciada. É final?: Não. O que seu sistema deve fazer: Registrar o andamento, sem liberar nada.
  • pending. O que significa: A verificação ainda não tem decisão final. É final?: Não. O que seu sistema deve fazer: Exibir espera informada, sem liberar nada, e acompanhar até o status final.
  • approved. O que significa: A verificação concluiu com resultado positivo; o payload traz dados do documento e da face. É final?: Sim. O que seu sistema deve fazer: Confirmar na consulta de status, liberar o acesso e registrar o resultado com carimbo de tempo.
  • rejected. O que significa: A verificação concluiu com resultado negativo. É final?: Sim. O que seu sistema deve fazer: Bloquear, registrar o motivo e acionar a política de recurso.

Três observações economizam retrabalho. Pending não é erro nem reprovação: tratá-lo como falha joga fora usuário legítimo que ainda não recebeu decisão. Sessão que fica em created ou started e nunca chega a um status final também não é reprovação, porque um cadastro abandonado não afirma nada sobre a identidade da pessoa, e misturar os dois contamina a métrica de aprovação. E toda reprovação precisa ficar registrada do seu lado com o motivo estruturado, para separar falha de captura, divergência de dado e suspeita de fraude.

Por que o retorno síncrono nunca libera acesso

A chamada de criação devolve um identificador e um estado inicial. Se o seu código lê esse retorno e conclui que o usuário está verificado, você construiu uma porta que abre sozinha.

A decisão ainda não existe naquele momento: processamento biométrico, consulta externa e fila de exceção acontecem depois. O redirecionamento de volta ao seu site no fim da jornada, como o returnUrl na Shiel ID, também não serve para autorizar, porque passa pelo navegador do usuário, e o que passa pelo cliente é manipulável. Casos que caem em análise humana podem levar minutos ou horas, e uma integração que só entende o síncrono costuma resolver o impasse liberando acesso.

A regra prática: o estado que autoriza é sempre o que chegou pelo webhook e foi confirmado por uma consulta ao endpoint de status do fornecedor. Na API da Shiel ID, essa consulta é o GET /getHashData, que devolve status, score, currentStep e os carimbos de criação e de conclusão da hash.

Como implementar o consumidor de webhook em sete passos

1. Exponha um endpoint dedicado e idempotente. Ele recebe, valida, grava e responde. Nada de regra de negócio dentro do handler.

2. Autentique a origem antes de confiar no corpo. Quando o fornecedor assina o payload, o padrão descrito na especificação Standard Webhooks é HMAC com SHA-256, com identificador do evento, carimbo de tempo e assinatura no cabeçalho. Recalcule sobre o corpo bruto, compare em tempo constante e rejeite o que não bater, inclusive evento com carimbo fora de uma janela curta. Se o fornecedor não assina, trate o webhook apenas como aviso e decida pela consulta de status do passo 6.

3. Responda 2xx rápido e processe depois. Confirme o recebimento e empilhe o evento. Processamento pesado dentro do handler gera timeout e, se o fornecedor faz retentativa, você passa a receber o mesmo evento várias vezes.

4. Deduplique cada evento. Use o identificador do evento quando o fornecedor enviar um. Na falta dele, a combinação do identificador da verificação, como a hash na Shiel ID, com o status recebido serve de chave. Guarde a chave com restrição de unicidade. Se já existe, descarte e responda 2xx. Reentrega é comportamento esperado, não anomalia.

5. Descarte evento fora de ordem. Guarde a versão ou o carimbo de tempo do último evento aplicado a cada verificação e ignore qualquer evento anterior. Sem isso, uma reentrega de "pending" reverte um "approved" já registrado.

6. Confirme na fonte antes de agir. Ao receber um estado final, consulte o endpoint de status com o identificador da verificação e aja sobre a resposta dessa consulta. O webhook é gatilho, a consulta é a verdade.

7. Registre transição, não só estado atual. Grave cada mudança com origem, carimbo de tempo, motivo e payload. Esse histórico sustenta auditoria, suporte ao usuário recusado e investigação de fraude.

O passo a passo de integração está em como integrar uma API de KYC, e a referência completa está na documentação da API da Shiel ID.

Idempotência, reentrega e ordem: os três que quebram em produção

  • Evento duplicado. Como se manifesta: O mesmo resultado processado duas vezes, notificação enviada em dobro. Tratamento: Chave de unicidade pelo identificador do evento, com descarte silencioso.
  • Reentrega após timeout. Como se manifesta: Pico de eventos repetidos depois de uma instabilidade sua. Tratamento: Responder 2xx antes de processar e enfileirar o trabalho.
  • Ordem fora de sequência. Como se manifesta: Estado final sendo sobrescrito por estado intermediário antigo. Tratamento: Comparação de versão ou carimbo de tempo antes de aplicar a transição.
  • Evento perdido. Como se manifesta: Verificação parada em status não final para sempre. Tratamento: Rotina de reconciliação periódica que consulta o status de tudo que não é final.
  • Origem não verificada. Como se manifesta: Qualquer um posta "approved" no seu endpoint. Tratamento: Verificação de assinatura quando o fornecedor assina e, sempre, confirmação na consulta de status antes de agir.

A reconciliação é um item que muita equipe deixa para depois. Entrega de webhook não é certa, e uma varredura periódica dos registros não finais fecha o buraco sem depender de entrega perfeita.

O estado que ninguém modela: a reverificação

A máquina de estados descrita aqui vale para uma verificação. A conta, porém, é reverificada ao longo da vida: troca de dispositivo, mudança de dado cadastral, operação de valor atípico, alteração societária no caso de empresa. Na API da Shiel ID, por exemplo, a validação facial em transação cria uma nova hash, associada ao último KYC aprovado do usuário.

Se o seu modelo de dados trata verificação como atributo booleano do usuário, cada reverificação sobrescreve a anterior e o histórico se perde. Modele verificação como entidade própria, com muitas por usuário, cada uma com sua máquina de estados, seu motivo de disparo e seu resultado preservado. É uma decisão de schema barata no início e cara de corrigir depois.

O que fazer agora

  1. Liste os estados que o seu código trata hoje e compare com os cinco da tabela. Todo estado não tratado vira caso preso em produção.
  2. Verifique se o seu handler valida assinatura sobre o corpo bruto e rejeita evento com carimbo de tempo antigo.
  3. Adicione restrição de unicidade pelo identificador do evento e teste reenviando o mesmo payload três vezes seguidas.
  4. Implante a rotina de reconciliação para registros que passam de um limite de tempo sem estado final.
  5. Confirme com o fornecedor os nomes exatos de evento e de estado, a política de retentativa e o prazo de retenção do histórico.

Perguntas frequentes

Posso liberar o acesso com base na resposta da chamada de criação? Não. Esse retorno é um protocolo de recebimento, não uma decisão. O estado que autoriza é o que chega pelo webhook e é confirmado por consulta ao endpoint de status.

O que fazer quando o mesmo evento chega duas vezes? Descartar e responder 2xx. Reentrega é comportamento esperado de qualquer fornecedor. A proteção é uma chave de unicidade pelo identificador do evento, gravada antes do processamento.

Como evitar que um evento antigo sobrescreva um resultado final? Guardando a versão ou o carimbo de tempo da última transição aplicada e ignorando qualquer evento anterior a ela. Sem essa comparação, uma reentrega reverte um estado final.

Pending deve contar como reprovação? Não. É um status intermediário, sem decisão final. Contá-lo como reprovação distorce a métrica e descarta usuário legítimo que ainda pode ser aprovado.

E se o webhook não chegar? Para isso existe a reconciliação: uma rotina periódica consulta o status de todas as verificações que passaram do tempo esperado sem estado final e atualiza o registro.

Para avaliar como isso se aplica à sua operação, fale com o time comercial da Shiel ID.