Flussi di lavoro automatizzatiTrigger

Trigger

Riferimento completo di ogni trigger dei Flussi di lavoro automatizzati di Certivant — quando si attiva ciascun evento, il suo contesto dati, esempi di payload, configurazione di soggetto e ambito e schemi di filtro DSL.

Un trigger è il singolo nodo di ingresso di ogni Flusso di lavoro automatizzato. Dichiara quale evento avvia il workflow, a quali oggetti quell'evento deve riferirsi (il soggetto + l'ambito) e un filtro facoltativo che restringe ulteriormente gli eventi idonei. Quando la piattaforma emette un evento corrispondente, viene creata una KycWorkflowRun e il workflow viene eseguito nodo per nodo.

Questa pagina documenta ogni trigger che il motore emette effettivamente, basandosi sui punti di emissione della piattaforma. Ogni voce indica quando si attiva, il context esatto che trasporta, un payload di esempio reale e come limitarlo e filtrarlo.

Il catalogo dei trigger è un enum fisso nel motore (WorkflowTriggerEvent). Il selettore dell'editor, questa pagina, lo schema di validazione dell'API e il dispatcher leggono tutti dalla stessa lista, quindi ogni trigger che può selezionare è un trigger che la piattaforma emette davvero — nessuno è decorativo. Alcune funzioni che non hanno ancora un punto di emissione sono volutamente escluse dal selettore e riportate in Trigger previsti con l'alternativa funzionante, così non si costruisce mai un workflow che non verrà eseguito in silenzio.


Come si attivano i trigger

I trigger non vengono interrogati periodicamente. Ciascuno corrisponde a un punto reale della piattaforma in cui il codice chiama il dispatcher:

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

Per chi integra sono importanti tre proprietà:

  • Fire-and-forget. L'invio è racchiuso in modo che un errore non interrompa mai l'azione di origine. Se il caricamento di un documento attiva un workflow e l'invio fallisce, il caricamento va comunque a buon fine. Consideri gli effetti collaterali dei workflow come eseguiti in modo eventuale, non in modo sincrono con la chiamata API.
  • Un evento, molte esecuzioni. Un singolo evento può avviare ogni workflow attivo che gli corrisponde. Usi soggetto + ambito (e filtri) per mantenere intenzionale questa diramazione.
  • Il contesto viene catturato una sola volta. L'oggetto context dell'evento viene salvato nella prima voce di log dell'esecuzione. I nodi Condizione, i modelli delle Azioni e i segnaposto per la stampa unione leggono da quell'istantanea — non interrogano di nuovo lo stato attuale.

La busta dell'invio

Ogni emissione produce un WorkflowEventInput. Oltre a organizationId ed event, i campi sono facoltativi e dipendono dall'evento:

CampoTipoPresente quando
organizationIdstringasempre
eventWorkflowTriggerEventsempre
kycRequestIdstringa | nulll'evento riguarda una verifica specifica (la maggior parte degli eventi)
formIdstringa | nullform.submitted
formKind"kyc" | "kyb" | nullform.submitted
applicantIdstringa | nulleventi legati a un richiedente
documentTypestringa | nulldocument.*
checkKindstringa | nullcheck.completed, liveness.*, sanctions.*
contextoggettofatti specifici dell'evento (vedi ogni trigger sotto)
workflowIdstringasolo in un'invocazione diretta da trigger_workflow
chainDepthnumeroprofondità della catena workflow-a-workflow (massimo 5)

subject, scope, filter, formIds, schedule, scoreMin e scoreMax sono proprietà del trigger che configura — non dell'evento in esecuzione. Il dispatcher li usa per decidere se un evento in arrivo corrisponde al suo workflow.


La definizione del trigger

Questa è la struttura salvata nel trigger di un workflow (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
}

Soggetto + ambito

Il soggetto è il tipo di oggetto che il trigger osserva; l'ambito lo restringe a oggetti specifici, a una categoria o a tutti. È ciò che impedisce a un trigger di attivarsi per errore su tutta l'organizzazione.

SoggettoEventi validiModalità di scope.modeSignificato di category
formform.submittedspecific (id dei moduli) · category · all"kyc" oppure "kyb"
applicantrequest.*, score.threshold, edd.triggered, verification.expiredspecific (id dei richiedenti) · all—
documentdocument.uploaded, document.failedspecific · category · allun tipo di documento, per esempio "passport"
checkcheck.completed, liveness.*, sanctions.*specific · category · allun tipo di controllo, per esempio "sanctions"
(nessuno)schedule, inbound.webhook, monitoring.alert, kyb.*, reviewer.decision, conference.completed—a livello di organizzazione

Modalità di ambito:

  • specific — corrisponde solo agli ids elencati (per esempio si attiva solo per questo modulo di onboarding). Se ogni id di un ambito specific punta a un oggetto eliminato, il trigger è orfano: il dispatcher si rifiuta di attivarlo (resta inattivo invece di ripiegare silenziosamente su "tutti"), e l'editor mostra un badge "Richiede una nuova associazione".
  • category — corrisponde a una classe di oggetti (tutti i moduli KYC, tutti i passaporti, tutti i controlli sanzioni).
  • all — corrisponde a qualsiasi oggetto del tipo di soggetto nell'organizzazione.

