A atividade de autenticação e a entrega de webhooks correspondente, que depende da versão, estão documentadas em Eventos de autenticação e webhooks. Não registre tipos de evento de autenticação não listados na API atual de destinos.
Visão geral
Webhooks são a forma como a Didit envia atualizações em tempo real para o seu backend sempre que uma sessão de verificação, uma sessão de empresa, um usuário (vendor user), uma empresa (vendor business) ou uma transação muda de estado. Ao inscrever um ou mais destinos nos eventos que interessam a você, é possível reagir no momento em que um resultado fica pronto, normalmente poucos segundos depois de o usuário em verificação concluir o fluxo. Webhooks são o padrão de integração recomendado. Consultar periodicamenteGET /v3/session/{id}/decision/ é suportado como alternativa, mas é mais lento, consome mais requisições e deixa de fora eventos como data.updated, transaction.status.updated e alterações no nível de entidade, que são emitidos apenas por webhooks. Trate a entrega de webhooks como a fonte da verdade e faça consultas apenas na inicialização a frio ou para reconciliação.
Este capítulo da aula de integração da Didit Academy retoma a partir da visão geral de webhooks.
A aula de integração da Academy aborda webhooks a partir de 3:12: como adicionar um destino, escolher eventos e o segredo de assinatura que você vai verificar a seguir.
Usuários de Cloudflare / WAF. A Didit entrega webhooks a partir do IP estático
18.203.201.92 com User-Agent: DiditWebhook/2.0 +https://didit.me. Se a sua infraestrutura de borda bloquear clientes desconhecidos, libere esse IP em Security > WAF > Tools > IP Access Rules e escolha Allow para o hostname receptor.Tipos de evento
Use o valor exato no arraysubscribed_events de um destino. Não há curinga: liste cada família que você quer receber. Você pode distribuir os eventos entre vários destinos.
Os valores desta tabela são o conjunto completo de eventos que você pode assinar: não existe
session.status.updated, kyc.completed nem nada parecido. A única outra entrega que a Didit faz é o callback kyb.registry_search.resolved, sem assinatura, que vai para a webhook_url informada em cada requisição em POST /v3/kyb/search/, nunca para um destino.
Formato do payload
Envelope
Todo webhook compartilha o mesmo envelope. Os campos são sempre serializados comsort_keys=True e separadores compactos (, / :), para que o mesmo payload gere a mesma assinatura nos dois lados.
Nos eventos de sessão (
status.updated, data.updated em sessões comuns e de empresas), o envelope também traz:
Exemplo de envelope de sessão
Arrays no plural da V3 (importante)
Dentro dedecision, cada resultado por recurso é um array no plural, para que um único fluxo possa incluir várias instâncias do mesmo recurso (por exemplo, várias verificações de identidade em documentos diferentes):
node_id correspondente ao grafo do fluxo, além de um status por recurso. Não leia os campos legados no singular da V2 (id_verification, nfc, liveness, face_match, phone, email, poa, aml, ip_analysis, database_validation). Eles só aparecem se um destino estiver fixado explicitamente em webhook_version: "v2".
Os resultados de Assinatura eletrônica ficam em signatures[], presente somente quando o fluxo tem uma etapa de Assinatura eletrônica: um item por signatário, com o signatário, a confirmação por código, o certificado, o carimbo de data e hora, os hashes e caminhos de download de cada documento e o PDF de evidências. Não existe um evento de assinatura separado: o status.updated indica quando lê-los. Consulte o relatório de Assinatura eletrônica.
Os resultados de Verificação bancária ficam em bank_verifications[], com um item por etapa de Verificação bancária. O method de cada item indica como a conta foi verificada: open_banking (login no banco), account_lookup (verificação da conta) ou uma alternativa statement ou microdeposits. O valor é null até que uma conta seja avaliada. Quando a pessoa escolheu Continue without your bank, continued_without_bank é true e continuation indica o motivo. Nesse caso, method é none, a menos que uma tentativa tenha sido avaliada antes: consulte Quando a pessoa não consegue verificar o banco. Uma conta verificada por verificação da conta não lê dados financeiros, então financial_data.not_collected lista os saldos, transações ou rendimentos que a etapa solicitou. Consulte Métodos de verificação bancária.
Bloco do Investigator
Umadecision V3 de uma sessão de usuário ou de empresa também traz investigator: a revisão mais recente da sessão feita pelo Investigator, ou null quando a sessão nunca teve uma.
Os recursos de coleta de documentos, Documentos KYB e Document AI, não têm um array dedicado no plural: eles aparecem na lista
features[] da decisão como { "feature": "DOCUMENT_AI", "node_id": "…" } (uma entrada por nó). Veja Document AI.
As decisões de Verificação de usuários também trazem um único objeto biometric_consent: se a sessão exigiu consentimento biométrico explícito, o trigger, o status da resposta e a evidência correspondente. status: "pending" com reason: "BIOMETRIC_CONSENT_UNSUPPORTED_SDK" significa que um SDK nativo anterior à versão 4.10.0 capturou um rosto sem a etapa de consentimento. Veja Consentimento biométrico.
Chaves do método de Verificação de identidade
Cada item deid_verifications[] informa qual método de Verificação de identidade o gerou, para que um handler possa ramificar conforme a evidência que de fato recebeu:
Os handlers existentes continuam funcionando sem alterações: uma sessão de documento define
verification_method como document e deixa os demais como null. Os tipos campo a campo estão em Modelos de dados.
Sessão de carteira aprovada (status.updated)
Payloads de eventos de entidade
ACTIVE, FLAGGED ou BLOCKED. user.data.updated e business.data.updated mantêm o campo status e adicionam um array changed_fields e um mapa changes no formato { field: { previous, current } }. Os eventos business.* usam vendor_business_id em vez de vendor_user_id.
Payloads de eventos de transação
Os webhooks de transação usam a mesma infraestrutura de distribuição (fan-out) e o mesmo envelope, mas identificam a transação em vez de uma sessão:action_required é null ou a ação pendente do usuário (type wallet_ownership ou verification_session, com a respectiva url) descrita em Ações exigidas do usuário.
Novas transações têm APPROVED como padrão. Se uma regra, lista de bloqueio ou fluxo alterar o status durante a criação, o payload inicial de transaction.created reflete esse status final. transaction.status.updated traz o mesmo formato com o status atualizado. Os status de transação são APPROVED, IN_REVIEW, DECLINED ou AWAITING_USER; severity é UNKNOWN / LOW / MEDIUM / HIGH / CRITICAL e direction é INBOUND / OUTBOUND. transaction.created e transaction.status.updated são entregues apenas para aplicações em produção. As trocas de Travel Rule em sandbox emitem, sim, travel_rule.status.updated com environment: sandbox.
Callback de busca no registro KYB
O fluxo assíncrono dePOST /v3/kyb/search/ entrega um payload extra que não é um evento de destino: quando você passa uma webhook_url na requisição de busca, a Didit envia um POST com o callback kyb.registry_search.resolved para essa URL assim que a consulta ao registro é concluída. Ele difere dos webhooks de destino em três aspectos:
- Ele é sem assinatura: não tem cabeçalhos
X-Signature*. Em vez disso, a Didit o marca comX-Didit-Unsigned-Callback: true; valide-o comparando orequest_idcom o ID retornado pela requisição de busca. - O nome do evento fica em
event_type(e não emwebhook_type), e não háapplication_idnemenvironment. timestampecreated_atsão inteiros epoch, como nos outros webhooks, mas diferentes docreated_atem ISO do endpoint de busca.
timestamp e continuam sem assinatura). Veja a API de registro KYB para o schema completo da lista de candidatos.
Verificação de assinatura
Todo webhook é assinado com HMAC-SHA256 usando asecret_shared_key do destino. São enviados três cabeçalhos de assinatura para que você escolha aquele que funcione com sua stack:
Cabeçalhos que a Didit sempre envia:
Por 24 horas após a rotação do segredo de assinatura de um destino, as entregas também incluem
X-Signature-Previous, X-Signature-V2-Previous e X-Signature-Simple-Previous (consulte Rotação do segredo de assinatura).
Rotação do segredo de assinatura
Você faz a rotação do segredo de assinatura de um destino no Business Console. Por 24 horas após uma rotação, toda entrega para esse destino inclui mais três cabeçalhos, gerados com o segredo que foi substituído:
Um handler que aceita a correspondência tanto em um cabeçalho de assinatura quanto no seu cabeçalho
-Previous continua verificando enquanto você implanta o novo segredo: o segredo antigo corresponde ao cabeçalho -Previous, e o novo corresponde ao cabeçalho normal.
Os exemplos de código abaixo fazem isso.
Implante o novo segredo dentro das 24 horas, porque, depois disso, somente o novo segredo valida as assinaturas.
O campo previous_secret_expires_at em GET /v3/webhook/destinations/{destination_uuid}/ informa quando as 24 horas terminam.
Novas tentativas e reenvios são assinados novamente com o segredo atual do destino no momento do envio, e incluem os cabeçalhos -Previous somente enquanto durarem as 24 horas.
Fazer uma nova rotação dentro das 24 horas desativa imediatamente o segredo mais antigo: somente o segredo que era o atual até a nova rotação continua validando as assinaturas dos cabeçalhos -Previous, por 24 horas a partir da nova rotação.
Os cabeçalhos -Previous existem somente para destinos de webhook.
A secret_shared_key no nível da aplicação, que assina o webhook_url legado, não tem período de sobreposição: fazer a rotação dela com rotate_secret_key em PATCH /v3/webhook/ invalida o segredo antigo imediatamente.
X-Signature-Simple está obsoleto
Esta é uma restrição de compatibilidade, não algo que possamos corrigir sem quebrar a compatibilidade. A string canônica de quatro campos é um contrato de comunicação: todo receptor que verifica X-Signature-Simple recalcula exatamente esses quatro campos, então mudar o que o cabeçalho assina quebraria silenciosamente todos eles na verificação de assinatura. Cobrir o corpo também exige uma forma canônica especificada do JSON que os dois lados reproduzam byte a byte, e a Didit já oferece uma como X-Signature-V2. Em vez de adicionar uma segunda variante que cubra o corpo, com suas próprias regras de canonicalização, o cabeçalho permanece exatamente como está e fica obsoleto em favor das variantes existentes que cobrem o payload completo.
O que isso significa para você:
- Nada muda na comunicação.
X-Signature-Simplecontinua sendo enviado em toda entrega, calculado da mesma forma. Os verificadores existentes continuam funcionando, e anunciaremos qualquer remoção com bastante antecedência. - Novas integrações devem usar
X-Signature-V2. Ele é seguro com middleware e cobre o payload inteiro. - Se você não puder deixar de usar
X-Signature-Simple, verifique-o e depois busque o registro na API, em vez de lê-lo do corpo do webhook.
Por que três variantes?
Alguns frameworks web (parsers de corpo do Express, middleware do Django, API gateways) recodificam o JSON silenciosamente antes que seu handler o leia. Se o corpo original continha"José" e seu middleware o reescreve como "José", os bytes mudam, embora os dados sejam idênticos. Nesse caso, X-Signature falha. X-Signature-V2 é calculado a partir de uma forma canônica do JSON (chaves ordenadas, separadores compactos, Unicode sem escape) que quase todo middleware reproduz de forma determinística, e o obsoleto X-Signature-Simple recorre a assinar somente uma string curta, independente do parser, quando até isso falha. Como ele assina somente essa string curta e nunca o corpo, não pode garantir a integridade do corpo.
Ordem de verificação recomendada
- Tente primeiro o
X-Signature-V2. RecodifiqueJSON.parse(body)com as chaves ordenadas e os caracteres Unicode preservados, calculeHMAC-SHA256(secret, canonical)e compare o resultado com o cabeçalho usandotimingSafeEqual. - Se a V2 falhar e você conseguir ler os bytes brutos, tente o
X-SignaturecomHMAC-SHA256(secret, rawBody). - Somente se ambas falharem, recorra ao
X-Signature-Simple, que está obsoleto, comHMAC-SHA256(secret, "{timestamp}:{session_id}:{status}:{webhook_type}"). Ele autentica apenas o envelope: trate adecisiondo corpo como não confiável e busque-a novamente emGET /v3/session/{session_id}/decision/antes de agir com base nela.
-Previous correspondente estiver presente, aceite uma correspondência em qualquer um dos dois cabeçalhos (consulte Rotação do segredo de assinatura).
Em todos os casos, rejeite qualquer requisição com mais de 5 minutos (abs(now - X-Timestamp) > 300) para se proteger contra ataques de repetição.
Política de novas tentativas
Se o seu endpoint responder com5xx ou 404, a Didit tenta novamente até 2 vezes, com backoff exponencial:
- 1ª nova tentativa: cerca de 1 minuto após a falha inicial.
- 2ª nova tentativa: cerca de 4 minutos após a 1ª nova tentativa.
attempt: 1 para a primeira entrega de um evento e, para cada nova tentativa ou reenvio, um a mais que o número da tentativa que ele repete.
As novas tentativas automáticas vão para a URL para a qual a primeira tentativa foi enviada, mesmo que você tenha alterado a url do destino depois disso.
Um reenvio manual vai para a URL atual do destino, então corrigir uma URL também corrige o reenvio.
Outras regras de entrega que você deve conhecer:
- A requisição HTTP de saída atinge o tempo limite após 5 segundos.
- A Didit bloqueia entregas para URLs privadas ou de localhost como proteção contra SSRF e nunca segue redirecionamentos. Use um endpoint HTTPS público que responda diretamente.
- Uma
urlde destino que contenha uma barra invertida literal, um caractere de controle ou mais de um@na autoridade é rejeitada como malformada: uma URL assim não pode ser criada, e uma já existente não pode ter entregas reenviadas nem repetidas. Use uma URL simpleshttps://host/pathcom no máximo uma seção de userinfo (para URLs de webhook no estilo basic auth). 2xxé tratado como sucesso. Timeouts e falhas de conexão contam como passíveis de nova tentativa (aparecem como504/503no registro de entregas); respostas3xxe4xxdiferentes de404não geram novas tentativas.- Em cada nova tentativa, a Didit recalcula as assinaturas com um
X-Timestampnovo, de modo que o par timestamp/assinatura sempre corresponde. - Novas tentativas e reenvios são assinados com o segredo de assinatura atual do destino no momento em que são enviados.
Ordem de entrega
A Didit não garante a ordem em que os webhooks chegam até você. Cada entrega entra na fila de forma independente e uma nova tentativa chega minutos após a primeira tentativa, então umstatus.updated para Approved pode chegar antes de um anterior para In Progress.
Construa o seu receptor levando isso em conta:
- Trate um webhook como um sinal de que algo mudou. Quando a ordem importar, chame
GET /v3/session/{session_id}/decision/e aja com base nostatusatual. - Nunca permita que um evento mais antigo sobrescreva um estado mais recente: compare o
created_atdo evento com o do último que você armazenou para aquele registro e ignore o mais antigo. - Identifique repetições pelo
event_id, que permanece o mesmo em todas as novas tentativas.
Configurar um destino
Você pode gerenciar destinos em Business Console > API & Webhooks ou pela Management API.1
Crie o destino
Chame
POST /v3/webhook/destinations/ (ou clique em Add destination no console) com um label, uma url pública, webhook_version ("v3") e um array subscribed_events contendo todas as famílias de eventos que esse endpoint deve receber. É necessário pelo menos um evento, e não existe curinga.2
Armazene o secret_shared_key
A resposta da criação retorna o
secret_shared_key do destino. Armazene-o junto com os outros segredos do seu servidor: ele pertence apenas a esse destino e é a entrada das três variantes de assinatura HMAC-SHA256. GET /v3/webhook/destinations/{destination_uuid}/ o retorna novamente para quem chama a API com uma chave de API e para usuários do console que podem ler webhooks.3
Implemente o endpoint
Exponha uma rota
POST HTTPS pública, leia o corpo bruto, verifique a assinatura (dando preferência ao X-Signature-V2), verifique se o timestamp está dentro da janela permitida e, em seguida, faça o despacho com base no webhook_type. Retorne 2xx assim que tiver colocado o trabalho na fila; faça o processamento pesado de forma assíncrona.4
Confirme os recebimentos
Acompanhe a aba Deliveries do destino. Webhooks em bom funcionamento retornam
200 em um ou dois segundos. 5xx/404 aciona a política de novas tentativas da Didit; falhas persistentes são descartadas após a segunda tentativa.5
Itere com o Try Webhook
Use os cenários do Try Webhook no console para fazer o seu endpoint processar payloads de aprovado, recusado, em revisão, KYB, entidade, atividade e transação antes de ativar o tráfego de produção.

