Fluxos de automaçãoGatilhos

Gatilhos

Referência completa de cada gatilho dos Fluxos de automação da Certivant — quando cada evento é disparado, seu contexto de dados, payloads de exemplo, configuração de assunto e escopo e padrões de filtro DSL.

Um Gatilho é o único nó de entrada de cada Fluxo de automação. Ele declara qual evento inicia o fluxo, quais objetos esse evento deve envolver (o assunto + escopo) e um filtro opcional que restringe ainda mais quais eventos se qualificam. Quando a plataforma emite um evento correspondente, um KycWorkflowRun é criado e o fluxo é executado nó por nó.

Esta página documenta cada gatilho que o motor realmente emite, com base nos pontos de emissão da plataforma. Cada entrada lista quando ele é disparado, o context exato que carrega, um payload de exemplo real e como definir o escopo e filtrá-lo.

O catálogo de gatilhos é um enum fixo no motor (WorkflowTriggerEvent). O seletor do editor, esta página, o schema de validação da API e o despachante leem da mesma lista, então todo gatilho que você pode selecionar é um gatilho que a plataforma realmente emite — nenhum é decorativo. Algumas capacidades que ainda não têm ponto de emissão são intencionalmente mantidas fora do seletor e listadas em Gatilhos do roadmap, com a alternativa que funciona, para que você nunca crie um fluxo que simplesmente nunca é executado.


Como os gatilhos são disparados

Os gatilhos não são verificados por polling. Cada um corresponde a um ponto real da plataforma em que o código chama o despachante:

emitter (API route / worker / cron)
   └─ enqueueWorkflowDispatch({ organizationId, event, kycRequestId?, …, context })
          └─ workflow.worker → dispatchWorkflowEvent(input)
                 ├─ load active workflows whose trigger.event === input.event
                 ├─ narrow by subject + scope            (form / applicant / document / check)
                 ├─ evaluate the optional DSL filter      (against input.context)
                 └─ create + run a KycWorkflowRun per match

Três propriedades importam para quem faz integração:

  • Fire-and-forget. O disparo é encapsulado para que uma falha nunca quebre a ação de origem. Se um envio de documento dispara um fluxo e o disparo falha, o envio ainda é concluído. Trate os efeitos colaterais dos fluxos como executados eventualmente, não de forma síncrona com a chamada da API.
  • Um evento, muitas execuções. Um único evento pode iniciar todo fluxo ativo que corresponda a ele. Use assunto + escopo (e filtros) para manter esse leque de disparos intencional.
  • O contexto é capturado uma vez. O objeto context do evento é armazenado na primeira entrada do log da execução. Os nós de Condição, os modelos de Ação e os tokens de mesclagem de e-mail leem desse snapshot — eles não consultam novamente o estado atual.

O envelope de disparo

Cada emissão produz um WorkflowEventInput. Os campos além de organizationId e event são opcionais e dependem do evento:

CampoTipoPresente quando
organizationIdstringsempre
eventWorkflowTriggerEventsempre
kycRequestIdstring | nullo evento diz respeito a uma verificação específica (a maioria dos eventos)
formIdstring | nullform.submitted
formKind"kyc" | "kyb" | nullform.submitted
applicantIdstring | nulleventos com escopo de solicitante
documentTypestring | nulldocument.*
checkKindstring | nullcheck.completed, liveness.*, sanctions.*
contextobjectfatos específicos do evento (veja cada gatilho abaixo)
workflowIdstringsomente em uma invocação direta de trigger_workflow
chainDepthnumberprofundidade da cadeia de fluxo para fluxo (máximo 5)

subject, scope, filter, formIds, schedule, scoreMin e scoreMax são propriedades do gatilho que você configura — não do evento em tempo de execução. O despachante os usa para decidir se um evento recebido corresponde ao seu fluxo.


A definição do gatilho

Esta é a estrutura persistida no trigger de um fluxo (WorkflowTrigger):

interface WorkflowTrigger {
  event: WorkflowTriggerEvent;          // which event starts the workflow
  subject?: "form" | "applicant"        // the kind of object it binds to
          | "document" | "check";       //   (absent → legacy org-wide)
  scope?: {
    mode: "specific" | "category" | "all";
    ids?: string[];                     // specific-mode object ids
    category?: string;                  // category-mode key (see below)
  };
  filter?: string;                      // optional DSL predicate over context
  formIds?: string[];                   // legacy form-binding (pre-subject)
  schedule?: string;                    // schedule only — cron expression
  scoreMin?: number;                    // score.threshold convenience bound
  scoreMax?: number;                    // score.threshold convenience bound
}

Assunto + escopo

O assunto é o tipo de objeto que o gatilho observa; o escopo o restringe a objetos específicos, a uma categoria ou a todos. É isso que impede um gatilho de disparar para toda a organização por acidente.

AssuntoEventos válidosOpções de scope.modeSignificado de category
formform.submittedspecific (ids de formulário) · category · all"kyc" ou "kyb"
applicantrequest.*, score.threshold, edd.triggered, verification.expiredspecific (ids de solicitante) · all—
documentdocument.uploaded, document.failedspecific · category · allum tipo de documento, por exemplo "passport"
checkcheck.completed, liveness.*, sanctions.*specific · category · allum tipo de checagem, por exemplo "sanctions"
(nenhum)schedule, inbound.webhook, monitoring.alert, kyb.*, reviewer.decision, conference.completed—toda a organização

Modos de escopo:

  • specific — corresponde somente aos ids listados (por exemplo, dispara apenas para este formulário de onboarding). Se todos os ids de um escopo specific apontarem para objetos excluídos, o gatilho fica órfão: o despachante se recusa a disparar (ele permanece inativo, em vez de cair silenciosamente em "todos"), e o editor mostra um selo "Precisa ser revinculado".
  • category — corresponde a uma classe de objetos (todos os formulários de KYC, todos os passaportes, todas as checagens de sanções).
  • all — corresponde a qualquer objeto do tipo de assunto na organização.

Um gatilho sem subject mantém o comportamento legado: para toda a organização, opcionalmente restringido por formIds em form.submitted. Novos fluxos sempre devem definir um assunto + escopo.

