Vai al contenuto principale
Le SDK di Didit possono inviare transazioni monitorate direttamente dal dispositivo dell’utente finale, inclusi i trasferimenti Travel Rule e lo screening delle criptovalute. Il tuo backend genera un token con ambito limitato e di breve durata; il resto lo gestisce l’SDK. L’SDK allega automaticamente le informazioni sul dispositivo e, quando un trasferimento Travel Rule richiede la prova del controllo del wallet, avvia subito il flusso corrispondente. Una sessione di verifica richiesta non viene mai avviata automaticamente: viene restituita nella risposta, così la tua app può avviarla con la tua integrazione di verifica Didit; vedi Azioni richieste all’utente più sotto.

Panoramica

L’invio lato client si basa su tre garanzie:
  • Associazione del soggetto a prova di spoofing. Il token è associato a un solo vendor_data (il tuo ID utente) al momento della generazione. L’identità del soggetto di ogni transazione inviata con il token viene applicata lato server a partire da questa associazione, quindi un client manomesso non può inviare per conto di un altro utente, e il payload non deve contenere credenziali.
  • Informazioni automatiche sul dispositivo. L’SDK raccoglie un’impronta del dispositivo e la invia insieme alla richiesta, mentre Didit ricava lato server l’indirizzo IP e lo user agent. Il contesto del dispositivo così ottenuto viene associato al soggetto della transazione senza librerie aggiuntive, senza un token di dispositivo separato e senza alcuna configurazione da parte tua. Alimenta le regole basate sul dispositivo e la correlazione per dispositivo tra sessioni e transazioni.
  • Prova del possesso del wallet avviata automaticamente. Quando la transazione creata richiede che l’utente finale dimostri il controllo di un wallet, la risposta contiene un blocco action_required di tipo wallet_ownership. L’SDK apre subito questo flusso e poi aggiorna la transazione al termine, senza che tu debba costruire cicli di polling o invii di link. Un’azione verification_session richiesta è diversa: non viene mai avviata automaticamente e viene restituita nello stesso blocco, così la tua app può avviarla con la propria integrazione di verifica Didit.
POST /v3/transactions/ lato server resta la scelta giusta quando non è presente alcun utente finale: trasferimenti di back office, importazioni in batch o eventi segnalati dai tuoi sistemi. Entrambi i percorsi creano gli stessi record di transazione, eseguono le stesse regole e inviano gli stessi webhook.

Flusso

1

Genera un token per la transazione

Il tuo backend chiama POST /v3/transactions/sdk-token/ con la tua chiave API e il vendor_data dell’utente, e consegna l’sdk_token restituito alla tua app.
2

Invia dal dispositivo

L’app chiama submitTransaction sull’SDK, che invia il payload a POST /v1/transactions/ con l’header X-Transaction-Token e l’arricchimento automatico del dispositivo.
3

Gestisci eventuali azioni richieste

Se action_required.type è wallet_ownership, l’SDK avvia automaticamente il widget e poi aggiorna la transazione tramite GET /v1/transactions/{transaction_id}/. Se è verification_session, l’SDK non avvia nulla: leggi action_required e avvia il tuo flusso di verifica con il suo session_token (o url sul web).

Generazione di un token per la transazione

Genera i token dal tuo backend con POST /v3/transactions/sdk-token/, con la stessa autenticazione tramite chiave API degli altri endpoint /v3/ delle transazioni.
Sono accettati anche gli alias in camelCase (vendorData, ttlSeconds, maxUses), come nel resto dell’API /v3/.
Non inserire mai la tua chiave API in un’app. La chiave API resta sul tuo backend e genera i token; solo il breve sdk_token arriva al dispositivo. Genera un nuovo token per ogni sessione utente invece di riutilizzare token a lunga durata, e mantieni max_uses basso quando il numero di invii previsti è noto.

Invio dal dispositivo

Gli SDK inviano una richiesta a POST /v1/transactions/ con il token nell’intestazione X-Transaction-Token. Il payload trasmesso usa il formato camelCase. I metodi submitTransaction lo costruiscono per te, quindi lo gestisci direttamente solo quando chiami l’endpoint dal tuo client HTTP:
Ogni campo di primo livello corrisponde al suo equivalente in POST /v3/transactions/: La risposta contiene lo stesso dettaglio della transazione in formato snake_case degli endpoint /v3/, più il blocco action_required:

Dichiarare un wallet in autocustodia