Un trigger senza subject mantiene il comportamento precedente: a livello di organizzazione, eventualmente ristretto da formIds per form.submitted. I nuovi workflow dovrebbero sempre impostare un soggetto + un ambito.

Filtro (DSL)

filter è un predicato booleano facoltativo, con la stessa grammatica delle espressioni del DSL delle politiche decisionali. Viene eseguito sul context dell'evento. Un filtro vuoto si attiva per ogni evento di quel tipo. Vedi Filtrare con il DSL.


Categorie di trigger

CategoriaTrigger
Ingressoform.submitted, request.created
Ciclo di vita / statorequest.status_changed, request.approved, request.rejected, request.escalated, request.resubmitted, request.documents_ready
Documentidocument.uploaded, document.failed
Segnali di verificacheck.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
Revisione umanareviewer.decision, reviewer.assigned, reviewer.comment_added, edd.triggered
Rischio / punteggioscore.threshold, score.updated
Tempo / monitoraggioschedule, verification.expired, monitoring.alert
Esterni / integrazioniinbound.webhook, conference.completed

Riferimento dei trigger

Ogni voce qui sotto usa la stessa struttura: Quando si attiva, Contesto dei dati, Evento di esempio e Esempio di utilizzo nel workflow. Ogni payload è la forma reale dell'invio prodotta dal codice che emette l'evento.

Ingresso

form.submitted

"Un modulo viene inviato" · soggetto form

Quando si attiva. Un modulo di verifica pubblico e ospitato viene inviato da un richiedente. Emesso dalla route dei moduli pubblici nel momento in cui un invio crea il suo KycRequest. L'evento complementare request.created viene emesso insieme ad esso.

Contesto dei dati.

CampoTipoNote
typestringaTipo di richiedente al momento dell'invio, per esempio "INDIVIDUAL"

Al primo livello: formId, formKind ("kyc"/"kyb"), kycRequestId, applicantId.

Evento di esempio.

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

Esempio di utilizzo nel workflow. Colleghi un workflow di email di benvenuto + richiesta documenti a un solo modulo di onboarding specifico:

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

request.created

"Una verifica viene creata" · soggetto applicant

Quando si attiva. Viene creata una richiesta di verifica — tramite POST /api/v1/kyc/requests (201) oppure come complemento a un form.submitted ospitato. Si attiva prima che venga caricato un documento o che venga eseguito un controllo, quindi usatelo per l'instradamento in ingresso, non per gli esiti.

Contesto dei dati.

CampoTipoNote
applicant.countrystringa | nullISO-3166 alpha-2, dalla cittadinanza del richiedente
applicant.typestringa"INDIVIDUAL" oppure "BUSINESS"
typestringaTipo di richiesta (INDIVIDUAL / BUSINESS)

Evento di esempio.

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

Esempio di utilizzo nel workflow. Instradi i richiedenti aziendali verso una coda di acquisizione KYB:

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

Ciclo di vita / stato

Questi si attivano dopo che l'orchestrazione ha finalizzato un verdetto. Trasportano tutti lo stesso contesto di decisione. Gli eventi specifici (approved/rejected/escalated) si attivano in aggiunta al request.status_changed generico: si ascolti uno dei due — non entrambi — per evitare un'esecuzione doppia.

Contesto dei dati condiviso (request.approved, request.rejected, request.escalated, request.status_changed):

CampoTipoNote
statusstringaNuovo stato, per esempio "APPROVED", "REJECTED", "ESCALATED", "PENDING_SECOND_REVIEW"
decisionstringa | nullVerdetto del motore di scoring: "approve" / "reject" / "escalate"
overallScorenumero | nullPunteggio di affidabilità 0–100 (più alto = rischio più basso)
applicant.countrystringa | nullISO-3166 alpha-2
applicant.typestringaINDIVIDUAL / BUSINESS

request.approved

"Una verifica viene approvata" · soggetto applicant

Quando si attiva. L'orchestrazione finalizza la richiesta come 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" }
  }
}

Esempio di utilizzo nel workflow. Generi e invii via email un report di verifica solo per le approvazioni ad alta affidabilità:

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

request.rejected

"Una verifica viene rifiutata" · soggetto applicant

Quando si attiva. L'orchestrazione finalizza la richiesta come REJECTED. Contesto identico a request.approved, con status: "REJECTED", decision: "reject".

Esempio di utilizzo nel workflow. Avvisi il manager assegnato e marchi il richiedente in caso di rifiuto:

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

request.escalated

"Una verifica viene inoltrata" · soggetto applicant

Quando si attiva. L'orchestrazione (oppure un'azione escalate di un workflow precedente) sposta la richiesta in ESCALATED / revisione umana. Contesto identico alla forma condivisa, con status: "ESCALATED".

Esempio di utilizzo nel workflow. Pianifichi una conferenza Live ID ogni volta che una richiesta viene inoltrata:

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

request.status_changed

"Lo stato di una verifica cambia" · soggetto applicant

Quando si attiva. Viene sempre emesso a una variazione di stato finalizzata, insieme a qualsiasi evento di decisione specifico. Lo usi per reagire a stati che non hanno un evento dedicato (per esempio PENDING_SECOND_REVIEW, CANCELLED).

Esempio di utilizzo nel workflow. Reagisca solo al passaggio di consegne tra due revisori:

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

request.resubmitted

"Una verifica viene reinviata" · soggetto applicant