Filtro (DSL)

filter é um predicado booleano opcional, na mesma gramática de expressões da DSL de política de decisão. Ele é executado contra o context do evento. Um filtro vazio dispara em todo evento do tipo. Veja Filtrando com a DSL.


Categorias de gatilhos

CategoriaGatilhos
Entradaform.submitted, request.created
Ciclo de vida / statusrequest.status_changed, request.approved, request.rejected, request.escalated, request.resubmitted, request.documents_ready
Documentosdocument.uploaded, document.failed
Sinais de verificaçãocheck.completed, liveness.passed, liveness.failed, face_match.passed, face_match.failed, sanctions.hit, sanctions.cleared, pep.match, kyb.match_found, kyb.match_not_found, kyb.ubo_completed
Revisão humanareviewer.decision, reviewer.assigned, reviewer.comment_added, edd.triggered
Risco / pontuaçãoscore.threshold, score.updated
Tempo / monitoramentoschedule, verification.expired, monitoring.alert
Externo / integraçãoinbound.webhook, conference.completed

Referência de gatilhos

Cada entrada abaixo usa a mesma estrutura: Quando é disparado, Contexto de dados, Evento de exemplo e Exemplo de uso no fluxo. Cada payload é o formato de disparo real produzido pelo código que emite o evento.

Entrada

form.submitted

"Um formulário é enviado" · assunto form

Quando é disparado. Um formulário público de verificação hospedado é enviado por um solicitante. Emitido pela rota de formulários públicos no momento em que um envio cria o seu KycRequest. O evento companheiro request.created é emitido junto com ele.

Contexto de dados.

CampoTipoObservações
typestringTipo de solicitante no envio, por exemplo "INDIVIDUAL"

No nível superior: formId, formKind ("kyc"/"kyb"), kycRequestId, applicantId.

Evento de exemplo.

{
  "organizationId": "org_01H...",
  "event": "form.submitted",
  "kycRequestId": "req_01J...",
  "formId": "form_01H...",
  "formKind": "kyc",
  "context": { "type": "INDIVIDUAL" }
}

Exemplo de uso no fluxo. Vincule um fluxo de e-mail de boas-vindas + solicitação de documentos a um único formulário de onboarding:

trigger:
  event: form.submitted
  subject: form
  scope: { mode: specific, ids: ["form_01H..."] }

request.created

"Uma verificação é criada" · assunto applicant

Quando é disparado. Uma solicitação de verificação é criada — via POST /api/v1/kyc/requests (201) ou como companheiro de um form.submitted hospedado. É disparado antes de qualquer documento ser enviado ou de qualquer checagem ser executada, então use-o para roteamento de entrada, não para resultados.

Contexto de dados.

CampoTipoObservações
applicant.countrystring | nullISO-3166 alfa-2, a partir da nacionalidade do solicitante
applicant.typestring"INDIVIDUAL" ou "BUSINESS"
typestringTipo da solicitação (INDIVIDUAL / BUSINESS)

Evento de exemplo.

{
  "organizationId": "org_01H...",
  "event": "request.created",
  "kycRequestId": "req_01J...",
  "applicantId": "app_01J...",
  "context": {
    "applicant": { "country": "CA", "type": "INDIVIDUAL" },
    "type": "INDIVIDUAL"
  }
}

Exemplo de uso no fluxo. Encaminhe solicitantes empresariais para uma fila de entrada de KYB:

trigger:
  event: request.created
  subject: applicant
  scope: { mode: all }
  filter: 'applicant.type = "BUSINESS"'

Ciclo de vida / status

Estes disparam depois que a orquestração finaliza um veredito. Todos carregam o mesmo contexto de decisão. Os eventos específicos (approved / rejected / escalated) disparam além do request.status_changed genérico, então escute um ou outro — não ambos — para evitar execução dupla.

Contexto de dados compartilhado (request.approved, request.rejected, request.escalated, request.status_changed):

CampoTipoObservações
statusstringNovo status, por exemplo "APPROVED", "REJECTED", "ESCALATED", "PENDING_SECOND_REVIEW"
decisionstring | nullVeredito do pontuador: "approve" / "reject" / "escalate"
overallScorenumber | nullPontuação de confiança 0–100 (maior = menor risco)
applicant.countrystring | nullISO-3166 alfa-2
applicant.typestringINDIVIDUAL / BUSINESS

request.approved

"Uma verificação é aprovada" · assunto applicant

Quando é disparado. A orquestração finaliza a solicitação como APPROVED.

{
  "organizationId": "org_01H...",
  "event": "request.approved",
  "kycRequestId": "req_01J...",
  "applicantId": "app_01J...",
  "context": {
    "status": "APPROVED",
    "decision": "approve",
    "overallScore": 88,
    "applicant": { "country": "US", "type": "INDIVIDUAL" }
  }
}

Exemplo de uso no fluxo. Gere e envie por e-mail um relatório de verificação somente para aprovações de alta confiança:

trigger:
  event: request.approved
  subject: applicant
  scope: { mode: all }
  filter: 'overallScore >= 80'

request.rejected

"Uma verificação é rejeitada" · assunto applicant

Quando é disparado. A orquestração finaliza a solicitação como REJECTED. Contexto idêntico ao de request.approved, com status: "REJECTED" e decision: "reject".

Exemplo de uso no fluxo. Notifique o gerente responsável e marque o solicitante em caso de rejeição:

trigger:
  event: request.rejected
  subject: applicant
  scope: { mode: all }

request.escalated

"Uma verificação é encaminhada para revisão" · assunto applicant

Quando é disparado. A orquestração (ou a ação escalate de um fluxo anterior) move a solicitação para ESCALATED / revisão humana. Contexto idêntico ao formato compartilhado, com status: "ESCALATED".

Exemplo de uso no fluxo. Agende uma conferência de Live ID sempre que uma solicitação for encaminhada:

trigger:
  event: request.escalated
  subject: applicant
  scope: { mode: all }

request.status_changed

"O status de uma verificação muda" · assunto applicant