Envie webhooks de teste diretamente do Business Console para validar o seu endpoint antes de entrar em produção.
Exemplos de código
Para garantir a segurança do seu endpoint de webhook, verifique a autenticidade das requisições recebidas usando asecret_shared_key do destino.
Node.js / Express (recomendado: X-Signature-V2)
X-Signature funciona da mesma forma, mas assina o corpo bruto. Ela é mantida na seção legada abaixo para fins de completude.
Python / FastAPI (recomendado: X-Signature-V2)
PHP / Laravel (recomendado: X-Signature-V2)
Os padrões de JSON do PHP não reproduzem a forma canônica. Quatro deles precisam ser substituídos, e esquecer qualquer um deles altera os bytes assinados, de modo que o digest nunca coincide:
A armadilha do objeto vazio é a que mais causa problemas. Os payloads da Didit trazem com frequência mapas vazios como
risk_view.countries.risk_scores, risk_view.crimes.risk_scores e risk_view.custom_list, então uma conversão de ida e volta associativa quebra a assinatura em sessões aprovadas comuns, e não em algum caso extremo raro.
- Floats com valor inteiro não precisam de tratamento especial no PHP. A Didit os serializa como inteiros (
100.0→100), e ojson_encodedo PHP escreve um float inteiro da mesma forma, então nenhuma etapashortenFloatsé necessária, ao contrário do exemplo de Node.js acima. - Mantenha
serialize_precisionem-1(o padrão desde o PHP 7.1). Se o seu servidor o substituir para17, ojson_encodeescreve87.42como87.420000000000002e toda verificação deX-Signature-V2falha.
verifySignatureRaw acima é a alternativa mais simples: o $request->getContent() do Laravel retorna os bytes intactos, então X-Signature funciona desde que nada à frente do PHP recodifique o corpo. Esse é exatamente o cenário ao qual X-Signature-V2 foi projetado para resistir, e é por isso que a V2 é testada primeiro.
Testes
Você não precisa executar uma verificação completa para testar o seu endpoint.- Em Business Console > API & Webhooks > Try Webhook, escolha um cenário (por exemplo,
approved_full_features,declined_face_mismatch,in_review_aml_hit,business_session_approved,transaction_created,activity_created,user_status_updated) e envie um webhook completo para a URL do seu destino. - Todo cenário é assinado com os mesmos três cabeçalhos de assinatura e a mesma codificação canônica do tráfego de produção, incluindo dados de teste em unicode (
approved_kyc_with_unicode), para que você possa confirmar queX-Signature-V2funciona através do seu middleware. As entregas de teste trazem dados de exemplo e são marcadas com um cabeçalho extraX-Didit-Test-Webhook: true(e um sufixo(Test)noUser-Agent), para que você possa impedir que elas afetem o estado de produção. - Reenvie qualquer entrega anterior pela aba Deliveries de um destino se precisar depurar uma regressão.
Exemplos
Sessão KYC aprovada (status.updated)
Sessão recusada com avisos (status.updated)
Sessão de empresa aprovada (status.updated, KYB)
Transação criada (transaction.created)
Status do intercâmbio da Travel Rule alterado (travel_rule.status.updated)
travel_rule_status traz um dos 13 status de intercâmbio documentados em Travel Rule.
rail é INTERNAL, TRP, GTR, TRUST, VERIFYVASP, SYGNA ou EMAIL (string vazia até que um canal seja selecionado, quando nenhum pôde ser determinado e sempre em registros sunrise), ou TEST_VASP para uma transferência de sandbox respondida pelo Didit Test VASP, e direction é INBOUND ou OUTBOUND.
Um evento de sandbox traz "environment": "sandbox", e um evento TEST_VASP também traz test_vasp: true e test_vasp_scenario (o cenário que respondeu).
Quando a Didit cria uma transação para uma transferência Travel Rule (uma chamada a POST /v3/transactions/, um registro sunrise após o depósito, uma consulta TRP de entrada ou a perna de entrada do canal INTERNAL), ela envia primeiro o evento transaction.created dessa transação e, depois dele, os eventos travel_rule.status.updated da transferência.
Uma regra que altera o status da transação durante essa etapa também pode enviar transaction.status.updated antes de transaction.created, e um registro sunrise vinculado a uma transação de depósito que você já criou não envia transaction.created.
Cada webhook é entregue e reenviado de forma independente, então um evento reenviado ainda pode chegar depois de um evento posterior: use transaction_id como chave no seu handler e consulte a transação novamente quando precisar do estado atual dela.
Uma transferência enviada pelo TRUST nunca recebe um evento COMPLETED, porque o TRUST não devolve nada que a Didit consiga ler: ela permanece em AWAITING_COUNTERPARTY até o prazo de confirmação e então termina como EXPIRED. No GTR, no VerifyVASP e no Sygna Bridge, a Didit lê a resposta da contraparte na rede e o evento a acompanha (consulte Redes Travel Rule).
Conformidade do fluxo alterada (workflow.compliance_changed)
transition.from e transition.to são um dos valores compliant, gaps_detected, pending_change, unknown_coverage ou not_evaluated.
severity é actionable quando foram detectadas lacunas, indicative para uma mudança pendente e informative quando as lacunas foram resolvidas.
A Didit envia um evento por fluxo alterado.
Sugestões de regras de transação criadas (transaction_rule_suggestions.created)
kind é ADD para uma nova regra, TWEAK para uma alteração em uma regra existente e DISABLE para desativar uma regra. rule_id é null para ADD.
open_count é o número de sugestões ainda abertas após a execução, incluindo as novas.
As configurações de sugestão de regras ativam ou desativam este webhook; consulte Webhooks de transação.
Eventos do Investigator (investigator.*)
Os doze eventos investigator.* usam o mesmo envelope (event_id, webhook_type, timestamp, created_at, application_id, environment) e os mesmos cabeçalhos de assinatura usados por todos os outros eventos. Eles trazem apenas ids e números: nunca um nome, um motivo digitado por um revisor, uma narrativa ou qualquer coisa que o usuário final tenha escrito. Eles não têm session_id; o item a que se referem é identificado em item.
provenance indica qual versão do Investigator escreveu o relatório:
engineé a versão do motor do Investigator: um id opaco de oito caracteres. Ele muda sempre que o motor muda, então dois relatórios com o mesmoengineforam escritos pelo mesmo motor.agent_versioné a versão do agente.policy_versioné a versão da sua própria política que o relatório seguiu.model_idé um alias obsoleto que traz o mesmo valor deengine. Useengineem vez dele.prompt_hashnão é mais enviado.
item.kind indica o tipo de item (uma sessão, uma sessão de empresa, uma transação ou um caso) e item.id, o id dele. Os timestamps terminados em _at são strings ISO 8601. Assine esses eventos listando as strings exatas em subscribed_events, como qualquer outro evento.
Status da entidade de usuário alterado (user.status.updated)
Sessão reenviada
Legado: X-Signature com corpo bruto
OX-Signature continua sendo enviado em todas as entregas; ele é um HMAC-SHA256 calculado sobre os bytes exatos que a Didit transmite (json.dumps(..., sort_keys=True, separators=(",", ":"))). Use-o somente quando você conseguir ler o corpo bruto antes que qualquer middleware o modifique.
- Node.js
- Python
Legado: formato de webhook V2
Os objetos de cada recurso são os mesmos nas duas versões: um destino V2 recebe exatamente os mesmos
match_score, risk_score, score_breakdown, risk_view e review_status por ocorrência e o mesmo score agregado que a V3 (consulte o relatório AML). Configurar para V2 muda onde o relatório AML fica dentro de decision, não o que ele contém.
Se um consumidor downstream ainda precisar dos formatos V2, crie um destino separado fixado em webhook_version: "v2" e mantenha seus novos consumidores na V3.
Relacionados
- API de destinos de webhook: liste, crie, atualize e exclua destinos e inspecione os logs de entrega.
- Criar destino de webhook: esquema de requisição e resposta, incluindo o
secret_shared_keydo destino. - Modelos de dados: esquema canônico de cada objeto específico de cada recurso incorporado em
decision. - Esquema de decisão: lista completa de campos do objeto
decisionincorporado nos webhooks de sessão. - Status de verificação: valores exatos de
statuse transições mencionados acima. - Limite de requisições: limites que se aplicam ao consultar repetidamente
GET /v3/session/{id}/decision/como alternativa.