Quando si attiva. Un revisore rimanda una richiesta per il reinvio dalla dashboard; la richiesta torna in PENDING_UPLOAD.

Contesto dei dati.

CampoTipoNote
statusstringa"PENDING_UPLOAD"
kindstringa"resubmit"
{
  "organizationId": "org_01H...",
  "event": "request.resubmitted",
  "kycRequestId": "req_01J...",
  "context": { "status": "PENDING_UPLOAD", "kind": "resubmit" }
}

Esempio di utilizzo nel workflow. Reinvii al richiedente il link per il caricamento e avvii un timer di promemoria.

request.documents_ready

"Tutti i documenti richiesti sono caricati" · soggetto applicant

Quando si attiva. Il richiedente ha appena caricato l'ultimo elemento della checklist dei documenti richiesti, quindi la richiesta passa in DOCUMENTS_READY. Emesso da entrambi i percorsi di caricamento — l'API documentale della dashboard e la sessione di verifica ospitata — solo sul caricamento che svuota l'insieme dei documenti in sospeso. È il segnale concreto di "il richiedente ha completato ogni passo richiesto".

Contesto dei dati.

CampoTipoNote
statusstringa"DOCUMENTS_READY"
{
  "organizationId": "org_01H...",
  "event": "request.documents_ready",
  "kycRequestId": "req_01J...",
  "context": { "status": "DOCUMENTS_READY" }
}

Esempio di utilizzo nel workflow. Avvisi il revisore assegnato nel momento in cui una pratica è pronta per la revisione:

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

Documenti

document.uploaded

"Un documento viene caricato" · soggetto document

Quando si attiva. Ogni volta che viene caricato un documento — tramite l'API della dashboard oppure una sessione di verifica ospitata. Un evento per ogni documento.

Contesto dei dati.

CampoTipoNote
document.typestringaper esempio "passport", "id_card", "drivers_license"
document.sidestringa | null"front" / "back" (null quando non applicabile)

Al primo livello: documentType, kycRequestId.

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

Esempio di utilizzo nel workflow. Si attivi solo sui caricamenti della prova di residenza:

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

document.failed

"Un documento non supera la validazione" · soggetto document

Quando si attiva. L'orchestrazione segna un documento come failed (l'OCR non è potuto essere eseguito, l'autenticità è stata respinta, ecc.). Un evento per ogni documento non riuscito. La stessa forma di contesto di document.uploaded.

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

Esempio di utilizzo nel workflow. Richieda automaticamente una nuova acquisizione di ogni documento non riuscito.

Segnali di verifica

check.completed

"Un controllo di verifica si completa" · soggetto check

Quando si attiva. Una volta per ogni singolo controllo che termina durante un'esecuzione dell'orchestrazione (OCR, confronto del volto, vivacità, sanzioni, autenticità del documento, lettura NFC…). È il segnale generico e ad alta frequenza — per un esito chiaro preferisca liveness.* / sanctions.*.

Diramazione protetta. Poiché più controlli terminano per ogni richiesta, la piattaforma chiede prima se l'organizzazione ha almeno un workflow attivo in ascolto di check.completed. Senza ascoltatori non vengono creati job di invio. Il segno di avanzamento per esecuzione impedisce che una nuova verifica riemetta i controlli di un'esecuzione precedente.

Contesto dei dati.

CampoTipoNote
check.typestringaTipo di controllo, per esempio "sanctions", "face_match", "ocr"
check.statusstringa"passed" / "failed" / "error"
check.providerstringa | nullFornitore che ha eseguito il controllo
check.scorenumero | nullPunteggio del fornitore, dove applicabile

Al primo livello: 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 }
  }
}

Esempio di utilizzo nel workflow. Reagisca al completamento di qualsiasi controllo sanzioni, indipendentemente dall'esito (per un passa/fallisci netto preferisca gli eventi dedicati sanctions.* / face_match.* qui sotto):

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

liveness.passed / liveness.failed

"Vivacità superata" / "Vivacità non superata" · soggetto check (liveness)

Quando si attiva. Dopo l'orchestrazione, quando il controllo di vivacità ha salvato un booleano isLive non ambiguo. passed quando isLive è vero, failed quando è falso. Un controllo di vivacità in errore o ambiguo non emette nessuno dei due.

Contesto dei dati.

CampoTipoNote
liveness.isLivebooleanoSe il richiedente ha superato la vivacità
liveness.confidencenumero | nullConfidenza 0–1
{
  "organizationId": "org_01H...",
  "event": "liveness.failed",
  "kycRequestId": "req_01J...",
  "checkKind": "liveness",
  "context": { "liveness": { "isLive": false, "confidence": 0.31 } }
}

Esempio di utilizzo nel workflow. Inoltri in caso di mancata vivacità con bassa confidenza:

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

face_match.passed / face_match.failed

"Confronto del volto superato" / "Confronto del volto non superato" · soggetto check (face_match)

Quando si attiva. Dopo l'orchestrazione, quando il controllo di confronto del volto (selfie ↔ ritratto del documento) ha salvato un booleano isMatch non ambiguo. passed quando isMatch è vero, failed quando è falso. Un controllo in errore o ambiguo non emette nessuno dei due.

Contesto dei dati.