Quando é disparado. Sempre emitido em uma mudança de status finalizada, junto com qualquer evento de decisão específico. Use-o para reagir a status que não têm evento dedicado (por exemplo, PENDING_SECOND_REVIEW, CANCELLED).

Exemplo de uso no fluxo. Reaja apenas a um repasse para dois revisores:

trigger:
  event: request.status_changed
  subject: applicant
  scope: { mode: all }
  filter: 'status = "PENDING_SECOND_REVIEW"'

request.resubmitted

"Uma verificação é reenviada" · assunto applicant

Quando é disparado. Um revisor devolve uma solicitação para reenvio pelo painel; a solicitação volta para PENDING_UPLOAD.

Contexto de dados.

CampoTipoObservações
statusstring"PENDING_UPLOAD"
kindstring"resubmit"
{
  "organizationId": "org_01H...",
  "event": "request.resubmitted",
  "kycRequestId": "req_01J...",
  "context": { "status": "PENDING_UPLOAD", "kind": "resubmit" }
}

Exemplo de uso no fluxo. Reenvie ao solicitante o link de envio dele e inicie um temporizador de lembrete.

request.documents_ready

"Todos os documentos obrigatórios foram enviados" · assunto applicant

Quando é disparado. O solicitante acabou de enviar o último item da sua lista de documentos obrigatórios, então a solicitação passa para DOCUMENTS_READY. Emitido pelos dois caminhos de envio — a API de documentos do painel e a sessão de verificação hospedada — somente no envio que esvazia o conjunto de documentos pendentes. Este é o sinal concreto de que o solicitante concluiu todas as etapas obrigatórias.

Contexto de dados.

CampoTipoObservações
statusstring"DOCUMENTS_READY"
{
  "organizationId": "org_01H...",
  "event": "request.documents_ready",
  "kycRequestId": "req_01J...",
  "context": { "status": "DOCUMENTS_READY" }
}

Exemplo de uso no fluxo. Notifique o revisor responsável no momento em que um caso estiver pronto para revisão:

trigger:
  event: request.documents_ready
  subject: applicant
  scope: { mode: all }

Documentos

document.uploaded

"Um documento é enviado" · assunto document

Quando é disparado. Cada vez que um documento é enviado — pela API do painel ou por uma sessão de verificação hospedada. Um evento por documento.

Contexto de dados.

CampoTipoObservações
document.typestringpor exemplo "passport", "id_card", "drivers_license"
document.sidestring | null"front" / "back" (null quando não se aplica)

No nível superior: documentType, kycRequestId.

{
  "organizationId": "org_01H...",
  "event": "document.uploaded",
  "kycRequestId": "req_01J...",
  "documentType": "passport",
  "context": { "document": { "type": "passport", "side": "front" } }
}

Exemplo de uso no fluxo. Dispare somente em envios de comprovante de endereço:

trigger:
  event: document.uploaded
  subject: document
  scope: { mode: category, category: "utility_bill" }

document.failed

"Um documento falha na validação" · assunto document

Quando é disparado. A orquestração marca um documento como failed (o OCR não pôde ser executado, a autenticidade o rejeitou etc.). Um evento por documento com falha. Mesmo formato de contexto de document.uploaded.

{
  "organizationId": "org_01H...",
  "event": "document.failed",
  "kycRequestId": "req_01J...",
  "documentType": "drivers_license",
  "context": { "document": { "type": "drivers_license", "side": "back" } }
}

Exemplo de uso no fluxo. Solicite automaticamente uma nova captura de qualquer documento com falha.

Sinais de verificação

check.completed

"Uma checagem de verificação é concluída" · assunto check

Quando é disparado. Uma vez para cada checagem individual concluída durante uma execução de orquestração (OCR, comparação facial, prova de vida, sanções, autenticidade de documento, leitura NFC…). Este é o sinal genérico e de alta frequência — para um resultado claro e único, prefira liveness.* / sanctions.*.

Disparo protegido. Como várias checagens terminam por solicitação, a plataforma primeiro verifica se a organização tem algum fluxo ativo escutando check.completed. Sem nenhum ouvinte, nenhum job de disparo é criado. A marca d'água por execução impede que uma nova verificação reemita as checagens de uma execução anterior.

Contexto de dados.

CampoTipoObservações
check.typestringTipo da checagem, por exemplo "sanctions", "face_match", "ocr"
check.statusstring"passed" / "failed" / "error"
check.providerstring | nullProvedor que executou a checagem
check.scorenumber | nullPontuação do provedor, quando aplicável

No nível superior: checkKind, kycRequestId.

{
  "organizationId": "org_01H...",
  "event": "check.completed",
  "kycRequestId": "req_01J...",
  "checkKind": "face_match",
  "context": {
    "check": { "type": "face_match", "status": "passed",
               "provider": "azure_face", "score": 0.93 }
  }
}

Exemplo de uso no fluxo. Reaja a qualquer checagem de sanções concluída, independentemente do resultado (para um aprovado / reprovado claro, prefira os eventos dedicados sanctions.* / face_match.* abaixo):

trigger:
  event: check.completed
  subject: check
  scope: { mode: category, category: "sanctions" }

liveness.passed / liveness.failed

"Prova de vida aprovada" / "Prova de vida reprovada" · assunto check (liveness)

Quando é disparado. Depois da orquestração, quando a checagem de prova de vida persistiu um booleano isLive inequívoco. passed quando isLive é verdadeiro, failed quando é falso. Uma checagem de prova de vida com erro ou ambígua não emite nenhum dos dois.

Contexto de dados.

CampoTipoObservações
liveness.isLivebooleanSe o solicitante passou na prova de vida
liveness.confidencenumber | nullConfiança de 0 a 1
{
  "organizationId": "org_01H...",
  "event": "liveness.failed",
  "kycRequestId": "req_01J...",
  "checkKind": "liveness",
  "context": { "liveness": { "isLive": false, "confidence": 0.31 } }
}

Exemplo de uso no fluxo. Encaminhe para revisão uma falha de prova de vida com baixa confiança:

trigger:
  event: liveness.failed
  subject: check
  scope: { mode: all }
  filter: 'liveness.confidence < 0.5'

