Webhooks
O webhook é o mecanismo de conclusão da API: é por ele que sua aplicação fica sabendo que um documento terminou de ser processado.
Formato da entrega
A Dynadok faz um POST para cada URL configurada, com
Content-Type: application/json e o corpo:
{
"event": "EXTERNAL_DOCUMENT_PROCESSED",
"data": { }
}
Além do Content-Type, seguem apenas os headers que você cadastrar na
configuração do webhook (pares nome/valor).
Cadastre um header secreto na configuração do webhook e valide esse header no seu endpoint.
Reentrega: leia isto antes de responder 500
Só erro de rede é retentado. Qualquer resposta HTTP do seu endpoint —
inclusive 500 — é tratada como entrega concluída e não gera reenvio.
| seu endpoint | a Dynadok faz |
|---|---|
| não responde (timeout, DNS, conexão recusada) | 1 envio inicial + até 5 retentativas (6 tentativas no total), com backoff exponencial a partir de 10 s |
responde 2xx | marca como sucesso |
responde 4xx ou 5xx | marca como terminal, sem reenvio |
Consequência prática: se o seu endpoint devolver 500 porque o banco estava
fora do ar, o evento é perdido. Se você não puder processar na hora, responda
2xx depois de enfileirar o evento do seu lado, e trate a falha
internamente. Há reenvio manual pelo painel, mas ele é operação humana, não
recuperação automática.
Escopo
Um webhook pode valer para a empresa inteira ou só para projetos escolhidos. Sem projeto configurado, ele recebe os eventos da empresa toda. Webhook desativado não recebe nada.
Cuidado com os eventos EXTERNAL_DOCUMENT_*. Um documento enviado direto
pela API (EXTERNAL_DOCUMENT_PROCESSED, EXTERNAL_DOCUMENT_UPDATED) não
pertence a projeto nenhum — só um webhook da empresa toda os recebe. Um
webhook restrito a projetos nunca vai receber esses dois eventos, e não há
erro nem aviso: a entrega simplesmente não acontece. A mesma regra vale para a
análise de dispensa avulsa (CREDIT_TRANSFER_ANALYSIS_*, ver abaixo). Os quatro
eventos PARTICIPANT_* pertencem a um projeto e chegam tanto no webhook da empresa toda quanto no
que lista aquele projeto — mas isso é roteamento interno, não uma garantia
sobre o payload. Só PARTICIPANT_STATUS_UPDATED e PARTICIPANT_DATA_UPDATED
carregam projectId dentro de data (ver exemplos abaixo). Os dois eventos
de documento (PARTICIPANT_DOCUMENT_PROCESSED, PARTICIPANT_DOCUMENT_UPDATED)
entregam o documento puro, que não tem projectId; os campos mais
próximos ficam dentro de documentExtraData (competencyParticipantId,
projectCompetencyId). Não use data.projectId para rotear esses dois
eventos do seu lado — ele simplesmente não existe nesse payload.
Eventos
| evento | quando dispara | escopo | data |
|---|---|---|---|
EXTERNAL_DOCUMENT_PROCESSED | documento enviado pela API terminou o processamento | empresa toda | o documento, mesmo formato de GET /v1/api/documents/{_id} |
EXTERNAL_DOCUMENT_UPDATED | o documento mudou depois de concluído | empresa toda | o documento, mais updateReason e message |
PARTICIPANT_DOCUMENT_PROCESSED | documento de um participante terminou o processamento | por projeto | o documento puro, mesmo formato de GET /v1/api/documents/{_id}, sem campos extras |
PARTICIPANT_DOCUMENT_UPDATED | documento de participante mudou depois de concluído | por projeto | o documento puro, mesmo formato de GET /v1/api/documents/{_id}, sem campos extras |
PARTICIPANT_STATUS_UPDATED | o status do participante mudou | por projeto | ver abaixo |
PARTICIPANT_DATA_UPDATED | os dados cadastrais do participante mudaram | por projeto | ver abaixo |
CREDIT_TRANSFER_ANALYSIS_COMPLETED | análise de dispensa de disciplinas concluída | empresa toda (avulsa) ou por projeto (participante) | a análise, mesmo formato de GET /v1/api/credit-transfer-analyses/{id} |
CREDIT_TRANSFER_ANALYSIS_FAILED | análise de dispensa de disciplinas falhou | empresa toda (avulsa) ou por projeto (participante) | a análise, mesmo formato de GET /v1/api/credit-transfer-analyses/{id} |
"Empresa toda" significa que só webhooks sem restrição de projeto recebem o evento; "por projeto" significa que tanto um webhook da empresa toda quanto um restrito ao projeto do documento/participante recebem.
O formato do documento está na Referência, no schema de
saída de documento — é o mesmo de GET /v1/api/documents/{_id}. O formato da
análise de dispensa está no guia Dispensa de disciplinas.
EXTERNAL_DOCUMENT_UPDATED
Ao documento somam-se dois campos. updateReason hoje tem um único valor,
FORENSIC_ANALYSIS_COMPLETED: a Análise Forense é assíncrona e pode concluir
depois do EXTERNAL_DOCUMENT_PROCESSED, então o laudo chega neste segundo
evento. message traz a frase correspondente, montada a partir do próprio
estado que vai no payload.
PARTICIPANT_STATUS_UPDATED
{
"event": "PARTICIPANT_STATUS_UPDATED",
"data": {
"projectId": "6500a1b2c3d4e5f600000003",
"competencyParticipantId": "6500a1b2c3d4e5f600000002",
"status": "APPROVED_BY_AI",
"message": null
}
}
status é um de PENDING, STARTED, APPROVED_BY_AI, IN_ANALYSIS,
APPROVED, DISAPPROVED.
PARTICIPANT_DATA_UPDATED
{
"event": "PARTICIPANT_DATA_UPDATED",
"data": {
"projectId": "6500a1b2c3d4e5f600000003",
"competencyParticipantId": "6500a1b2c3d4e5f600000002",
"name": "João Silva",
"email": "joao@exemplo.com.br",
"dateOfBirth": "1990-04-17",
"phone": "11999999999",
"cpf": "12345678909",
"cnpj": null,
"gender": null,
"customFields": [{ "name": "matricula", "value": "A-1024" }]
}
}
dateOfBirth é dia-calendário YYYY-MM-DD, sem fuso.