CampoTipoNote
faceMatch.isMatchbooleanoSe il selfie corrisponde al volto del documento
faceMatch.confidencenumero | nullConfidenza 0–1
{
  "organizationId": "org_01H...",
  "event": "face_match.failed",
  "kycRequestId": "req_01J...",
  "checkKind": "face_match",
  "context": { "faceMatch": { "isMatch": false, "confidence": 0.42 } }
}

Esempio di utilizzo nel workflow. Inoltri ogni mancato confronto del volto per una revisione manuale:

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

sanctions.hit / sanctions.cleared

"Riscontro nelle sanzioni" / "Sanzioni escluse" · soggetto check (sanctions)

Quando si attiva. Dopo l'orchestrazione, dal risultato salvato di sanzioni/liste di controllo. hit quando una corrispondenza raggiunge la soglia (oppure quando è presente una corrispondenza qualsiasi); cleared altrimenti. Una corrispondenza che riguarda specificamente una persona politicamente esposta attiva anche l'evento dedicato pep.match, quindi lo ascolti quando le interessa solo l'esposizione PEP.

Contesto dei dati.

CampoTipoNote
sanctions.isMatchbooleanoVero in caso di riscontro
sanctions.matchCountnumeroNumero di corrispondenze nelle liste
sanctions.topScorenumero | nullPunteggio della corrispondenza più forte, 0–1
sanctions.listsCheckedstringa[]Liste verificate (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"] }
  }
}

Esempio di utilizzo nel workflow. Inoltri e avvisi la compliance in caso di riscontro forte nelle sanzioni:

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

pep.match

"Trovata una corrispondenza PEP" · soggetto check (sanctions)

Quando si attiva. Dopo l'orchestrazione, quando il risultato di sanzioni/liste di controllo contiene almeno una corrispondenza il cui tipo di entità è una persona politicamente esposta. Si basa sullo stesso screening di sanctions.*, ma si attiva solo quando è presente una voce PEP — un semplice riscontro in lista senza voci PEP non lo attiva.

Contesto dei dati.