L’esempio sopra dichiara la destinazione come wallet in autocustodia dell’utente: il paymentMethod.type della controparte è unhosted_wallet. Questa dichiarazione modifica il funzionamento dello scambio Travel Rule:
  • Didit salta completamente l’instradamento verso la VASP controparte, perché dietro un wallet in autocustodia non c’è nessuna VASP, e crea il trasferimento direttamente in UNCONFIRMED_OWNERSHIP.
  • Quando auto_wallet_verification è attivo nelle tue impostazioni Travel Rule (il valore predefinito), una sessione del widget di verifica della proprietà del wallet viene generata automaticamente e restituita in action_required, esattamente come nella risposta sopra.
  • L’SDK avvia automaticamente il widget. Una volta che l’utente dimostra di controllare il wallet, viene eseguito il confronto del nome e il trasferimento passa a COMPLETED, oppure a COUNTERPARTY_MISMATCHED_DATA quando il nome non corrisponde al titolare della voce nella rubrica degli indirizzi del wallet.
Senza la dichiarazione, Didit prova invece i canali in ordine e ricorre al flusso di verifica della proprietà solo se non trova alcuna VASP controparte. Dichiara il wallet ogni volta che il tuo prodotto sa già che la destinazione del prelievo è il wallet dell’utente: così si evita il tentativo di routing fallito e l’utente accede subito al flusso di verifica.

Vincoli sul soggetto

Tre elementi sono sempre decisi dal token, mai dal payload:
  • L’applicazione in cui viene creata la transazione.
  • L’identità del soggetto: subject.externalUserId nel payload viene ignorato e sostituito con vendor_data del token.
  • Il contesto del dispositivo del soggetto (vedi sotto). In caso di conflitto, i campi derivati dal server prevalgono su qualsiasi valore fornito in subject.device.
Una risposta 401 da questo endpoint indica che il token è stato rifiutato: mancante, sconosciuto, revocato, oltre il suo max_uses o scaduto. Le risposte per token scaduto contengono sempre la parola expired nel dettaglio dell’errore, ed è così che gli SDK distinguono gli errori expired_token da quelli invalid_token.

Lettura della transazione

GET /v1/transactions/{transaction_id}/ restituisce lo stesso dettaglio con la stessa autenticazione tramite X-Transaction-Token. Gli SDK lo chiamano automaticamente al termine di un flusso d’azione avviato o alla chiusura della sua finestra, così lo stato finale non dipende mai da un singolo evento o da una singola notifica. Puoi chiamarlo tu stesso per ricontrollare status e action_required in qualsiasi momento finché il token è valido. Dal tuo backend, GET /v3/transactions/{transaction_id}/ restituisce lo stesso record.

Informazioni automatiche sul dispositivo

Ogni invio include informazioni sul dispositivo senza alcun lavoro di integrazione da parte tua:
  • L’SDK raccoglie un fingerprint del dispositivo che rispetta la privacy (lo schema didit-fp-v2: segnali di piattaforma e del navigatore, un ID dispositivo persistente, un punteggio bot e un hash composito) e lo invia con la richiesta.
  • Didit ricava lato server l’indirizzo IP, lo user agent e l’accept-language dalla connessione stessa, quindi non possono essere falsificati dal payload.
Il risultato unito viene normalizzato nel contesto del dispositivo del partecipante che è il soggetto e inserito con upsert nel grafo dei dispositivi, lo stesso alimentato dalle sessioni KYC. In questo modo le transazioni possono essere correlate per dispositivo in tutta la tua base utenti (“correlate per dispositivo”), i segnali di dispositivo e di rete alimentano le regole e l’arricchimento IP, e compaiono nel dettaglio della transazione nella Console. Le transazioni /v3/ inviate dal server possono continuare a inviare oggetti device_context espliciti per ogni partecipante. Con il percorso SDK ottieni gratuitamente una versione più completa degli stessi dati.

Azioni richieste all’utente

Alcune transazioni richiedono che l’utente finale faccia qualcosa prima di poter essere completate: una regola può richiedere una sessione di verifica, oppure un trasferimento Travel Rule può richiedere la prova che il wallet di destinazione appartenga effettivamente all’utente. Entrambi i casi compaiono nello stesso punto, nel blocco action_required della risposta di invio (e di ogni successiva lettura della transazione):
Se entrambe le azioni potrebbero applicarsi alla stessa transazione, wallet_ownership ha la precedenza, perché la prova di proprietà mancante blocca lo scambio Travel Rule stesso. action_required diventa null una volta risolta l’azione in sospeso. Il campo remediation esistente viene mantenuto per compatibilità con le versioni precedenti; action_required è il blocco canonico.

Contratto di avvio automatico