face_match.passed / face_match.failed

"Comparação facial aprovada" / "Comparação facial reprovada" · assunto check (face_match)

Quando é disparado. Depois da orquestração, quando a checagem de comparação facial (selfie ↔ retrato do documento) persistiu um booleano isMatch inequívoco. passed quando isMatch é verdadeiro, failed quando é falso. Uma checagem com erro ou ambígua não emite nenhum dos dois.

Contexto de dados.

CampoTipoObservações
faceMatch.isMatchbooleanSe a selfie correspondeu ao rosto do documento
faceMatch.confidencenumber | nullConfiança de 0 a 1
{
  "organizationId": "org_01H...",
  "event": "face_match.failed",
  "kycRequestId": "req_01J...",
  "checkKind": "face_match",
  "context": { "faceMatch": { "isMatch": false, "confidence": 0.42 } }
}

Exemplo de uso no fluxo. Encaminhe para revisão manual qualquer falha de comparação facial:

trigger:
  event: face_match.failed
  subject: check
  scope: { mode: all }

sanctions.hit / sanctions.cleared

"Ocorrência em sanções" / "Sanções liberadas" · assunto check (sanctions)

Quando é disparado. Depois da orquestração, a partir do resultado persistido de sanções / lista de observação. hit quando uma correspondência atinge o limite (ou quando existe qualquer correspondência); cleared caso contrário. Uma correspondência que seja especificamente de uma pessoa politicamente exposta também dispara o evento dedicado pep.match, então escute esse evento quando você se importar apenas com exposição a PEP.

Contexto de dados.

CampoTipoObservações
sanctions.isMatchbooleanVerdadeiro em uma ocorrência
sanctions.matchCountnumberNúmero de correspondências na lista de observação
sanctions.topScorenumber | nullMaior pontuação de correspondência, de 0 a 1
sanctions.listsCheckedstring[]Listas verificadas (OFAC, OpenSanctions, …)
{
  "organizationId": "org_01H...",
  "event": "sanctions.hit",
  "kycRequestId": "req_01J...",
  "checkKind": "sanctions",
  "context": {
    "sanctions": { "isMatch": true, "matchCount": 2, "topScore": 0.91,
                   "listsChecked": ["OFAC", "OpenSanctions"] }
  }
}

Exemplo de uso no fluxo. Encaminhe para revisão e notifique o compliance diante de uma correspondência forte em sanções:

trigger:
  event: sanctions.hit
  subject: check
  scope: { mode: all }
  filter: 'sanctions.topScore >= 0.85'

pep.match

"Correspondência de PEP encontrada" · assunto check (sanctions)

Quando é disparado. Depois da orquestração, quando o resultado de sanções / lista de observação contém pelo menos uma correspondência cujo tipo de entidade é uma pessoa politicamente exposta. Ele usa a mesma triagem de sanctions.*, mas só dispara quando há uma entrada de PEP — uma simples ocorrência em lista de observação sem entradas de PEP não o dispara.

Contexto de dados.

CampoTipoObservações
pep.isMatchbooleanSempre true (o evento só dispara em uma correspondência de PEP)
pep.matchCountnumberNúmero de correspondências de PEP
{
  "organizationId": "org_01H...",
  "event": "pep.match",
  "kycRequestId": "req_01J...",
  "checkKind": "sanctions",
  "context": { "pep": { "isMatch": true, "matchCount": 1 } }
}

Exemplo de uso no fluxo. Acione automaticamente a Diligência Devida Reforçada e encaminhe para o compliance sempre que um PEP for detectado:

trigger:
  event: pep.match
  subject: check
  scope: { mode: all }

kyb.match_found / kyb.match_not_found

"Correspondência KYB encontrada" / "Correspondência KYB não encontrada" · toda a organização

Quando é disparado. Somente para solicitações BUSINESS, depois que a orquestração grava o resultado da verificação no registro empresarial. verified → kyb.match_found; failed → kyb.match_not_found. Qualquer outro status (pendente / ausente) é ambíguo e não emite nada.

Contexto de dados.

CampoTipoObservações
kyb.statusstring"verified" ou "failed"
{
  "organizationId": "org_01H...",
  "event": "kyb.match_not_found",
  "kycRequestId": "req_01J...",
  "context": { "kyb": { "status": "failed" } }
}

Exemplo de uso no fluxo. Solicite documentos adicionais de constituição quando nenhuma correspondência for encontrada no registro.

kyb.ubo_completed

"Verificação de UBO concluída" · toda a organização

Quando é disparado. Somente para solicitações BUSINESS, depois que a orquestração resolveu todos os registros de beneficiário final (UBO) da empresa (nenhum deixado como pending). Carrega a quantidade de UBOs triados e quantos falharam, para que um fluxo possa ramificar pelo risco dos UBOs sem consultar novamente. Só dispara quando a empresa realmente tem registros de UBO e a linha do KYB foi alterada nesta execução.

Contexto de dados.

CampoTipoObservações
kyb.uboCountnumberNúmero de beneficiários finais triados
kyb.failedUboCountnumberQuantos desses UBOs falharam na verificação
{
  "organizationId": "org_01H...",
  "event": "kyb.ubo_completed",
  "kycRequestId": "req_01J...",
  "context": { "kyb": { "uboCount": 3, "failedUboCount": 1 } }
}

Exemplo de uso no fluxo. Encaminhe para revisão quando qualquer beneficiário final falhar na triagem:

trigger:
  event: kyb.ubo_completed
  filter: 'kyb.failedUboCount > 0'

Revisão humana

reviewer.decision

"Um revisor toma uma decisão manual" · toda a organização

Quando é disparado. Um revisor humano registra uma decisão manual no painel — aprovação única, qualquer uma das etapas de um fluxo com dois revisores, ou uma ação em massa.

Contexto de dados.

CampoTipoObservações
decisionstringA escolha do revisor
reviewerIdstringId do revisor que decidiu
stagestring | undefined"first_approval" / "second_approval" em fluxos de dois revisores
viastring | undefined"bulk" quando faz parte de uma ação em massa
{
  "organizationId": "org_01H...",
  "event": "reviewer.decision",
  "kycRequestId": "req_01J...",
  "context": { "decision": "approve", "reviewerId": "usr_01H...",
               "stage": "second_approval" }
}