CampoTipoNote
pep.isMatchbooleanoSempre true (l'evento si attiva solo per una corrispondenza PEP)
pep.matchCountnumeroNumero di corrispondenze PEP
{
  "organizationId": "org_01H...",
  "event": "pep.match",
  "kycRequestId": "req_01J...",
  "checkKind": "sanctions",
  "context": { "pep": { "isMatch": true, "matchCount": 1 } }
}

Esempio di utilizzo nel workflow. Avvii automaticamente la Due Diligence Rafforzata (EDD) e instradi alla compliance ogni volta che viene rilevata una PEP:

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

kyb.match_found / kyb.match_not_found

"Trovata una corrispondenza aziendale KYB" / "Corrispondenza KYB non trovata" · a livello di organizzazione

Quando si attiva. Solo per richieste BUSINESS, dopo che l'orchestrazione scrive il risultato della verifica nel registro delle imprese. verified → kyb.match_found; failed → kyb.match_not_found. Qualsiasi altro stato (in attesa/assente) è ambiguo e non emette nulla.

Contesto dei dati.

CampoTipoNote
kyb.statusstringa"verified" oppure "failed"
{
  "organizationId": "org_01H...",
  "event": "kyb.match_not_found",
  "kycRequestId": "req_01J...",
  "context": { "kyb": { "status": "failed" } }
}

Esempio di utilizzo nel workflow. Richieda documenti di costituzione aggiuntivi quando non viene trovata alcuna corrispondenza nel registro.

kyb.ubo_completed

"Verifica dei titolari effettivi completata" · a livello di organizzazione

Quando si attiva. Solo per richieste BUSINESS, dopo che l'orchestrazione ha risolto ogni record di titolare effettivo (UBO) dell'azienda (nessuno rimasto pending). Trasporta il numero di UBO sottoposti a screening e quanti hanno fallito, così un workflow può ramificarsi in base al rischio UBO senza interrogare di nuovo. Si attiva solo quando l'azienda ha davvero dei record UBO e la riga KYB è stata toccata in questa esecuzione.

Contesto dei dati.

CampoTipoNote
kyb.uboCountnumeroNumero di titolari effettivi sottoposti a screening
kyb.failedUboCountnumeroQuanti di questi UBO non hanno superato la verifica
{
  "organizationId": "org_01H...",
  "event": "kyb.ubo_completed",
  "kycRequestId": "req_01J...",
  "context": { "kyb": { "uboCount": 3, "failedUboCount": 1 } }
}

Esempio di utilizzo nel workflow. Inoltri quando un titolare effettivo non supera lo screening:

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

Revisione umana

reviewer.decision

"Un revisore prende una decisione manuale" · a livello di organizzazione

Quando si attiva. Un revisore umano registra una decisione manuale nella dashboard — approvazione singola, una delle due fasi di un flusso a due revisori, oppure un'azione di massa.

Contesto dei dati.

CampoTipoNote
decisionstringaLa scelta del revisore
reviewerIdstringaID del revisore che decide
stagestringa | undefined"first_approval" / "second_approval" nei flussi a due revisori
viastringa | undefined"bulk" quando fa parte di un'azione di massa
{
  "organizationId": "org_01H...",
  "event": "reviewer.decision",
  "kycRequestId": "req_01J...",
  "context": { "decision": "approve", "reviewerId": "usr_01H...",
               "stage": "second_approval" }
}

Esempio di utilizzo nel workflow. Registri nel log di audit ogni decisione manuale e avvisi il richiedente dopo un'approvazione finale (la seconda).

reviewer.assigned

"Una richiesta viene assegnata a un revisore" · a livello di organizzazione

Quando si attiva. Una richiesta viene riassegnata a un nuovo titolare/revisore tramite l'azione di riassegnazione della dashboard. Il titolare iniziale impostato alla creazione della richiesta non attiva questo evento — lo attiva solo una riassegnazione esplicita, quindi l'evento segnala un vero cambio di instradamento.

Contesto dei dati.

CampoTipoNote
assignedUserIdstringaIl nuovo titolare/revisore
previousUserIdstringa | nullIl titolare precedente
teamIdstringa | nullIl team a cui la richiesta ora appartiene
{
  "organizationId": "org_01H...",
  "event": "reviewer.assigned",
  "kycRequestId": "req_01J...",
  "context": { "assignedUserId": "usr_new...", "previousUserId": "usr_old...",
               "teamId": "team_01H..." }
}

Esempio di utilizzo nel workflow. Invii al revisore appena assegnato una notifica con un link diretto alla pratica.

reviewer.comment_added

"Un revisore aggiunge un commento" · a livello di organizzazione

Quando si attiva. Un revisore pubblica un commento su una pratica (il flusso dei commenti della pratica). Il corpo del commento non viene intenzionalmente inserito nel contesto dell'evento — può contenere note sensibili — quindi l'evento trasporta solo i metadati.

Contesto dei dati.

CampoTipoNote
commentIdstringaID del commento creato
authorIdstringaIl revisore che ha commentato
authorNamestringaNome visualizzato dell'autore
{
  "organizationId": "org_01H...",
  "event": "reviewer.comment_added",
  "kycRequestId": "req_01J...",
  "context": { "commentId": "cmt_01J...", "authorId": "usr_01H...",
               "authorName": "Jordan Lee" }
}

Esempio di utilizzo nel workflow. Ricopi i nuovi commenti della pratica in un canale Slack per il team di compliance.

edd.triggered

"Viene avviata la Due Diligence Rafforzata (EDD)" · soggetto applicant

Quando si attiva. La Due Diligence Rafforzata (EDD) viene avviata per una richiesta (attualmente tramite l'azione EDD manuale).

Contesto dei dati.

CampoTipoNote
kindstringa"edd"
{
  "organizationId": "org_01H...",
  "event": "edd.triggered",
  "kycRequestId": "req_01J...",
  "context": { "kind": "edd" }
}

Esempio di utilizzo nel workflow. All'avvio della EDD, richieda documenti sull'origine dei fondi e assegni la pratica al reparto compliance.

Rischio / punteggio

Entrambi gli eventi di punteggio si attivano dal percorso di decisione, una volta che lo scorer ha scritto un punteggio di affidabilità per l'esecuzione (una richiesta che si finalizza senza punteggio — per esempio annullata prima dello scoring — non emette nessuno dei due). Trasportano l'intero contesto di decisione, quindi overallScore, decision, status e applicant.* sono tutti disponibili per i filtri.

score.updated

"Il punteggio di affidabilità viene aggiornato" · soggetto applicant

Quando si attiva. A ogni finalizzazione con (ri)calcolo del punteggio — inclusa una rianalisi che ricalcola il punteggio. Lo usi per reagire al nuovo punteggio di affidabilità indipendentemente dal verdetto.

Contesto dei dati. Il contesto di decisione condiviso (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" }
  }
}

Esempio di utilizzo nel workflow. Contrassegni ogni richiesta il cui punteggio cade in una fascia di revisione manuale:

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

score.threshold

"Il punteggio di affidabilità supera una soglia" · soggetto applicant

Quando si attiva. Stesso punto di emissione di score.updated, ma il dispatcher lo restringe in base ai limiti scoreMin / scoreMax del trigger stesso (legge context.overallScore). Imposti i limiti sul trigger e il workflow viene eseguito solo quando il punteggio rientra in essi — una comodità rispetto a scrivere a mano un filtro su overallScore.

Contesto dei dati. Identico a 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" }
  }
}

Esempio di utilizzo nel workflow. Si esegua solo per i punteggi bassi impostando un limite superiore sul trigger:

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

scoreMin / scoreMax sono inclusivi. Ne imposti uno o entrambi. Se non ne imposta nessuno, score.threshold si comporta come score.updated.

Tempo / monitoraggio

schedule

"Secondo una pianificazione" · a livello di organizzazione, non legato a una richiesta

Quando si attiva. Secondo l'espressione cron del workflow stesso (impostata nel campo schedule del trigger). L'esecuzione è a livello di organizzazione — kycRequestId è null — quindi lo usi per lavori batch o di manutenzione, non per una logica per singolo richiedente.

Il cron è un'espressione standard a 5 campi (minuto ora giorno mese giorno-della-settimana) valutata in UTC e non può attivarsi più spesso di ogni 5 minuti: la pubblicazione rifiuta * * * * * o */2 * * * *.

Contesto dei dati.

CampoTipoNote
firedAtstringaMomento di attivazione in ISO-8601
cronstringaL'espressione cron che si è attivata
{
  "organizationId": "org_01H...",
  "event": "schedule",
  "kycRequestId": null,
  "context": { "firedAt": "2026-05-31T09:00:00.000Z", "cron": "0 9 * * 1" }
}