autoLaunchAction (predefinito true) avvia automaticamente solo l’azione wallet_ownership:
  • Web - aperta nell’overlay modale proprio dell’SDK, con il completamento rilevato tramite messaggistica con validazione dell’origine e un polling limitato della transazione (mai una dipendenza da un singolo evento).
  • iOS / Android (e i wrapper React Native e Flutter, che delegano agli SDK nativi) - presentata come l’esperienza nativa di proprietà del wallet dell’SDK (SwiftUI / Jetpack Compose) quando useNativeWalletOwnership è true (il valore predefinito), con ripiego su SFSafariViewController / una Chrome Custom Tab e, in ultima istanza, sul browser predefinito. Dopo la chiusura del flusso o il ritorno dell’app in primo piano, l’SDK rilegge automaticamente la transazione.
Un’azione verification_session non viene mai avviata automaticamente: torna sempre in action_required affinché la tua app la avvii con la propria integrazione di verifica Didit.
Il widget di verifica della proprietà del wallet non deve mai essere mostrato all’interno di una webview mobile incorporata: i deep link dei wallet non possono uscirne e il flusso di prova si blocca. Gli SDK usano già la superficie browser corretta quando avviano automaticamente l’azione; mantieni questa regola se gestisci tu l’url. Per i dettagli, consulta la guida al widget di verifica della proprietà del wallet.
Al termine o alla chiusura del flusso di proprietà del wallet avviato automaticamente dall’SDK, l’SDK rilegge la transazione e passa il risultato aggiornato alla tua callback onActionCompleted. Un’azione verification_session non fa parte di questo ciclo, perché viene avviata dalla tua app: vedi la sezione successiva.

Avvio di una sessione di verifica

Quando action_required.type è verification_session, leggi il blocco da result.actionRequired e passalo alla tua integrazione di verifica Didit esistente:
Quando il flusso di verifica termina, chiama GET /v1/transactions/{transaction_id}/ (oppure attendi il webhook di transazione) per recuperare lo stato aggiornato della transazione.

Esempio: verifica biometrica rafforzata per un prelievo di alto valore

Questo è il flusso nella sua forma migliore: sottoponi le transazioni rischiose (importi elevati, nuovi beneficiari, nuovi dispositivi) a un nuovo controllo biometrico della stessa persona verificata in fase di onboarding. Una presa di controllo dell’account si presenta esattamente così: password corretta, codice 2FA corretto (ottenuto con phishing o SIM swap), un prelievo verso un wallet mai visto prima. A quel punto, ogni difesa basata sulle credenziali è già fallita. Il volto, invece, no: l’attaccante non può superare un selfie sottoposto al rilevamento della vitalità che deve corrispondere al volto acquisito durante il KYC di onboarding del legittimo proprietario.
1

Crea la regola una volta, nella console

Transactions → Rules → Create Rule: condizione amount gte 5000, azione Change status → AWAITING_USER, e seleziona il tuo workflow Autenticazione biometrica sull’azione. Aggiungi le altre condizioni di rischio che preferisci - nuova controparte, nuova impronta del dispositivo, cambio del paese dell’IP - come condizioni aggiuntive o come regole aggiuntive.
2

Invia il prelievo come di consueto

Il codice di invio non cambia. Quando la regola si attiva, la risposta arriva con status: "AWAITING_USER" e un blocco action_required di tipo verification_session:
3

Avvia il nuovo controllo

Passa il session_token (o l’url) alla tua integrazione di verifica Didit esistente, esattamente come in Avvio di una sessione di verifica. Poiché la sessione viene creata per lo stesso vendor_data del soggetto della transazione, Didit possiede già il volto di riferimento verificato dell’utente acquisito in fase di onboarding: il selfie con rilevamento della vitalità approvato, la foto del chip del passaporto o il ritratto del documento. L’utente scatta un unico selfie dal vivo; Didit esegue il controllo di vitalità e un confronto facciale 1:1 con quel riferimento memorizzato. Nessuna nuova scansione del documento, nessun nuovo onboarding: un’esperienza di pochi secondi, solo sulle transazioni che lo richiedono.
4

Concludi in base all'esito