Exemplo de uso no fluxo. Registre em log de auditoria cada decisão manual e notifique o solicitante após a aprovação final (a segunda).

reviewer.assigned

"Uma solicitação é atribuída a um revisor" · toda a organização

Quando é disparado. Uma solicitação é redistribuída para um novo responsável / revisor pela ação de reatribuição do painel. O responsável inicial definido na criação da solicitação não dispara este evento — somente uma reatribuição explícita dispara, então o evento marca uma mudança de roteamento genuína.

Contexto de dados.

CampoTipoObservações
assignedUserIdstringO novo responsável / revisor
previousUserIdstring | nullO responsável anterior
teamIdstring | nullA equipe à qual a solicitação agora pertence
{
  "organizationId": "org_01H...",
  "event": "reviewer.assigned",
  "kycRequestId": "req_01J...",
  "context": { "assignedUserId": "usr_new...", "previousUserId": "usr_old...",
               "teamId": "team_01H..." }
}

Exemplo de uso no fluxo. Envie ao revisor recém-atribuído uma notificação com um link direto para o caso.

reviewer.comment_added

"Um revisor adiciona um comentário" · toda a organização

Quando é disparado. Um revisor publica um comentário em um caso (o fluxo de comentários do caso). O corpo do comentário intencionalmente não é colocado no contexto do evento — ele pode conter notas sensíveis — então o evento carrega apenas metadados.

Contexto de dados.

CampoTipoObservações
commentIdstringO id do comentário criado
authorIdstringO revisor que comentou
authorNamestringNome de exibição do autor
{
  "organizationId": "org_01H...",
  "event": "reviewer.comment_added",
  "kycRequestId": "req_01J...",
  "context": { "commentId": "cmt_01J...", "authorId": "usr_01H...",
               "authorName": "Jordan Lee" }
}

Exemplo de uso no fluxo. Espelhe os novos comentários do caso em um canal do Slack para a equipe de compliance.

edd.triggered

"A Diligência Devida Reforçada é acionada" · assunto applicant

Quando é disparado. A Diligência Devida Reforçada (EDD) é iniciada para uma solicitação (atualmente pela ação manual de EDD).

Contexto de dados.

CampoTipoObservações
kindstring"edd"
{
  "organizationId": "org_01H...",
  "event": "edd.triggered",
  "kycRequestId": "req_01J...",
  "context": { "kind": "edd" }
}

Exemplo de uso no fluxo. Ao iniciar a EDD, solicite documentos de origem dos recursos e atribua o caso ao departamento de compliance.

Risco / pontuação

Ambos os eventos de pontuação disparam a partir do caminho de decisão, assim que o pontuador gravou uma pontuação de confiança para a execução (uma solicitação finalizada sem pontuação — por exemplo, cancelada antes da pontuação — não emite nenhum dos dois). Eles carregam o contexto completo de decisão, então overallScore, decision, status e applicant.* ficam disponíveis para os filtros.

score.updated

"A pontuação de confiança é atualizada" · assunto applicant

Quando é disparado. A cada finalização (re)pontuada — incluindo uma reanálise que recalcula a pontuação. Use-o para reagir à nova pontuação de confiança, independentemente do veredito.

Contexto de dados. O contexto de decisão compartilhado (overallScore, decision, status, applicant.country, applicant.type).

{
  "organizationId": "org_01H...",
  "event": "score.updated",
  "kycRequestId": "req_01J...",
  "applicantId": "app_01J...",
  "context": {
    "status": "APPROVED",
    "decision": "approve",
    "overallScore": 72,
    "applicant": { "country": "CA", "type": "INDIVIDUAL" }
  }
}

Exemplo de uso no fluxo. Marque qualquer solicitação cuja pontuação caia em uma faixa de revisão manual:

trigger:
  event: score.updated
  subject: applicant
  scope: { mode: all }
  filter: 'overallScore >= 50 AND overallScore < 70'

score.threshold

"A pontuação de confiança cruza um limite" · assunto applicant

Quando é disparado. No mesmo ponto de emissão de score.updated, mas o despachante o restringe pelos limites scoreMin / scoreMax do próprio gatilho (ele lê context.overallScore). Defina os limites no gatilho e o fluxo só roda quando a pontuação cai dentro deles — uma conveniência em vez de escrever um filtro de overallScore manualmente.

Contexto de dados. Idêntico ao de score.updated.

{
  "organizationId": "org_01H...",
  "event": "score.threshold",
  "kycRequestId": "req_01J...",
  "applicantId": "app_01J...",
  "context": {
    "status": "REJECTED",
    "decision": "reject",
    "overallScore": 38,
    "applicant": { "country": "US", "type": "INDIVIDUAL" }
  }
}

Exemplo de uso no fluxo. Execute somente para pontuações baixas, definindo um limite superior no gatilho:

trigger:
  event: score.threshold
  subject: applicant
  scope: { mode: all }
  scoreMax: 50          # fire only when overallScore <= 50

scoreMin / scoreMax são inclusivos. Defina um ou ambos. Deixar os dois sem valor faz score.threshold se comportar como score.updated.

Tempo / monitoramento

schedule

"Em um agendamento" · toda a organização, não vinculado a uma solicitação

Quando é disparado. Na expressão cron própria do fluxo (definida no campo schedule do gatilho). A execução é em nível de organização — kycRequestId é nulo — então use-o para trabalhos em lote / manutenção, não para lógica por solicitante.

O cron é uma expressão padrão de 5 campos (minuto hora dia mês dia-da-semana) avaliada em UTC e não pode disparar com frequência maior que a cada 5 minutos: a publicação recusa * * * * * ou */2 * * * *.

Contexto de dados.

CampoTipoObservações
firedAtstringHorário do disparo em ISO-8601
cronstringA expressão cron que disparou
{
  "organizationId": "org_01H...",
  "event": "schedule",
  "kycRequestId": null,
  "context": { "firedAt": "2026-05-31T09:00:00.000Z", "cron": "0 9 * * 1" }
}