Esempio di utilizzo nel workflow. Ogni lunedì alle 09:00 genera un riepilogo settimanale delle verifiche e lo pubblica su Slack:

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

verification.expired

"Una verifica scade" · soggetto applicant

Quando si attiva. Dal cron di scadenza, quando una richiesta supera la propria finestra di validità.

Contesto dei dati.

CampoTipoNote
statusstringa"EXPIRED"
{
  "organizationId": "org_01H...",
  "event": "verification.expired",
  "kycRequestId": "req_01J...",
  "context": { "status": "EXPIRED" }
}

Esempio di utilizzo nel workflow. Invii al richiedente un nuovo link via email e avvisi il titolare quando una verifica scade.

monitoring.alert

"Scatta un avviso di monitoraggio" · a livello di organizzazione

Quando si attiva. Dal nuovo screening continuo delle sanzioni, quando un soggetto già escluso trova una nuova corrispondenza in una lista di controllo (monitoraggio continuo).

Contesto dei dati.

CampoTipoNote
alertTypestringaIl tipo di avviso di monitoraggio
severitystringaGravità dell'avviso
topScorenumero | nullPunteggio della nuova corrispondenza più forte
{
  "organizationId": "org_01H...",
  "event": "monitoring.alert",
  "kycRequestId": "req_01J...",
  "context": { "alertType": "sanctions_rescreen", "severity": "high",
               "topScore": 0.88 }
}

Esempio di utilizzo nel workflow. Su un avviso di monitoraggio di gravità alta, inoltri la richiesta e apra una pratica di compliance.

Esterni / integrazioni

inbound.webhook

"Arriva un webhook in ingresso" · a livello di organizzazione, payload arbitrario

Quando si attiva. Il Suo sistema invia una POST all'endpoint dei webhook in ingresso del workflow. Il context è il JSON che invia, quindi controlla interamente la sua forma. Non è legato a una richiesta, a meno che il payload non fornisca un ID che il workflow usa.

Endpoint. POST /api/v1/kyc/workflows/inbound, autenticato con una chiave API che ha lo scope workflows:trigger. Nessuno scope kyc:* lo include: selezioni Workflow di automazione → Attivare quando crea o modifica la chiave. Il Suo piano deve includere i workflow di automazione (altrimenti 402). Invii context (il Suo oggetto JSON, massimo 64 KB) e, facoltativamente, eventId (il Suo ID: un nuovo tentativo con lo stesso eventId non avvia mai due volte lo stesso workflow). La chiamata risponde 202 e le esecuzioni avvengono in background. Una chiave limitata a determinati workflow avvia solo quelli.

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"}}'

Contesto dei dati. Arbitrario — il corpo JSON inviato, così com'è.

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

Esempio di utilizzo nel workflow. Lasci che il Suo CRM avvii un batch di nuova verifica per i clienti segnalati; filtri su un campo che ha inviato:

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

conference.completed

"Una conferenza dal vivo si completa" · a livello di organizzazione

Quando si attiva. Una conferenza web di verifica dell'identità dal vivo termina e il conduttore (notaio/revisore) registra una decisione. Emesso una sola volta, con una protezione che impedisce a una decisione concorrente di attivarlo due volte.

Contesto dei dati.

CampoTipoNote
decisionstringaLa decisione registrata dal conduttore
statusstringaLo stato risultante della richiesta
{
  "organizationId": "org_01H...",
  "event": "conference.completed",
  "kycRequestId": "req_01J...",
  "context": { "decision": "approve", "status": "APPROVED" }
}

Esempio di utilizzo nel workflow. Dopo una conferenza, generi il Certificato di verifica firmato e lo invii via email al richiedente.


Filtrare con il DSL

Il filtro del trigger (e ogni nodo Condizione) usa la grammatica delle espressioni delle politiche — un solo predicato booleano, senza chiamate di funzione:

  • Campi: il contesto dell'evento appiattito (i campi mostrati nella tabella Contesto dei dati di ogni trigger).
  • Operatori: = != > >= < <=, IN / NOT IN.
  • Combinatori: AND OR NOT, con raggruppamento ( ).
  • Letterali: numeri, "stringhe", true / false / null, e liste [...].

L'operatore di uguaglianza è un singolo = (non ==). I campi mancanti sono a prova di errore: un campo assente vale falso invece di generare un'eccezione — quindi un filtro che fa riferimento a sanctions.topScore su un evento non legato alle sanzioni semplicemente non corrisponde.

Quali campi sono valorizzati dipende dall'evento: un evento sanctions.* trasporta sanctions.*, un evento liveness.* trasporta liveness.*, e gli eventi di decisione trasportano overallScore / decision / status / applicant.*.

CampoTipoDisponibile su
overallScorenumeroeventi di decisione / stato
decisionstringaeventi di decisione / stato
statusstringaeventi di decisione / stato / ciclo di vita
applicant.countrystringaeventi di ingresso / decisione
applicant.typestringaeventi di ingresso / decisione
document.typestringadocument.*
document.sidestringadocument.*
liveness.isLivebooleanoliveness.*
liveness.confidencenumeroliveness.*
faceMatch.isMatchbooleanoface_match.*
faceMatch.confidencenumeroface_match.*
sanctions.isMatchbooleanosanctions.*
sanctions.topScorenumerosanctions.*
sanctions.matchCountnumerosanctions.*
pep.isMatchbooleanopep.match
pep.matchCountnumeropep.match
kyb.statusstringakyb.match_*
kyb.uboCountnumerokyb.ubo_completed
kyb.failedUboCountnumerokyb.ubo_completed
reviewerIdstringareviewer.decision