Al termine della sessione, la transazione viene rivalutata automaticamente: APPROVED se il nuovo controllo è superato, DECLINED se non lo è, con un webhook transaction.status.updated in entrambi i casi (reason: remediation_completed nella cronologia della transazione). La tua app riceve il risultato tramite callback in stile onActionCompleted, tramite GET /v1/transactions/{transaction_id}/ o tramite il webhook.
Questo funziona perché l’onboarding KYC, il ricontrollo biometrico e la decisione sulla transazione girano su un’unica piattaforma: il volto acquisito in fase di onboarding è il riferimento per lo step-up, il grafo dei dispositivi collega la sessione alla transazione e il motore delle regole tiene insieme tutto. Nessuna integrazione tra fornitori diversi, nessuna esportazione delle immagini del volto, nessun contratto separato per la biometria. Il ricontrollo viene fatturato come funzionalità standard di Autenticazione biometrica ($0.10) solo quando viene effettivamente eseguito.

SDK Web

submitTransaction è disponibile oggi sul singleton dell’SDK Web:
Il risultato è { transactionId, status, travelRuleStatus?, actionRequired? }. Nel payload a livello di SDK scrivi category, details e customProperties; l’SDK li mappa automaticamente sugli alias del wire (type, info, props). Gli errori sono tipizzati tramite DiditTransactionError:

SDK nativi

L’API nativa submitTransaction è disponibile con gli SDK iOS e Android 4.1.0 (e con le release React Native e Flutter che la fissano a questa versione). A differenza dell’SDK Web, i payload nativi usano direttamente i nomi dei campi del wire: type, info e props.
Ogni SDK espone anche getTransaction(transactionToken, transactionId) per rileggere la transazione con lo stesso token: gli SDK lo chiamano per te dopo un flusso d’azione avviato, e tu puoi chiamarlo autonomamente in qualsiasi momento. Su mobile il trasporto dell’impronta del dispositivo è già applicato a tutte le chiamate di rete dell’SDK, quindi le richieste di invio delle transazioni includono automaticamente la stessa intelligence sul dispositivo.

Senza gli SDK

Tutto ciò che fanno gli SDK avviene tramite HTTPS standard: qui non serve nessuno di essi. Due forme di integrazione equivalenti: Solo lato server (senza alcun token). Invia la transazione con POST /v3/transactions/ dal tuo backend, usando la tua chiave API. La risposta (e ogni successiva GET /v3/transactions/{transaction_id}/) contiene lo stesso blocco action_required. Puoi anche passare un contesto device esplicito per ogni partecipante, dato che non c’è un SDK che lo raccolga per te. Lato client con il tuo stack HTTP. Genera un token con POST /v3/transactions/sdk-token/, poi chiama POST /v1/transactions/ e GET /v1/transactions/{transaction_id}/ dal dispositivo con l’header X-Transaction-Token: si applicano la stessa associazione al soggetto e la stessa derivazione lato server di IP e user agent. Poi gestisci tu action_required: Per rilevare il completamento senza alcun callback lato client:
  • Webhook (consigliato). transaction.status.updated viene inviato quando l’azione in sospeso porta a conclusione la transazione, e travel_rule.status.updated viene inviato a ogni transizione dello scambio: entrambi contengono informazioni sufficienti per aggiornare i tuoi registri senza polling.
  • Polling. Rileggi la transazione (/v3/ dal tuo backend, oppure /v1/ con il token finché è valido): action_required torna a null una volta risolta, e status / travel_rule.status riportano l’esito.
In questo design non esistono dipendenze monouso: il widget, la sessione di verifica e il tuo backend convergono tutti sullo stesso stato persistente della transazione, quindi, anche in caso di scheda chiusa o webhook non ricevuto, è sempre possibile recuperare lo stato rileggendo la transazione.

Webhook

Il tuo backend non ha bisogno che l’app gli riporti l’esito. Quando una transazione inviata richiede un’azione dell’utente, il webhook della transazione (transaction.created o transaction.status.updated) include lo stesso blocco action_required, così i tuoi sistemi conoscono l’azione in sospeso anche se l’app viene chiusa prima del termine del flusso. Un’azione verification_session arriva con lo stato AWAITING_USER, mentre un’azione wallet_ownership non cambia da sola lo stato della transazione: leggi quindi action_required invece dello stato. Le transizioni Travel Rule emettono inoltre travel_rule.status.updated.

Prossimi passi

Invio delle transazioni

L’API Create Transaction lato server e il riferimento completo del payload.

Travel Rule

Lo scambio gestito tra ordinante e beneficiario, le impostazioni e il ciclo di vita.

Widget per la verifica della proprietà del wallet

Il flusso ospitato per dimostrare il controllo alla base delle azioni wallet_ownership.

Webhook

transaction.created, transaction.status.updated e la verifica della firma.

Regole

Azioni delle regole, incluse quelle che creano sessioni di verifica.

Stati

Che cosa significa AWAITING_USER nel ciclo di vita della transazione.
Ultima modifica il 7 novembre 2026