Exemplo de uso no fluxo. Toda segunda-feira às 09:00, gere um resumo semanal de verificações e publique-o no Slack:

trigger:
  event: schedule
  schedule: "0 9 * * 1"

verification.expired

"Uma verificação expira" · assunto applicant

Quando é disparado. Pelo cron de expiração, quando uma solicitação ultrapassa a sua janela de validade.

Contexto de dados.

CampoTipoObservações
statusstring"EXPIRED"
{
  "organizationId": "org_01H...",
  "event": "verification.expired",
  "kycRequestId": "req_01J...",
  "context": { "status": "EXPIRED" }
}

Exemplo de uso no fluxo. Envie ao solicitante um novo link por e-mail e notifique o responsável quando uma verificação expirar.

monitoring.alert

"Um alerta de monitoramento é disparado" · toda a organização

Quando é disparado. Pela retriagem contínua de sanções, quando um sujeito previamente liberado passa a corresponder a uma lista de observação (monitoramento contínuo).

Contexto de dados.

CampoTipoObservações
alertTypestringO tipo do alerta de monitoramento
severitystringSeveridade do alerta
topScorenumber | nullMaior pontuação da nova correspondência
{
  "organizationId": "org_01H...",
  "event": "monitoring.alert",
  "kycRequestId": "req_01J...",
  "context": { "alertType": "sanctions_rescreen", "severity": "high",
               "topScore": 0.88 }
}

Exemplo de uso no fluxo. Diante de um alerta de monitoramento de alta severidade, encaminhe a solicitação para revisão e abra um caso de compliance.

Externo / integração

inbound.webhook

"Um webhook de entrada chega" · toda a organização, payload arbitrário

Quando é disparado. Seu sistema envia um POST para o endpoint de webhook de entrada do fluxo. O context é qualquer JSON que você enviar, então você controla totalmente o formato. Não é vinculado a uma solicitação, a menos que seu payload forneça um id que o fluxo utilize.

Endpoint. POST /api/v1/kyc/workflows/inbound, autenticado com uma chave de API que tenha o escopo workflows:trigger. Nenhum escopo kyc:* o inclui: marque Fluxos de automação → Disparar ao criar ou editar a chave. Seu plano precisa incluir fluxos de automação (caso contrário, 402). Envie context (seu objeto JSON, no máximo 64 KB) e, opcionalmente, eventId (seu próprio id: repetir com o mesmo eventId nunca inicia o mesmo fluxo duas vezes). A chamada responde 202 e as execuções acontecem em segundo plano. Uma chave restrita a determinados fluxos só inicia esses fluxos.

curl -X POST https://app.certivant.com/api/v1/kyc/workflows/inbound \
  -H "Authorization: Bearer ck_live_..." \
  -H "Content-Type: application/json" \
  -d '{"eventId":"crm-9281-1","context":{"externalRef":"user-9281","tier":"vip"}}'

Contexto de dados. Arbitrário — o corpo JSON enviado, sem alterações.

{
  "organizationId": "org_01H...",
  "event": "inbound.webhook",
  "context": { "externalRef": "user-9281", "tier": "vip", "source": "crm" }
}

Exemplo de uso no fluxo. Deixe seu CRM iniciar um lote de nova verificação para clientes sinalizados; filtre por um campo que você enviou:

trigger:
  event: inbound.webhook
  filter: 'tier = "vip"'

conference.completed

"Uma conferência ao vivo é concluída" · toda a organização

Quando é disparado. Uma conferência web de verificação de Live ID termina e o anfitrião (tabelião / revisor) registra uma decisão. Emitido uma única vez, com proteção para que uma decisão concorrente não dispare duas vezes.

Contexto de dados.

CampoTipoObservações
decisionstringA decisão registrada pelo anfitrião
statusstringO status resultante da solicitação
{
  "organizationId": "org_01H...",
  "event": "conference.completed",
  "kycRequestId": "req_01J...",
  "context": { "decision": "approve", "status": "APPROVED" }
}

Exemplo de uso no fluxo. Após uma conferência, gere o Certificado de Verificação assinado e envie-o por e-mail ao solicitante.


Filtrando com a DSL

O filter do gatilho (e cada nó de Condição) usa a gramática de expressões da política — um predicado booleano, sem chamadas de função:

  • Campos: o contexto do evento achatado (os campos mostrados na tabela Contexto de dados de cada gatilho).
  • Operadores: = != > >= < <=, IN / NOT IN.
  • Combinadores: AND OR NOT, com agrupamento ( ).
  • Literais: números, "strings", true / false / null e listas [...].

O operador de igualdade é um único = (não ==). Campos ausentes são à prova de falhas: um campo ausente é avaliado como falso, em vez de gerar erro — então um filtro que referencie sanctions.topScore em um evento que não é de sanções simplesmente não corresponde.

Quais campos são preenchidos depende do evento: um evento sanctions.* carrega sanctions.*, um evento liveness.* carrega liveness.*, e os eventos de decisão carregam overallScore / decision / status / applicant.*.

CampoTipoDisponível em
overallScorenumbereventos de decisão / status
decisionstringeventos de decisão / status
statusstringeventos de decisão / status / ciclo de vida
applicant.countrystringeventos de entrada / decisão
applicant.typestringeventos de entrada / decisão
document.typestringdocument.*
document.sidestringdocument.*
liveness.isLivebooleanliveness.*
liveness.confidencenumberliveness.*
faceMatch.isMatchbooleanface_match.*
faceMatch.confidencenumberface_match.*
sanctions.isMatchbooleansanctions.*
sanctions.topScorenumbersanctions.*
sanctions.matchCountnumbersanctions.*
pep.isMatchbooleanpep.match
pep.matchCountnumberpep.match
kyb.statusstringkyb.match_*
kyb.uboCountnumberkyb.ubo_completed
kyb.failedUboCountnumberkyb.ubo_completed
reviewerIdstringreviewer.decision

Exemplos de predicados:

overallScore >= 75
decision = "reject"
applicant.country IN ["CA","US"]
document.type = "passport"
sanctions.isMatch = true AND sanctions.topScore >= 0.85
overallScore < 50 OR sanctions.isMatch = true

No editor, pressione Ctrl+Espaço em qualquer campo de expressão para obter autocompletar sensível ao contexto e um popover de ajuda com campos, operadores e exemplos prontos.


Tabela comparativa

GatilhoCategoriaAssuntoVinculado à solicitaçãoFrequênciaObservações
form.submittedEntradaformsimpor envioSomente formulários hospedados
request.createdEntradaapplicantsimpor solicitaçãoAntes de qualquer checagem
request.status_changedCiclo de vidaapplicantsimpor mudança de statusGenérico; combina com eventos específicos
request.approvedCiclo de vidaapplicantsimpor finalização
request.rejectedCiclo de vidaapplicantsimpor finalização
request.escalatedCiclo de vidaapplicantsimpor finalização
request.resubmittedCiclo de vidaapplicantsimpor reenvio
request.documents_readyCiclo de vidaapplicantsimpor conclusão da listaTodos os documentos obrigatórios enviados
document.uploadedDocumentosdocumentsimpor documento
document.failedDocumentosdocumentsimpor documento com falha
check.completedSinaischecksimpor checagem (alta)Protegido; uma por checagem
liveness.passedSinaischecksimpor solicitaçãoSomente inequívocos
liveness.failedSinaischecksimpor solicitaçãoSomente inequívocos
face_match.passedSinaischecksimpor solicitaçãoSomente inequívocos
face_match.failedSinaischecksimpor solicitaçãoSomente inequívocos
sanctions.hitSinaischecksimpor solicitaçãoCorrespondência em lista de observação
sanctions.clearedSinaischecksimpor solicitação
pep.matchSinaischecksimpor solicitaçãoSomente entradas de PEP
kyb.match_foundSinais—simpor solicitação empresarialSomente BUSINESS
kyb.match_not_foundSinais—simpor solicitação empresarialSomente BUSINESS
kyb.ubo_completedSinais—simpor solicitação empresarialSomente BUSINESS; UBOs resolvidos
reviewer.decisionRevisão—simpor decisão manualEtapas + em massa
reviewer.assignedRevisão—simpor reatribuiçãoSomente reatribuição explícita
reviewer.comment_addedRevisão—simpor comentárioCorpo fora do contexto
edd.triggeredRevisãoapplicantsimpor início de EDD
score.updatedRiscoapplicantsimpor (re)pontuaçãoDispara junto com a decisão
score.thresholdRiscoapplicantsimpor (re)pontuaçãoRestringido por scoreMin/scoreMax
scheduleTempo—nãocronExecução em nível de organização
verification.expiredTempoapplicantsimpor expiraçãoDo cron de expiração
monitoring.alertMonitoramento—simpor alertaTriagem contínua
inbound.webhookExterno—opcionalpor POSTPayload arbitrário
conference.completedExterno—simpor conferênciaSessões de Live ID

Gatilhos do roadmap

Alguns gatilhos muito solicitados não estão no seletor, porque a plataforma ainda não tem um sinal para emiti-los — deixá-los como opções selecionáveis criaria gatilhos que nunca disparariam. Eles estão listados aqui com a alternativa que funciona, para que a lacuna fique explícita. (Tudo o que está no seletor, por outro lado, é de fato emitido — incluindo score.threshold, comparação facial, PEP, UBO e os eventos de revisor, que foram conectados nesta versão.)

Gatilho solicitadoPor que não é emitidoO que usar no lugar
Sinal de fraude detectadoAinda não existe um tipo de checagem independente de perícia / autenticidade (a perícia hoje é um fator de pontuação de risco, não uma checagem discreta que emite resultado).Ramifique em document.failed, ou nos eventos de decisão / score.threshold pelo risco resultante.
Dados do registro empresarial atualizadosO KYB é uma verificação única; não há um job contínuo de nova consulta / monitoramento do registro para disparar.Refaça a verificação em um schedule e ramifique em kyb.match_* / kyb.ubo_completed.
Link de e-mail / SMS clicadoNão existe infraestrutura de rastreamento de abertura de e-mail nem de cliques em links.Use form.submitted / document.uploaded como sinais concretos de que o solicitante agiu.

Estes são itens genuínos do roadmap, não lacunas de documentação — o enum de gatilhos do motor é a única fonte de verdade, e esta tabela reflete o que ele ainda não contém.


Boas práticas

  • Sempre defina um assunto + escopo. Um gatilho sem assunto é de toda a organização e dispara para cada objeto correspondente na organização. Prefira escopo specific ou category; reserve all para automações genuinamente de toda a organização.
  • Escute eventos específicos, não o genérico, sempre que possível. Use liveness.failed / sanctions.hit em vez de interpretar check.completed, e um único evento de decisão em vez de request.status_changed — a menos que você precise especificamente de um status sem evento dedicado.
  • Não escute os eventos de status específico e genérico ao mesmo tempo. request.approved e request.status_changed disparam ambos na aprovação; vincular dois fluxos (ou dois gatilhos) ao mesmo resultado executa os efeitos colaterais duas vezes.
  • Trate o contexto como um snapshot. O contexto do evento é capturado no disparo. Se o seu fluxo esperar (um nó de wait, um atraso longo), releia o estado atual dentro de uma Ação em vez de confiar em um contexto desatualizado para uma decisão crítica.
  • Mantenha os fluxos de check.completed enxutos. É o gatilho de maior frequência; restrinja-o a uma categoria e filtre cedo para não gerar disparos em cada checagem de cada solicitação.
  • Atenção aos escopos órfãos. Excluir um formulário / solicitante referenciado por um escopo specific deixa o gatilho inativo (ele não dispara e mostra um selo "Precisa ser revinculado"). Vincule-o novamente a um objeto ativo para reativá-lo.
  • Fluxos encaminham, nunca decidem. Se o resultado de um gatilho deve mudar o veredito, direcione-o pela ação escalate para a revisão humana — intencionalmente não existe ação de aprovar / rejeitar.
  • Espere execução eventual e ao menos uma vez. O disparo é assíncrono e sem espera; torne os efeitos colaterais das Ações idempotentes onde isso importa (por exemplo, inclua uma referência externa para que um novo disparo não duplique registros downstream).