Esempi di predicati:

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

Nell'editor, prema Ctrl+Space in qualsiasi campo di espressione per il completamento automatico in base al contesto e un popover di aiuto con campi, operatori ed esempi pronti.


Tabella di confronto

TriggerCategoriaSoggettoLegato alla richiestaFrequenzaNote
form.submittedIngressoformsìper invioSolo moduli ospitati
request.createdIngressoapplicantsìper richiestaPrima di qualsiasi controllo
request.status_changedCiclo di vitaapplicantsìper cambio di statoGenerico; affianca eventi specifici
request.approvedCiclo di vitaapplicantsìper finalizzazione
request.rejectedCiclo di vitaapplicantsìper finalizzazione
request.escalatedCiclo di vitaapplicantsìper finalizzazione
request.resubmittedCiclo di vitaapplicantsìper reinvio
request.documents_readyCiclo di vitaapplicantsìper completamento della checklistTutti i documenti richiesti caricati
document.uploadedDocumentidocumentsìper documento
document.failedDocumentidocumentsìper documento non riuscito
check.completedSegnalichecksìper controllo (alta)Protetto; uno per controllo
liveness.passedSegnalichecksìper richiestaSolo esiti non ambigui
liveness.failedSegnalichecksìper richiestaSolo esiti non ambigui
face_match.passedSegnalichecksìper richiestaSolo esiti non ambigui
face_match.failedSegnalichecksìper richiestaSolo esiti non ambigui
sanctions.hitSegnalichecksìper richiestaCorrispondenza in lista
sanctions.clearedSegnalichecksìper richiesta
pep.matchSegnalichecksìper richiestaSolo voci PEP
kyb.match_foundSegnali—sìper richiesta aziendaleSolo BUSINESS
kyb.match_not_foundSegnali—sìper richiesta aziendaleSolo BUSINESS
kyb.ubo_completedSegnali—sìper richiesta aziendaleSolo BUSINESS; UBO risolti
reviewer.decisionRevisione—sìper decisione manualeFasi + azione di massa
reviewer.assignedRevisione—sìper riassegnazioneSolo riassegnazione esplicita
reviewer.comment_addedRevisione—sìper commentoCorpo non nel contesto
edd.triggeredRevisioneapplicantsìper avvio EDD
score.updatedRischioapplicantsìper (ri)calcoloSi attiva con la decisione
score.thresholdRischioapplicantsìper (ri)calcoloRistretto da scoreMin/scoreMax
scheduleTempo—nocronEsecuzione a livello di organizzazione
verification.expiredTempoapplicantsìper scadenzaDal cron di scadenza
monitoring.alertMonitoraggio—sìper avvisoScreening continuo
inbound.webhookEsterno—facoltativoper POSTPayload arbitrario
conference.completedEsterno—sìper conferenzaSessioni Live ID

Trigger previsti

Alcuni trigger richiesti di frequente non sono nel selettore, perché la piattaforma non ha ancora un segnale da cui emetterli — renderli selezionabili creerebbe trigger che non si attivano mai. Sono elencati qui con l'alternativa funzionante, così il divario è esplicito. (Tutto ciò che è nel selettore, al contrario, è davvero emesso — incluso score.threshold, il confronto del volto, PEP, UBO e gli eventi di revisione, che sono stati collegati in questa release.)

Trigger richiestoPerché non viene emessoCosa usare al suo posto
Frode rilevataNon esiste ancora un tipo di controllo autonomo di analisi forense/autenticità (la analisi forense è oggi un fattore di scoring del rischio, non un controllo discreto che emette un risultato).Ramificarsi su document.failed, oppure sugli eventi di decisione / score.threshold per il rischio risultante.
Dati del registro delle imprese aggiornatiLa KYB è una verifica una tantum; non esiste un job continuo di ri-recupero/monitoraggio del registro da cui emettere.Ripetere la verifica secondo una schedule e ramificarsi su kyb.match_* / kyb.ubo_completed.
Link di email / SMS cliccatoNon esiste un'infrastruttura di tracciamento di apertura email o clic sui link.Usare form.submitted / document.uploaded come segnali concreti che "il richiedente ha agito".

Queste sono vere voci di roadmap, non lacune della documentazione — l'enum dei trigger del motore è l'unica fonte di verità, e questa tabella riflette ciò che non contiene ancora.


