Eventi webhook
Riferimento completo di ogni tipo di evento webhook di Certivant — schema della busta, forme dei payload per evento, schema di idempotenza e regole di ordinamento.
Ogni payload webhook condivide la stessa busta esterna. Cambia solo il blocco data, per tipo di evento.
{
"event": "kyc.request.approved",
"requestId": "req_01J...",
"organizationId": "org_01H...",
"externalRef": "user-9281",
"subTenantName": "Acme Corp",
"subTenantExternalId": "cust_8842",
"status": "APPROVED",
"timestamp": "2026-05-27T10:04:21.000Z",
"data": { /* event-specific */ }
}
| Campo | Tipo | Note |
|---|---|---|
event | stringa | Il tipo di evento. Inizia sempre con kyc. |
requestId | stringa | L'ID della richiesta Certivant |
organizationId | stringa | L'organizzazione proprietaria della richiesta |
externalRef | stringa | null | La chiave esterna impostata alla creazione |
subTenantName | stringa | null | Modalità partner: il nome visualizzato del cliente finale impostato sulla richiesta, se presente |
subTenantExternalId | stringa | null | Modalità partner: l'ID del Suo cliente finale impostato sulla richiesta — instradi l'evento al cliente giusto senza una ricerca |
status | enum | Stato attuale della richiesta al momento dell'evento |
timestamp | stringa (ISO-8601) | Quando l'evento è stato accodato |
data | oggetto | null | Payload specifico dell'evento (vedi sotto) |
Catalogo degli eventi
kyc.webhook.test
Un controllo di connettività — inviato quando preme Test su un URL webhook (a livello di tenant in Impostazioni KYC → Sviluppatore, oppure per modulo nell'editor dei moduli). Non è un vero evento di verifica: non contiene un requestId e non modifica nulla dalla Sua parte. Il suo unico scopo è confermare che il Suo endpoint sia raggiungibile.
Include un flag esplicito di primo livello test: true, così il Suo gestore può chiudere subito e rispondere 2xx — senza cercare una richiesta, controllare lo status, né verificare la firma (non c'è nulla da proteggere, dato che un ping non modifica alcuno stato).
{
"event": "kyc.webhook.test",
"test": true,
"organizationId": "org_...",
"formId": "form_...", // present only for the per-form test
"formName": "KYC — Identity Verification",
"timestamp": "...",
"data": { "message": "This is a test webhook from your KYC platform." }
}
Gestore consigliato:
const payload = JSON.parse(rawBody);
// Reachability ping — ack immediately, no auth needed.
if (payload.test === true || payload.event === "kyc.webhook.test") {
return new Response(JSON.stringify({ ok: true }), { status: 200 });
}
// …verify the signature and process real events below…
Gli eventi di risultato reali (con uno status dell'enum del ciclo di vita) devono comunque essere verificati con la firma prima di agire — vedi Verifica della firma. Anche l'evento di prova è firmato (quando è impostato un segreto), con la stessa intestazione X-Certivant-Signature-V2 degli eventi reali e un timestamp in secondi Unix: quindi può verificarlo, ma non è obbligatorio.
Il pulsante Invia un evento di prova della dashboard può anche inviare un payload di esempio kyc.request.created, kyc.request.approved, kyc.request.rejected o kyc.request.escalated. Gli esempi hanno la forma reale della busta, contengono "test": true, usano un requestId fittizio (req_sample_…) e data.sample: true, e non vengono mai memorizzati come eventi reali. Non agisca su di essi nei percorsi di codice di produzione: si ramifichi prima su payload.test === true.
kyc.request.created
Si attiva subito dopo che POST /api/v1/kyc/requests restituisce 201. Utile se vuole confermare la ricezione senza attendere che il richiedente invii.
{
"event": "kyc.request.created",
"requestId": "req_...",
"organizationId": "org_...",
"externalRef": "user-9281",
"status": "PENDING_UPLOAD",
"timestamp": "...",
"data": null
}
kyc.request.processing
Si attiva nel momento in cui la richiesta passa in PROCESSING — cioè quando il richiedente (tramite una sessione ospitata o un invio di modulo) oppure Lei (tramite POST /requests/:id/submit) l'ha inviata. È il segnale di «inviata»: arriva prima di qualsiasi decisione, quindi la Sua app può mostrare l'utente in stato «in revisione» non appena ha finito di caricare.
{
"event": "kyc.request.processing",
"requestId": "req_...",
"externalRef": "user-9281",
"status": "PROCESSING",
"timestamp": "...",
"data": null
}
Agisca su di esso quando vuole mostrare l'avanzamento («La Sua verifica è in corso di revisione») tra l'invio e l'evento finale approved / rejected / escalated. Trasporta externalRef e i campi del sub-tenant, così può instradarlo all'utente giusto senza una ricerca.
kyc.document.uploaded
Si attiva ogni volta che un documento viene collegato a una richiesta. Utile se la Sua app mostra un indicatore in tempo reale dei «documenti ricevuti finora».
{
"event": "kyc.document.uploaded",
"requestId": "req_...",
"status": "PENDING_UPLOAD",
"data": {
"documentId": "doc_...",
"documentType": "GOVERNMENT_ID",
"side": "front",
"mimeType": "image/jpeg",
"fileSize": 412938
}
}
kyc.check.completed
Si attiva al termine di ciascun controllo di verifica. Una singola richiesta può produrne più di uno — uno per l'autenticità del documento, uno per il confronto del volto, uno per la vivacità, ecc.
{
"event": "kyc.check.completed",
"requestId": "req_...",
"status": "PROCESSING",
"data": {
"checkType": "face_match",
"result": "pass",
"score": 0.91,
"details": { "method": "deep_face_match" }
}
}
Possibili valori di checkType: document_authenticity, ocr, face_match, liveness, sanctions_screening, pep_screening, adverse_media, proof_of_address, phone_verification, email_verification, registry_lookup (KYB).
kyc.request.approved
Il caso principale — la richiesta è approvata. Si attiva sia quando il motore automatico la approva, sia quando un revisore umano approva manualmente una richiesta inoltrata (o precedentemente rifiutata automaticamente) dalla dashboard o dall'API /decision. Per le approvazioni manuali data.decision è "approve" e data.reason riporta la nota del revisore; i campi ricchi del motore (score, factors, …) sono presenti solo sulle decisioni automatiche.
{
"event": "kyc.request.approved",
"requestId": "req_...",
"status": "APPROVED",
"data": {
"score": 94,
"decision": "approve",
"factors": [
{ "name": "document_authenticity", "weight": 0.3, "score": 0.96 },
{ "name": "face_match", "weight": 0.3, "score": 0.91 },
{ "name": "liveness", "weight": 0.2, "score": 0.98 },
{ "name": "sanctions_screening", "weight": 0.2, "score": 1.0 }
],
"document": {
"type": "GOVERNMENT_ID",
"issuingCountry": "GB",
"expiryDate": "2031-04-12",
"isExpired": false
},
"extracted": {
"fullName": "Alex Singh",
"dateOfBirth": "1988-11-03",
"documentNumber": "GBR-1234567",
"address": "1 Example Street, London",
"nationality": "GB"
},
"liveness": { "isLive": true, "confidence": 0.98 },
"faceMatch": { "isMatch": true, "confidence": 0.91 },
"sanctions": { "isMatch": false, "topScore": 0, "listsChecked": ["OFAC", "UN", "EU", "UK_HMT"] }
}
}
Il blocco data.extracted è la Sua miniera d'oro: salvi quei valori nel record dell'utente, così da non chiedere di nuovo al richiedente gli stessi dati.
kyc.request.rejected
La richiesta è rifiutata — o il motore automatico l'ha rifiutata (guardi data.disqualifiers e data.factors per i motivi), oppure un revisore umano ha rifiutato manualmente una richiesta inoltrata (data.decision = "reject", data.reason = la nota del revisore).
{
"event": "kyc.request.rejected",
"requestId": "req_...",
"status": "REJECTED",
"data": {
"score": 18,
"decision": "reject",
"disqualifiers": ["document_expired", "liveness_failed"],
"reviewTriggers": [],
"reason": "Document expired (2024-01-01) and liveness confidence below threshold (0.22).",
"factors": [
{ "name": "document_authenticity", "weight": 0.3, "score": 0.0, "verdict": "fail" },
{ "name": "liveness", "weight": 0.2, "score": 0.22, "verdict": "fail" }
],
"document": {
"type": "GOVERNMENT_ID",
"expiryDate": "2024-01-01",
"isExpired": true
},
"liveness": { "isLive": false, "confidence": 0.22 }
}
}
Può reinviare la richiesta tramite POST /requests/:id/resubmit, se la Sua policy lo consente.
kyc.request.escalated
Il motore non era sicuro — punteggi al limite, corrispondenza parziale nelle sanzioni, documento di bassa qualità, oppure qualsiasi cosa che abbia superato la soglia di inoltro della Sua organizzazione. Una persona della Sua organizzazione deve esaminarla.
{
"event": "kyc.request.escalated",
"requestId": "req_...",
"status": "ESCALATED",
"data": {
"score": 62,
"decision": "escalate",
"reviewTriggers": ["sanctions_partial_match", "low_face_match_confidence"],
"factors": [...],
"sanctions": {
"isMatch": true,
"topScore": 0.72,
"listsChecked": ["OFAC", "UN", "EU", "UK_HMT"]
},
"faceMatch": { "isMatch": true, "confidence": 0.68 }
}
}
Un revisore apre la richiesta nella dashboard e decide. Quando lo fa, riceve un secondo webhook — kyc.request.approved oppure kyc.request.rejected — con la decisione umana annotata nel registro di audit.
kyc.request.cancelled
Si attiva quando una richiesta viene annullata esplicitamente (attualmente solo dalla dashboard — nessun endpoint API).
{
"event": "kyc.request.cancelled",
"requestId": "req_...",
"status": "CANCELLED",
"data": { "reason": "applicant_withdrew", "cancelledBy": "user_..." }
}
Eventi di verifica dal vivo — conference.*
Gli eventi della verifica dell'identità dal vivo Live ID (sessione video in diretta condotta da un operatore) usano un proprio corpo con la forma della conferenza — conferenceId, kycRequestId, applicantId, organizationId, il Suo externalRef, lo status della conferenza e i timestamp — invece della busta kyc.request.* descritta sopra. Si attivano per le conferenze pianificate sia dalla dashboard sia dall'API.
| Evento | Si attiva quando |
|---|---|
conference.scheduled | Viene creata una verifica dal vivo. |
conference.rescheduled | Viene spostata a un nuovo orario (previousScheduledFor). |
conference.cancelled | Viene annullata — tramite API, oppure perché il richiedente ha rifiutato il consenso (cancellation). |
conference.started | Il conduttore avvia la sessione (una volta per avvio). |
conference.completed | Il conduttore registra la decisione (decision.decision, decision.outcome, note, indicatore di registrazione, certificato). |
{
"event": "conference.completed",
"conferenceId": "cmf...",
"kycRequestId": "req_...",
"applicantId": "cma...",
"organizationId": "org_...",
"externalRef": "order-10042",
"status": "completed",
"decision": { "decision": "verified", "outcome": "approved", "requestStatus": "APPROVED", "notes": "..." },
"timestamp": "..."
}
Payload completo e API di pianificazione: Verifica dal vivo.
Idempotenza
Certivant può consegnare lo stesso evento più di una volta (interruzioni di rete, nuovi tentativi — vedi Tentativi e consegna). Renda idempotente il Suo gestore.
L'intestazione X-Certivant-Event-Id (inviata anche come X-KYC-Event-Id) è univoca per evento. Schema comune:
async function handleWebhook(rawBody: string, headers: Headers) {
const eventId = headers.get("X-KYC-Event-Id")!;
// Skip if already processed
const seen = await db.webhookEvent.findUnique({ where: { id: eventId } });
if (seen) return;
const payload = JSON.parse(rawBody);
// Do the work + record the event in a single transaction
await db.$transaction([
db.webhookEvent.create({ data: { id: eventId, eventType: payload.event } }),
...yourBusinessLogic(payload),
]);
}
X-KYC-Event-Id è stabile tra i nuovi tentativi: lo stesso evento logico mantiene lo stesso ID indipendentemente da quanti tentativi di consegna avvengono.
Ordinamento
Gli eventi per richieste diverse possono arrivare in qualsiasi ordine. Gli eventi per la stessa richiesta vengono accodati nell'ordine in cui sono stati generati, ma i nuovi tentativi possono intercalarsi — per esempio, un evento processing bloccato potrebbe arrivare dopo un evento approved della stessa richiesta.
Schema difensivo: usi sempre il campo status come fonte di verità, e ignori gli eventi il cui status è «più vecchio» di quanto già registrato. Il ciclo di vita procede solo in avanti (PENDING_UPLOAD → PROCESSING → APPROVED/REJECTED/ESCALATED), quindi se il Suo database dice che la richiesta è APPROVED e arriva un evento kyc.request.processing in ritardo, lo scarti.
Test dei gestori di eventi in locale
Due pulsanti della dashboard aiutano:
- Test (accanto a qualsiasi URL webhook, a livello di tenant o per modulo) invia un ping di connettività
kyc.webhook.test(test: true). Lo usi per confermare che il Suo endpoint sia raggiungibile e restituisca2xx— non richiede dati di richiesta né un segreto configurato. - Invia evento di prova invia un payload completo
kyc.request.approvedcon dati di esempio, così può verificare il Suo vero percorso di gestione dell'esito.
Per testare altri tipi di evento, scriva un piccolo script che costruisca la stessa busta e la invii con POST al Suo URL di sviluppo tramite un tunnel:
# tunnel localhost:3000 to a public URL
ngrok http 3000
# Then in the dashboard webhook settings, paste the ngrok URL,
# trigger real test requests via the API, and watch your logs.
Verifica della firma
Verifichi l'intestazione X-Certivant-Signature-V2 (HMAC-SHA256 su timestamp.body, tolleranza di 300 s) per fidarsi dei payload dei webhook. Include esempi in Node, Python, Go, Ruby e PHP.
Tentativi e consegna
Come Certivant riprova le consegne dei webhook non riuscite — programma esatto del backoff, regola del timeout di 10 secondi, ripristino degli eventi esauriti e uno schema di gestore rapido per restare entro il budget.