Perguntas frequentes

Um fluxo pode aprovar ou rejeitar uma verificação? Não. O conjunto de Ações não tem approve / reject. O controle de resultado mais forte é escalate, que move a solicitação para a revisão humana pela máquina de estados existente, com bloqueio otimista e auditada. O veredito pertence à política de decisão e aos revisores humanos.

Uma decisão específica e request.status_changed dispararam ambos — isso é um bug? Não. O request.status_changed genérico é sempre emitido junto com o evento específico, para que você possa reagir a status que não têm evento dedicado. Escute um, não os dois.

Por que meu fluxo de check.completed não foi executado? O fan-out por checagem é protegido: ele só dispara quando um fluxo ativo escuta check.completed. Confirme que o fluxo está ativo e que o escopo / filtro corresponde à checagem. Observe também que checagens com erro sem um booleano utilizável não emitem os sinais específicos liveness.* / sanctions.*.

Por que meu gatilho score.threshold não está disparando? Ele dispara em toda finalização pontuada, e depois o despachante o restringe pelos limites scoreMin / scoreMax do gatilho. Se nunca executar, verifique se a pontuação realmente cai dentro dos seus limites e se a solicitação chegou a um resultado pontuado (uma solicitação cancelada antes da pontuação não emite nenhum evento de pontuação). Sem limites definidos, ele se comporta como score.updated.

Qual é a diferença entre score.updated e score.threshold? Mesmo ponto de emissão e mesmo contexto. score.updated sempre executa; score.threshold só executa quando overallScore cai dentro dos limites scoreMin / scoreMax do gatilho — uma conveniência embutida, para você não precisar escrever o filtro de overallScore manualmente.

Como disparo um gatilho com base em um resultado de comparação facial ou de PEP? Use os eventos dedicados face_match.passed / face_match.failed e pep.match. Uma correspondência de PEP também dispara sanctions.hit (a triagem de PEP usa a triagem de listas de observação), então escute pep.match quando você se importar apenas com exposição a PEP.

Excluir um formulário desativa os fluxos dele? Um fluxo cujo gatilho tem escopo specific restrito somente àquele formulário torna-se órfão: fica inativo (não dispara) e mostra um selo "Precisa ser revinculado". Vincule-o novamente a um objeto ativo para reativá-lo. Ele nunca cai silenciosamente para disparar em toda a organização.

O disparo é síncrono com a minha chamada de API? Não. O disparo é fire-and-forget e roda fora de banda. A ação de origem (envio, mudança de status) é concluída independentemente de o disparo ou a execução do fluxo ter sucesso.

Um fluxo pode disparar outro? Sim — a ação trigger_workflow executa diretamente um fluxo salvo escolhido, limitada a uma profundidade máxima de cadeia de 5 para evitar loops.


Apêndice

Glossário

  • Gatilho — o único nó de entrada que declara o evento, o assunto, o escopo e o filtro que iniciam um fluxo.
  • Evento — um WorkflowTriggerEvent que a plataforma emite em um ponto real do ciclo de vida da verificação.
  • Assunto — o tipo de objeto ao qual um gatilho se vincula: form, applicant, document ou check.
  • Escopo — como o assunto é restringido: specific (ids), category ou all.
  • Contexto — os fatos específicos do evento capturados no disparo e lidos pelos filtros e pelos modelos de Ação.
  • Filtro — um predicado DSL opcional que restringe ainda mais quais eventos correspondem.
  • Gatilho órfão — um gatilho com escopo specific cujos objetos foram excluídos; fica inativo até ser vinculado novamente.
  • Execução — um KycWorkflowRun: uma execução de um fluxo, com um log somente de acréscimo.

Enum completo de eventos

type WorkflowTriggerEvent =
  | "form.submitted" | "request.created"
  | "request.status_changed" | "request.approved" | "request.rejected"
  | "request.escalated" | "request.resubmitted" | "request.documents_ready"
  | "document.uploaded" | "document.failed" | "check.completed"
  | "liveness.passed" | "liveness.failed"
  | "face_match.passed" | "face_match.failed"
  | "sanctions.hit" | "sanctions.cleared" | "pep.match"
  | "kyb.match_found" | "kyb.match_not_found" | "kyb.ubo_completed"
  | "reviewer.decision" | "reviewer.assigned" | "reviewer.comment_added"
  | "score.threshold" | "score.updated" | "edd.triggered"
  | "monitoring.alert" | "verification.expired" | "schedule"
  | "inbound.webhook" | "conference.completed";

Ciclo de vida de um disparo

1. Something happens            (form submit, check finishes, cron, webhook…)
2. Emitter calls                enqueueWorkflowDispatch({ event, …, context })
3. workflow.worker picks it up  → dispatchWorkflowEvent(input)
4. Match active workflows       trigger.event === input.event
5. Narrow by subject + scope    specific ids / category / all  (orphans excluded)
6. Evaluate filter (DSL)        against input.context  (missing fields = false)
7. Create KycWorkflowRun        context stashed on the first log entry
8. Execute nodes                Condition → Action → Wait → … → End

Exemplo de ponta a ponta

Uma automação completa de onboarding vinculada a um único formulário de KYC:

trigger:
  event: form.submitted
  subject: form
  scope: { mode: specific, ids: ["form_onboarding_01H..."] }

# graph (conceptual):
#   ▸ Trigger: form.submitted
#   → Action: email applicant "We received your submission"
#   → Wait: until check.completed (or 24h timeout)
#   → Condition: sanctions.isMatch = true
#        true  → Action: escalate  +  Action: notify compliance (Slack)
#        false → Condition: overallScore >= 80
#                     true  → Action: generate_pdf_report → email applicant
#                     false → Action: assign_department "manual-review"
#   → End

Isto nunca define o veredito: a única etapa que afeta o resultado é escalate, que direciona para a revisão humana. Todo o resto é notificação, coleta de dados e relatório em torno da decisão que a política e os revisores detêm.