Buone pratiche

  • Imposti sempre un soggetto + un ambito. Un trigger senza soggetto è a livello di organizzazione e si attiva per ogni oggetto corrispondente nell'organizzazione. Preferisca l'ambito specific o category; riservi all alle automazioni davvero a livello di organizzazione.
  • Ascolti gli eventi specifici, non quello generico, quando possibile. Usi liveness.failed / sanctions.hit invece di analizzare check.completed, e un unico evento di decisione invece di request.status_changed — a meno che non le serva specificamente uno stato senza un evento dedicato.
  • Non ascolti sia l'evento di stato specifico sia quello generico. request.approved e request.status_changed si attivano entrambi all'approvazione; collegare due workflow (o due trigger) allo stesso esito esegue due volte gli effetti collaterali.
  • Consideri il contesto come un'istantanea. Il contesto dell'evento viene catturato all'invio. Se il workflow attende (un nodo wait, un lungo ritardo), rilegga lo stato attuale all'interno di un'Azione invece di fidarsi di un contesto obsoleto per una decisione critica.
  • Mantenga snelli i workflow di check.completed. È il trigger a più alta frequenza; lo limiti a una categoria e filtri presto, così da non diramarsi su ogni controllo di ogni richiesta.
  • Attenzione agli ambiti orfani. Eliminare un modulo/richiedente referenziato da un ambito specific rende il trigger inattivo (non si attiva e mostra il badge "Richiede una nuova associazione"). Lo riassocii a un oggetto attivo per riattivarlo.
  • I workflow inoltrano, non decidono. Se l'esito di un trigger dovrebbe cambiare il verdetto, passi da escalate per la revisione umana — non esiste volutamente alcuna azione di approvazione/rifiuto.
  • Si aspetti un'esecuzione almeno una volta, in modo eventuale. L'invio è asincrono e fire-and-forget; renda idempotenti gli effetti collaterali delle Azioni dove conta (per esempio includendo un riferimento esterno, così un nuovo attivarsi non duplica i record a valle).

Domande frequenti

Un workflow può approvare o rifiutare una verifica? No. Il set di Azioni non ha approve/reject. Il controllo più forte sull'esito è escalate, che sposta la richiesta in revisione umana tramite la macchina a stati esistente, con blocco ottimistico e tracciata. Il verdetto appartiene alla politica decisionale e ai revisori umani.

Si sono attivati sia un evento di decisione specifico sia request.status_changed — è un bug? No. Il request.status_changed generico viene sempre emesso insieme all'evento specifico, così può reagire a stati che non hanno un evento dedicato. Ne ascolti uno, non entrambi.

Perché il mio workflow check.completed non è stato eseguito? La diramazione per controllo è protetta: invia solo quando un workflow attivo è in ascolto di check.completed. Verifichi che il workflow sia attivo e che ambito/filtro corrispondano al controllo. Tenga presente anche che i controlli in errore senza un dato booleano utilizzabile non emettono i segnali specifici liveness.* / sanctions.*.

Perché il mio trigger score.threshold non si attiva? Si attiva a ogni finalizzazione con punteggio, poi il dispatcher lo restringe in base ai limiti scoreMin / scoreMax del trigger. Se non viene mai eseguito, verifichi che il punteggio rientri davvero nei limiti, e che la richiesta abbia raggiunto un esito con punteggio (una richiesta annullata prima dello scoring non emette alcun evento di punteggio). Senza limiti impostati, si comporta come score.updated.

Qual è la differenza tra score.updated e score.threshold? Stesso punto di emissione e stesso contesto. score.updated si esegue sempre; score.threshold si esegue solo quando overallScore rientra nei limiti scoreMin / scoreMax del trigger — una comodità integrata, così non deve scrivere lei stessa il filtro su overallScore.

Come si attiva su un esito di confronto del volto o PEP? Usi gli eventi dedicati face_match.passed / face_match.failed e pep.match. Un riscontro PEP attiva anche sanctions.hit (lo screening PEP si basa sullo screening delle liste di controllo), quindi ascolti pep.match quando le interessa solo l'esposizione PEP.

Eliminare un modulo disattiva i suoi workflow? Un workflow il cui trigger è limitato per ambito specific solo a quel modulo diventa orfano: resta inattivo (non si attiva) e mostra il badge "Richiede una nuova associazione". Lo riassocii a un oggetto attivo per riattivarlo. Non ripiega mai silenziosamente sull'attivazione a livello di organizzazione.

L'invio è sincrono con la mia chiamata API? No. L'invio è fire-and-forget ed è eseguito fuori banda. L'azione di origine (caricamento, cambio di stato) va a buon fine indipendentemente dal successo dell'invio o dell'esecuzione del workflow.

Un workflow può attivarne un altro? Sì — l'azione trigger_workflow esegue direttamente un workflow salvato scelto, con una profondità massima di catena pari a 5 per evitare cicli.


Appendice

Glossario

  • Trigger — il singolo nodo di ingresso che dichiara evento, soggetto, ambito e filtro che avviano un workflow.
  • Evento — un WorkflowTriggerEvent che la piattaforma emette in un punto reale del ciclo di vita della verifica.
  • Soggetto — il tipo di oggetto a cui un trigger si lega: form, applicant, document oppure check.
  • Ambito — come viene ristretto il soggetto: specific (id), category oppure all.
  • Contesto — i fatti specifici dell'evento catturati all'invio e letti dai filtri e dai modelli delle Azioni.
  • Filtro — un predicato DSL facoltativo che restringe ulteriormente quali eventi corrispondono.
  • Trigger orfano — un trigger con ambito specific i cui oggetti sono stati eliminati; inattivo finché non viene riassociato.
  • Esecuzione — una KycWorkflowRun: un'esecuzione di un workflow, con un log in sola aggiunta.

Enum completo degli eventi

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 di vita di un invio

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

Esempio completo

Un'automazione di onboarding completa, collegata a un solo modulo 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

Questo non imposta mai il verdetto: l'unico passo che incide sull'esito è escalate, che instrada alla revisione umana. Tutto il resto è notifica, raccolta dati e reportistica attorno alla decisione di cui sono responsabili la politica e i revisori.