> ## Documentation Index
> Fetch the complete documentation index at: https://docs.didit.me/llms.txt
> Use this file to discover all available pages before exploring further.

# Invio delle transazioni tramite SDK - KYT lato client

> Con Didit, invia transazioni monitorate dalla tua app con token con ambito, analisi del dispositivo automatica e verifica del wallet.

Le SDK di Didit possono inviare [transazioni monitorate](https://docs.didit.me/it/transaction-monitoring/transactions.md) direttamente dal dispositivo dell'utente finale, inclusi i trasferimenti [Travel Rule](https://docs.didit.me/it/transaction-monitoring/travel-rule.md) 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](#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](https://docs.didit.me/it/transaction-monitoring/rules.md) 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.

> [!NOTE]
>
> [`POST /v3/transactions/`](https://docs.didit.me/it/transaction-monitoring/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](https://docs.didit.me/it/transaction-monitoring/webhooks.md).

## 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.

```bash theme={null}
curl -X POST https://verification.didit.me/v3/transactions/sdk-token/ \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "vendor_data": "user-042",
    "ttl_seconds": 900,
    "max_uses": 3
  }'
```

```json theme={null}
{
  "sdk_token": "q7Zt...urlsafe",
  "expires_at": "2026-07-07T12:15:00Z"
}
```

| Campo | Tipo | Descrizione |
| - | - | - |
| `vendor_data` | string | **Obbligatorio.** Il tuo identificativo interno per l'utente finale a cui è associato questo token. Per ogni transazione inviata con il token, l'identità del soggetto viene applicata lato server a partire da questo valore. |
| `ttl_seconds` | integer | Durata del token in secondi. Il valore predefinito è `900` (15 minuti); il massimo è `86400` (24 ore). |
| `max_uses` | integer | Facoltativo. Numero massimo di invii riusciti consentiti con questo token. Ometti il campo per utilizzi illimitati entro il TTL. |

Sono accettati anche gli alias in camelCase (`vendorData`, `ttlSeconds`, `maxUses`), come nel resto dell'API `/v3/`.

> [!WARNING]
>
> **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:

```bash theme={null}
curl -X POST https://verification.didit.me/v1/transactions/ \
  -H "X-Transaction-Token: SDK_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "txnId": "wd-2026-07-07-0042",
    "type": "travelRule",
    "info": {
      "direction": "out",
      "amount": "0.25",
      "currency": "ETH",
      "currencyType": "crypto",
      "cryptoParams": { "crypto_chain": "ETH" }
    },
    "subject": { "fullName": "Jane Doe" },
    "counterparty": {
      "fullName": "Ana Diaz",
      "paymentMethod": { "type": "unhosted_wallet", "accountId": "0xBeneficiaryWallet01" }
    },
    "travelRule": {
      "status": "AWAITING_COUNTERPARTY",
      "beneficiaryData": { "wallet_address": "0xBeneficiaryWallet01", "name": "Ana Diaz" }
    },
    "includeCryptoScreening": true
  }'
```

Ogni campo di primo livello corrisponde al suo equivalente in [`POST /v3/transactions/`](https://docs.didit.me/it/transaction-monitoring/transactions):

| Campo trasmesso | Equivalente `/v3/` | Note |
| - | - | - |
| `txnId` | `transaction_id` | **Obbligatorio.** Il tuo identificatore univoco della transazione. |
| `txnDate` | `transaction_at` | Timestamp ISO-8601; per impostazione predefinita è l'ora corrente. |
| `zoneId` | `time_zone` | Identificatore di fuso orario IANA, ad esempio `Europe/Madrid`. |
| `type` | `transaction_category` | **Obbligatorio.** `finance`, `kyc`, `travelRule`, `userPlatformEvent`, `gamblingBet` e le altre categorie. |
| `info` | `transaction_details` | **Obbligatorio.** `direction`, `amount`, `currency` (obbligatorio), più `currencyType`, `amountInDefaultCurrency`, `defaultCurrencyCode`, `paymentDetails`, `paymentTxnId`, `type` (tipo di azione), `cryptoParams`. |
| `subject`, `counterparty` | `subject`, `counterparty` | Partecipanti: `type` (tipo di entità), `externalUserId`, `fullName`, `firstName`, `lastName`, `dob`, `address`, `institutionInfo`, `device`, `paymentMethod` (`type`, `accountId`, `issuingCountry`). Un `paymentMethod.type` della controparte pari a `unhosted_wallet` dichiara la destinazione come wallet in autocustodia - vedi [sotto](#dichiarare-un-wallet-in-autocustodia). |
| `props` | `custom_properties` | Coppie chiave-valore personalizzate, utilizzabili nelle regole come `custom_values.<key>`. |
| `travelRule` | `travel_rule_details` | `status`, `protocol`, `required`, `obligationsCount`, `originatorData`, `beneficiaryData`, `metadata`; `status` è obbligatorio ogni volta che invii questo blocco, altrimenti la richiesta fallisce con `400`. Quando Travel Rule è abilitato, Didit ignora qualsiasi `status` che invii e gestisce lo scambio autonomamente. |
| `includeCryptoScreening` | `include_crypto_screening` | Sovrascrittura dello screening crypto per singola transazione. |

La risposta contiene lo stesso dettaglio della transazione in formato snake\_case degli endpoint `/v3/`, più il blocco `action_required`:

```json theme={null}
{
  "uuid": "22222222-3333-4444-5555-666666666666",
  "txn_id": "wd-2026-07-07-0042",
  "transaction_type": "travelRule",
  "direction": "OUTBOUND",
  "status": "APPROVED",
  "amount": "0.25",
  "currency": "ETH",
  "travel_rule": {
    "uuid": "33333333-4444-5555-6666-777777777777",
    "status": "UNCONFIRMED_OWNERSHIP",
    "required": true
  },
  "action_required": {
    "type": "wallet_ownership",
    "url": "https://verify.didit.me/wallet-ownership/3s9x...urlsafe",
    "widget_session_id": "77777777-8888-9999-aaaa-bbbbbbbbbbbb",
    "expires_at": "2026-07-07T13:00:00Z"
  }
}
```

### 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](https://docs.didit.me/it/transaction-monitoring/travel-rule.md#wallet-in-autocustodia-dichiarati):

* 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](https://docs.didit.me/it/transaction-monitoring/travel-rule.md#configurazione) (il valore predefinito), una sessione del [widget di verifica della proprietà del wallet](https://docs.didit.me/it/transaction-monitoring/wallet-ownership-widget.md) 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](https://docs.didit.me/it/transaction-monitoring/travel-rule.md#overview) 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}/`](https://docs.didit.me/it/transaction-monitoring/transactions) 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](https://docs.didit.me/it/transaction-monitoring/rules.md) e l'[arricchimento IP](https://docs.didit.me/it/transaction-monitoring/ip-enrichment.md), e compaiono nel dettaglio della transazione nella [Console](https://docs.didit.me/it/transaction-monitoring/console.md).

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):

```json theme={null}
"action_required": {
  "type": "verification_session",
  "url": "https://verify.didit.me/session/...",
  "session_id": "11111111-2222-3333-4444-555555555555",
  "session_token": "SESSION_TOKEN",
  "status": "Not Started"
}
```

```json theme={null}
"action_required": {
  "type": "wallet_ownership",
  "url": "https://verify.didit.me/wallet-ownership/3s9x...urlsafe",
  "widget_session_id": "77777777-8888-9999-aaaa-bbbbbbbbbbbb",
  "expires_at": "2026-07-07T13:00:00Z"
}
```

| Tipo | Quando appare | Cosa fa l'utente |
| - | - | - |
| `verification_session` | Un'[azione della regola](https://docs.didit.me/it/transaction-monitoring/rules.md) ha richiesto una verifica aggiuntiva dell'utente e la transazione è passata a `AWAITING_USER`. | Completa una sessione di verifica ospitata (ad esempio un nuovo controllo biometrico), avviata dalla tua app: vedi [Avvio di una sessione di verifica](#avvio-di-una-sessione-di-verifica) più sotto. |
| `wallet_ownership` | Un trasferimento Travel Rule richiede la prova da parte dell'utente del controllo del wallet: la destinazione è stata [dichiarata come wallet in autocustodia](#dichiarare-un-wallet-in-autocustodia) (`UNCONFIRMED_OWNERSHIP`), oppure un trasferimento in uscita non ha trovato un VASP controparte, e `auto_wallet_verification` è abilitato nelle tue [impostazioni Travel Rule](https://docs.didit.me/it/transaction-monitoring/travel-rule.md#configurazione). Didit genera automaticamente una sessione del [widget di verifica della proprietà del wallet](https://docs.didit.me/it/transaction-monitoring/wallet-ownership-widget.md) collegata alla transazione. | Dimostra il controllo del wallet tramite firma di un messaggio (su una blockchain supportata), acquisizione dello schermo o autodichiarazione, secondo quanto consentono le tue impostazioni Travel Rule, ma mai tramite Satoshi test su una sessione generata automaticamente: l'SDK avvia questa azione automaticamente per te. |

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.

> [!WARNING]
>
> 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](https://docs.didit.me/it/transaction-monitoring/wallet-ownership-widget.md#apri-l’url-restituito).

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:

```typescript Web theme={null}
if (result.actionRequired?.type === "verification_session") {
  DiditSdk.shared.startVerification({ url: result.actionRequired.url });
}
```

```swift iOS theme={null}
if actionRequired.type == "verification_session" {
    try await DiditSdk.shared.startVerification(token: actionRequired.sessionToken)
}
```

```kotlin Android theme={null}
if (actionRequired.type == "verification_session") {
    DiditSdk.startVerification(token = actionRequired.sessionToken) { result ->
        // handle the verification result
    }
}
```

```typescript React Native theme={null}
if (actionRequired.type === "verification_session") {
  await startVerification(actionRequired.sessionToken);
}
```

```dart Flutter theme={null}
if (actionRequired.type == 'verification_session') {
  await DiditSdk.startVerification(actionRequired.sessionToken);
}
```

Quando il flusso di verifica termina, chiama `GET /v1/transactions/{transaction_id}/` (oppure attendi il [webhook di transazione](#webhook)) 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`:

```json theme={null}
{
  "status": "AWAITING_USER",
  "action_required": {
    "type": "verification_session",
    "url": "https://verify.didit.me/session/...",
    "session_id": "11111111-2222-3333-4444-555555555555",
    "session_token": "SESSION_TOKEN",
    "status": "Not Started"
  }
}
```

**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](#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](https://docs.didit.me/it/getting-started/pricing.md)) solo quando viene effettivamente eseguito.

## SDK Web

`submitTransaction` è disponibile oggi sul singleton dell'SDK Web:

```typescript theme={null}
import { DiditSdk } from "@didit-protocol/sdk-web";

const result = await DiditSdk.shared.submitTransaction({
  transactionToken: sdkToken, // minted by your backend via POST /v3/transactions/sdk-token/
  transaction: {
    txnId: "wd-2026-07-07-0042",
    txnDate: new Date().toISOString(),
    zoneId: "Europe/Madrid",
    category: "travelRule",
    details: {
      direction: "out",
      amount: "0.25",
      currency: "ETH",
      currencyType: "crypto",
      paymentDetails: "ETH withdrawal to self-hosted wallet",
      cryptoParams: { crypto_chain: "ETH" }
    },
    subject: {
      type: "individual",
      fullName: "Jane Doe"
      // no externalUserId needed: the subject identity comes from the token
    },
    counterparty: {
      type: "individual",
      fullName: "Ana Diaz",
      paymentMethod: {
        type: "unhosted_wallet",
        accountId: "0xBeneficiaryWallet01"
      }
    },
    travelRule: {
      status: "AWAITING_COUNTERPARTY", // ignored when Travel Rule is enabled: Didit drives the exchange
      beneficiaryData: {
        wallet_address: "0xBeneficiaryWallet01",
        name: "Ana Diaz"
      }
    },
    includeCryptoScreening: true
  },
  autoLaunchAction: true, // default: auto-launches wallet_ownership only; verification_session comes back in result.actionRequired for you to launch
  onActionCompleted: (refreshed) => {
    console.log(refreshed.status, refreshed.travelRuleStatus);
  }
});

console.log(result.transactionId, result.status, result.actionRequired?.type);
```

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`:

```typescript theme={null}
import { DiditSdk, DiditTransactionError } from "@didit-protocol/sdk-web";

try {
  await DiditSdk.shared.submitTransaction({ transactionToken, transaction });
} catch (error) {
  if (error instanceof DiditTransactionError) {
    switch (error.type) {
      case "expired_token": // mint a fresh token and retry
        break;
      case "invalid_token": // rejected: unknown, revoked, or over max_uses
        break;
      case "validation": // inspect error.fieldErrors for per-field details
        break;
      case "network": // retry with backoff
        break;
    }
  }
}
```

## SDK nativi

> [!NOTE]
>
> 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`.

```swift Swift theme={null}
import DiditSDK

let payload = DiditTransactionPayload(
    txnId: "wd-2026-07-07-0042",
    type: "travelRule",
    info: DiditTransactionInfo(
        direction: "out",
        amount: 0.25,
        currency: "ETH",
        currencyType: "crypto",
        cryptoParams: ["crypto_chain": "ETH"]
    ),
    subject: DiditTransactionParticipant(type: "individual", fullName: "Jane Doe"),
    counterparty: DiditTransactionParticipant(
        type: "individual",
        fullName: "Ana Diaz",
        paymentMethod: DiditTransactionPaymentMethod(type: "unhosted_wallet", accountId: "0xBeneficiaryWallet01")
    ),
    travelRule: DiditTravelRuleInfo(
        status: "AWAITING_COUNTERPARTY", // required by the API; ignored when Travel Rule is enabled
        beneficiaryData: ["wallet_address": "0xBeneficiaryWallet01", "name": "Ana Diaz"]
    ),
    includeCryptoScreening: true
)

let result = try await DiditSdk.shared.submitTransaction(
    transactionToken: sdkToken,
    transaction: payload,
    options: DiditTransactionOptions(
        autoLaunchAction: true,           // default
        useNativeWalletOwnership: true,   // default: native flow, browser fallback
        onTransactionUpdated: { refreshed in
            print(refreshed.status ?? "", refreshed.travelRuleStatus ?? "")
        }
    )
)

print(result.transactionId, result.status ?? "", result.actionRequired?.type ?? "none")
// Errors: DiditTransactionError .invalidToken / .expiredToken / .validation(fieldErrors) / .network
```

```kotlin Kotlin theme={null}
import me.didit.sdk.DiditSdk
import me.didit.sdk.transactions.*

val payload = DiditTransactionPayload(
    txnId = "wd-2026-07-07-0042",
    type = "travelRule",
    info = DiditTransactionInfo(
        direction = "out",
        amount = 0.25,
        currency = "ETH",
        currencyType = "crypto",
        cryptoParams = mapOf("crypto_chain" to "ETH")
    ),
    subject = DiditTransactionParticipant(type = "individual", fullName = "Jane Doe"),
    counterparty = DiditTransactionParticipant(
        type = "individual",
        fullName = "Ana Diaz",
        paymentMethod = DiditTransactionPaymentMethod(
            type = "unhosted_wallet",
            accountId = "0xBeneficiaryWallet01"
        )
    ),
    travelRule = DiditTravelRule(
        status = "AWAITING_COUNTERPARTY", // required by the API; ignored when Travel Rule is enabled
        beneficiaryData = mapOf("wallet_address" to "0xBeneficiaryWallet01", "name" to "Ana Diaz")
    ),
    includeCryptoScreening = true
)

// suspend fun - call from a coroutine; DiditSdk.initialize() must have run first
val result = DiditSdk.submitTransaction(
    transactionToken = sdkToken,
    transaction = payload,
    options = DiditTransactionOptions(
        autoLaunchAction = true,          // default
        useNativeWalletOwnership = true,  // default: native flow, Custom Tab fallback
        onTransactionUpdated = { refreshed ->
            Log.d("Didit", "${refreshed.status} ${refreshed.travelRuleStatus}")
        }
    )
)

Log.d("Didit", "${result.transactionId} ${result.status} ${result.actionRequired?.type}")
// Errors: sealed DiditTransactionException - InvalidToken / ExpiredToken / Validation(fieldErrors) / Network
```

```typescript React Native theme={null}
import {
  submitTransaction,
  getTransaction,
  DiditTransactionError,
} from "@didit-protocol/sdk-react-native";

const result = await submitTransaction(sdkToken, {
  txnId: "wd-2026-07-07-0042",
  type: "travelRule",
  info: {
    direction: "out",
    amount: 0.25,
    currency: "ETH",
    currencyType: "crypto",
    cryptoParams: { crypto_chain: "ETH" },
  },
  subject: { type: "individual", fullName: "Jane Doe" },
  counterparty: {
    type: "individual",
    fullName: "Ana Diaz",
    paymentMethod: { type: "unhosted_wallet", accountId: "0xBeneficiaryWallet01" },
  },
  travelRule: {
    status: "AWAITING_COUNTERPARTY", // required by the API; ignored when Travel Rule is enabled
    beneficiaryData: { wallet_address: "0xBeneficiaryWallet01", name: "Ana Diaz" },
  },
  includeCryptoScreening: true,
}, {
  autoLaunchAction: true, // default
  onTransactionUpdated: (refreshed) => {
    console.log(refreshed.status, refreshed.travelRuleStatus);
  },
});

console.log(result.transactionId, result.status, result.actionRequired?.type);
// Errors: DiditTransactionError with code invalid_token | expired_token | validation | network
```

```dart Flutter theme={null}
import 'package:didit_sdk/sdk_flutter.dart';

final result = await DiditSdk.submitTransaction(
  sdkToken,
  DiditTransactionPayload(
    txnId: 'wd-2026-07-07-0042',
    type: 'travelRule',
    info: DiditTransactionInfo(
      direction: 'out',
      amount: 0.25,
      currency: 'ETH',
      currencyType: 'crypto',
      cryptoParams: {'crypto_chain': 'ETH'},
    ),
    subject: DiditTransactionParticipant(type: 'individual', fullName: 'Jane Doe'),
    counterparty: DiditTransactionParticipant(
      type: 'individual',
      fullName: 'Ana Diaz',
      paymentMethod: DiditTransactionPaymentMethod(
        type: 'unhosted_wallet',
        accountId: '0xBeneficiaryWallet01',
      ),
    ),
    travelRule: DiditTravelRule(
      status: 'AWAITING_COUNTERPARTY', // required by the API; ignored when Travel Rule is enabled
      beneficiaryData: {'wallet_address': '0xBeneficiaryWallet01', 'name': 'Ana Diaz'},
    ),
    includeCryptoScreening: true,
  ),
  options: DiditTransactionOptions(
    autoLaunchAction: true, // default
    onTransactionUpdated: (refreshed) {
      debugPrint('${refreshed.status} ${refreshed.travelRuleStatus}');
    },
  ),
);

debugPrint('${result.transactionId} ${result.status} ${result.actionRequired?.type}');
// Errors: DiditTransactionException with code invalid_token | expired_token | validation | network
```

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/`](https://docs.didit.me/it/management-api/transactions/create) 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/`](https://docs.didit.me/it/management-api/transactions/sdk-token), poi chiama [`POST /v1/transactions/`](https://docs.didit.me/it/management-api/transactions/submit-sdk) e [`GET /v1/transactions/{transaction_id}/`](https://docs.didit.me/it/management-api/transactions/get-sdk) 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`:

| `action_required.type` | Cosa fare |
| - | - |
| `wallet_ownership` | Consegna `url` all'utente e aprilo in un **ambiente browser reale**: una normale scheda sul web, `SFSafariViewController` / Chrome Custom Tabs su mobile, oppure fuori banda tramite un link via email o un codice QR. Non mostrarlo mai in una webview incorporata: i deep link dei wallet non possono uscirne. |
| `verification_session` | Invia l'utente a `url`, che è la pagina di verifica ospitata standard di Didit, oppure passa `session_token` a qualsiasi integrazione di verifica Didit che già utilizzi. |

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](https://docs.didit.me/it/transaction-monitoring/webhooks.md) (`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`](https://docs.didit.me/it/transaction-monitoring/travel-rule#webhook).

## Prossimi passi

> [card] [Invio delle transazioni](https://docs.didit.me/it/transaction-monitoring/transactions.md)
>
> L'API Create Transaction lato server e il riferimento completo del payload.

> [card] [Travel Rule](https://docs.didit.me/it/transaction-monitoring/travel-rule.md)
>
> Lo scambio gestito tra ordinante e beneficiario, le impostazioni e il ciclo di vita.

> [card] [Widget per la verifica della proprietà del wallet](https://docs.didit.me/it/transaction-monitoring/wallet-ownership-widget.md)
>
> Il flusso ospitato per dimostrare il controllo alla base delle azioni wallet\_ownership.

> [card] [Webhook](https://docs.didit.me/it/transaction-monitoring/webhooks.md)
>
> transaction.created, transaction.status.updated e la verifica della firma.

> [card] [Regole](https://docs.didit.me/it/transaction-monitoring/rules.md)
>
> Azioni delle regole, incluse quelle che creano sessioni di verifica.

> [card] [Stati](https://docs.didit.me/it/transaction-monitoring/statuses.md)
>
> Che cosa significa AWAITING\_USER nel ciclo di vita della transazione.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.