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

# Enviament de transaccions amb SDK - KYT des del client

> Envia transaccions monitoritzades des de la teva app amb Didit, amb un token d'àmbit limitat i intel·ligència de dispositiu automàtica.

Els SDK de Didit poden enviar [transaccions monitoritzades](https://docs.didit.me/ca/transaction-monitoring/transactions.md) directament des del dispositiu de l'usuari final, incloses les transferències de la [Travel Rule](https://docs.didit.me/ca/transaction-monitoring/travel-rule.md) i el cribratge de criptoactius.
El teu backend crea un token de vida curta limitat a un àmbit concret, i el SDK fa la resta.
Afegeix automàticament la intel·ligència de dispositiu i, quan una transferència de la Travel Rule necessita una prova de control de la cartera, obre el flux d'aquesta prova a l'instant.
Una sessió de verificació requerida no s'obre mai automàticament: es retorna a la resposta perquè la teva aplicació l'obri amb la seva pròpia integració de verificació de Didit; consulta més avall [Accions obligatòries per a l'usuari](#accions-requerides-de-l’usuari).

## Visió general

L'enviament des del costat del client es basa en tres garanties:

* **Vinculació del subjecte a prova de suplantació.** El token es limita a un sol `vendor_data` (l'identificador del teu usuari) en el moment de la creació. La identitat del subjecte de cada transacció enviada amb el token s'imposa al servidor a partir d'aquesta vinculació, de manera que un client manipulat no pot enviar transaccions en nom d'un altre usuari, i el payload mai no ha de portar credencials.
* **Intel·ligència de dispositiu automàtica.** El SDK recull una empremta digital del dispositiu i l'envia amb l'enviament, i Didit deriva al servidor l'adreça IP i l'agent d'usuari. El context combinat del dispositiu s'adjunta al subjecte de la transacció sense cap biblioteca addicional, sense cap token de dispositiu separat i sense cap infraestructura addicional per part teva. Alimenta les [regles](https://docs.didit.me/ca/transaction-monitoring/rules.md) basades en el dispositiu i la correlació per dispositiu entre sessions i transaccions.
* **Prova de titularitat de la cartera oberta automàticament.** Quan la transacció creada requereix que l'usuari final demostri el control d'una cartera, la resposta porta un bloc `action_required` de tipus `wallet_ownership` i el SDK obre aquest flux immediatament, i després actualitza la transacció quan acaba; no cal que construeixis tu bucles de sondeig ni l'enviament d'enllaços. Una acció `verification_session` obligatòria és diferent: no s'obre mai automàticament, i es retorna al mateix bloc perquè la teva aplicació l'obri amb la seva pròpia integració de verificació de Didit.

> [!NOTE]
>
> El [`POST /v3/transactions/`](https://docs.didit.me/ca/transaction-monitoring/transactions) al servidor continua sent l'opció adequada quan no hi ha cap usuari final present: transferències administratives, importacions per lots o esdeveniments informats pels teus propis sistemes.
> Tots dos camins creen els mateixos registres de transacció, executen les mateixes regles i emeten els mateixos [webhooks](https://docs.didit.me/ca/transaction-monitoring/webhooks.md).

## Flux

**1. Crea un token de transacció**

El teu backend crida `POST /v3/transactions/sdk-token/` amb la teva clau d'API i el `vendor_data` de l'usuari, i lliura el `sdk_token` retornat a la teva aplicació.

**2. Envia des del dispositiu**

L'aplicació crida `submitTransaction` del SDK, que envia el payload a `POST /v1/transactions/` amb la capçalera `X-Transaction-Token` i l'enriquiment automàtic del dispositiu.

**3. Gestiona qualsevol acció requerida**

Si `action_required.type` és `wallet_ownership`, el SDK obre el widget automàticament i després actualitza la transacció mitjançant `GET /v1/transactions/{transaction_id}/`.
Si és `verification_session`, el SDK no obre res: llegeix `action_required` i inicia el teu propi flux de verificació amb el `session_token` corresponent (o amb `url` a la web).

## Creació d'un token de transacció

Crea tokens des del servidor amb `POST /v3/transactions/sdk-token/`: s'utilitza la mateixa autenticació amb clau d'API que la resta d'endpoints de transaccions de `/v3/`.

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

| Camp | Tipus | Descripció |
| - | - | - |
| `vendor_data` | string | **Obligatori.** El teu identificador intern de l'usuari final per al qual està definit l'àmbit d'aquest token. La identitat del subjecte de cada transacció enviada amb el token s'imposa a partir d'aquest valor. |
| `ttl_seconds` | integer | Durada del token en segons. Per defecte és `900` (15 minuts); el màxim és `86400` (24 hores). |
| `max_uses` | integer | Opcional. Nombre màxim d'enviaments satisfactoris permesos amb aquest token. Omet-lo per a usos il·limitats dins del TTL. |

Els àlies en camelCase (`vendorData`, `ttlSeconds`, `maxUses`) també s'accepten, com la resta de l'API `/v3/`.

> [!WARNING]
>
> **Mai no incloguis la teva clau d'API en una aplicació.**
> El teu backend conserva la clau d'API i crea tokens; només el `sdk_token` de vida curta arriba al dispositiu.
> Crea un token nou per a cada sessió d'usuari en lloc de reutilitzar-ne de llarga durada, i mantén `max_uses` ajustat quan es coneix el nombre d'enviaments esperats.

## Enviament des del dispositiu

Els SDK envien una sol·licitud `POST /v1/transactions/` amb el token a la capçalera `X-Transaction-Token`.
El payload que es transmet utilitza camelCase; els mètodes `submitTransaction` el construeixen per tu, de manera que només cal que tractis directament amb ell quan cridis l'endpoint des del teu propi 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
  }'
```

Cada camp de primer nivell correspon al seu equivalent a [`POST /v3/transactions/`](https://docs.didit.me/ca/transaction-monitoring/transactions):

| Camp de transmissió | Equivalent a `/v3/` | Notes |
| - | - | - |
| `txnId` | `transaction_id` | **Obligatori.** El teu identificador únic de la transacció. |
| `txnDate` | `transaction_at` | Marca de temps ISO-8601; per defecte, l'hora actual. |
| `zoneId` | `time_zone` | Identificador de zona horària IANA, p. ex. `Europe/Madrid`. |
| `type` | `transaction_category` | **Obligatori.** `finance`, `kyc`, `travelRule`, `userPlatformEvent`, `gamblingBet` i les altres categories. |
| `info` | `transaction_details` | **Obligatori.** `direction`, `amount`, `currency` (obligatori), més `currencyType`, `amountInDefaultCurrency`, `defaultCurrencyCode`, `paymentDetails`, `paymentTxnId`, `type` (tipus d'acció), `cryptoParams`. |
| `subject`, `counterparty` | `subject`, `counterparty` | Participants: `type` (tipus d'entitat), `externalUserId`, `fullName`, `firstName`, `lastName`, `dob`, `address`, `institutionInfo`, `device`, `paymentMethod` (`type`, `accountId`, `issuingCountry`). Si el `paymentMethod.type` de la contrapart és `unhosted_wallet`, la destinació es declara com a cartera autoallotjada; consulta [més avall](#declarar-una-cartera-autoallotjada). |
| `props` | `custom_properties` | Parells clau-valor personalitzats, que es poden utilitzar a les regles com a `custom_values.<key>`. |
| `travelRule` | `travel_rule_details` | `status`, `protocol`, `required`, `obligationsCount`, `originatorData`, `beneficiaryData`, `metadata`; `status` és obligatori sempre que enviïs aquest bloc, o la sol·licitud fallarà amb `400`. Quan Travel Rule està activada, Didit ignora qualsevol `status` que enviïs i gestiona l'intercanvi per si mateix. |
| `includeCryptoScreening` | `include_crypto_screening` | Substitució del cribratge de criptoactius per transacció. |

La resposta és el mateix detall de transacció en snake\_case que els endpoints `/v3/`, més el bloc `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"
  }
}
```

### Declarar una cartera autoallotjada

L'exemple anterior declara la destinació com la cartera autoallotjada de l'usuari: el `paymentMethod.type` de la contrapart és `unhosted_wallet`.
Aquesta declaració canvia la manera com s'executa l'[intercanvi de Travel Rule](https://docs.didit.me/ca/transaction-monitoring/travel-rule.md#carteres-autoallotjades-declarades):

* Didit omet per complet l'encaminament cap a la VASP contrapart, perquè no hi ha cap VASP darrere d'una cartera autoallotjada, i crea la transferència directament en estat `UNCONFIRMED_OWNERSHIP`.
* Quan `auto_wallet_verification` està activat a la teva [configuració de Travel Rule](https://docs.didit.me/ca/transaction-monitoring/travel-rule.md#configuració) (per defecte, ho està), es crea automàticament una sessió del [widget de titularitat de la cartera](https://docs.didit.me/ca/transaction-monitoring/wallet-ownership-widget.md) i es retorna a `action_required`, exactament com a la resposta anterior.
* El SDK obre el widget automàticament; quan l'usuari demostra que controla la cartera, la transferència executa la comparació de noms i passa a `COMPLETED`, o a `COUNTERPARTY_MISMATCHED_DATA` quan el nom no coincideix amb el titular de l'adreça de la cartera en la seva llibreta d'adreces.

Sense la declaració, Didit prova primer els [canals](https://docs.didit.me/ca/transaction-monitoring/travel-rule.md#visió-general) en ordre i només recorre al flux de titularitat si no es troba cap VASP contrapart.
Declara la cartera sempre que el teu producte ja sàpiga que la destinació de la retirada és la cartera pròpia de l'usuari: t'estalvia l'intent d'encaminament fallit i porta l'usuari directament al flux de prova.

### Imposició del subjecte

Hi ha tres coses que sempre decideix el token, mai el payload:

* L'aplicació sota la qual es crea la transacció.
* La identitat del subjecte: `subject.externalUserId` del payload s'ignora i se substitueix pel `vendor_data` del token.
* El context del dispositiu del subjecte (vegeu més avall); els camps derivats del servidor tenen prioritat en cas de conflicte amb qualsevol cosa que s'indiqui a `subject.device`.

Un `401` d'aquest endpoint significa que el token ha estat rebutjat: falta, és desconegut, està revocat, ha superat el seu `max_uses` o ha caducat.
Les respostes per token caducat sempre contenen la paraula `expired` al detall de l'error, que és com els SDK distingeixen els errors `expired_token` dels errors `invalid_token`.

### Llegir la transacció de nou

`GET /v1/transactions/{transaction_id}/` retorna el mateix detall amb la mateixa autenticació `X-Transaction-Token`.
Els SDK el criden automàticament quan acaba un flux d'acció iniciat o quan es tanca la seva finestra, de manera que l'estat final mai depèn d'un únic esdeveniment o notificació.
Utilitza-ho tu mateix per tornar a comprovar `status` i `action_required` en qualsevol moment mentre el token sigui vàlid; des del teu backend, [`GET /v3/transactions/{transaction_id}/`](https://docs.didit.me/ca/transaction-monitoring/transactions) retorna el mateix registre.

## Intel·ligència automàtica del dispositiu

Cada enviament porta intel·ligència del dispositiu sense cap feina d'integració per la teva part:

* El SDK recull una empremta digital del dispositiu que respecta la privacitat (l'esquema `didit-fp-v2`: senyals de plataforma i del navegador, un identificador persistent del dispositiu, una puntuació de bot i un hash compost) i l'envia amb la sol·licitud.
* Didit deriva al servidor l'adreça, l'agent d'usuari i l'accept-language a partir de la mateixa connexió, de manera que el payload no els pot falsificar.

El resultat combinat es normalitza en el context del dispositiu del participant que és el subjecte i es crea o s'actualitza al graf de dispositius, el mateix que alimenten les sessions de KYC.
Això fa que les transaccions siguin correlacionables per dispositiu a tota la base d'usuaris («relacionades per dispositiu»), alimenta els senyals de dispositiu i de xarxa a les [regles](https://docs.didit.me/ca/transaction-monitoring/rules.md) i a l'[enriquiment d'IP](https://docs.didit.me/ca/transaction-monitoring/ip-enrichment.md), i apareix al detall de la transacció a la [Console](https://docs.didit.me/ca/transaction-monitoring/console.md).

Les transaccions `/v3/` enviades des del servidor poden continuar enviant objectes `device_context` explícits per participant; en la via del SDK obtens una versió més completa de les mateixes dades sense cap cost addicional.

## Accions requerides de l'usuari

Algunes transaccions necessiten que l'usuari final faci alguna cosa abans de poder liquidar-se: una regla pot exigir una sessió de verificació, o un traspàs de Travel Rule pot exigir una prova que la cartera de destinació és realment de l'usuari.
Els dos casos apareixen al mateix lloc: el bloc `action_required` de la resposta de l'enviament (i de cada lectura posterior de la transacció):

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

| Tipus | Quan apareix | Què fa l'usuari |
| - | - | - |
| `verification_session` | Una [acció de regla](https://docs.didit.me/ca/transaction-monitoring/rules.md) ha requerit una verificació addicional de l'usuari i la transacció ha passat a `AWAITING_USER`. | Completa una sessió de verificació allotjada (per exemple, una nova comprovació biomètrica), iniciada per la vostra aplicació - consulteu [Iniciar una sessió de verificació](#iniciar-una-sessió-de-verificació) més avall. |
| `wallet_ownership` | Un traspàs de Travel Rule necessita una prova de control de la cartera per part de l'usuari final: la destinació s'ha [declarat com a cartera autoallotjada](#declarar-una-cartera-autoallotjada) (`UNCONFIRMED_OWNERSHIP`), o un traspàs de sortida no ha trobat cap VASP contrapart - i `auto_wallet_verification` està activat a la vostra [configuració de Travel Rule](https://docs.didit.me/ca/transaction-monitoring/travel-rule.md#configuració). Didit genera automàticament una sessió del [widget de titularitat de la cartera](https://docs.didit.me/ca/transaction-monitoring/wallet-ownership-widget.md) vinculada a la transacció. | Demostra el control de la cartera mitjançant signatura de missatges (en una cadena compatible), captura de pantalla o autodeclaració, segons permeti la vostra configuració de Travel Rule, però mai mitjançant la prova de Satoshi en una sessió generada automàticament - el SDK l'inicia automàticament per a vosaltres. |

Si tots dos podrien aplicar-se a la mateixa transacció, `wallet_ownership` té prioritat, perquè la prova de titularitat que falta bloqueja el propi intercanvi de Travel Rule.
`action_required` passa a ser `null` quan l'acció pendent es resol.
El camp `remediation` existent es conserva per compatibilitat amb versions anteriors; `action_required` és el bloc canònic.

### Contracte d'inici automàtic

`autoLaunchAction` (per defecte `true`) inicia automàticament només l'acció `wallet_ownership`:

* **Web** - s'obre al propi modal superposat del SDK, i la finalització es detecta mitjançant missatgeria amb validació d'origen i una consulta acotada de la transacció (mai una dependència d'un únic esdeveniment).
* **iOS / Android** (i els embolcalls de React Native i Flutter, que deleguen en els SDK natius) - es presenta com a **experiència nativa de titularitat de la cartera** del SDK (SwiftUI / Jetpack Compose) quan `useNativeWalletOwnership` és `true` (el valor per defecte), amb opció de reserva a `SFSafariViewController` / una Chrome Custom Tab i, finalment, al navegador per defecte. Quan el flux es tanca o l'aplicació torna al primer pla, el SDK torna a llegir la transacció automàticament.

Una acció `verification_session` mai s'inicia automàticament: sempre torna a `action_required` perquè la vostra aplicació l'iniciï amb la pròpia integració de verificació de Didit.

> [!WARNING]
>
> El widget de titularitat de la cartera no s'ha de renderitzar mai dins d'un webview integrat en un dispositiu mòbil: els enllaços profunds de les carteres no poden sortir-ne i el flux de prova queda bloquejat.
> Els SDK ja utilitzen la superfície de navegador correcta quan s'inicien automàticament; mantingueu aquesta regla si gestioneu vosaltres el `url`.
> Consulteu la [guia del widget de titularitat de la cartera](https://docs.didit.me/ca/transaction-monitoring/wallet-ownership-widget.md#obrir-la-url-retornada) per obtenir més detalls.

Quan el flux de titularitat de la cartera iniciat automàticament pel propi SDK es completa o es tanca, el SDK torna a llegir la transacció i passa el resultat actualitzat al vostre callback `onActionCompleted`.
Una acció `verification_session` no forma part d'aquest cicle, perquè la vostra aplicació l'inicia - consulteu la secció següent.

### Iniciar una sessió de verificació

Quan `action_required.type` és `verification_session`, llegiu el bloc de `result.actionRequired` i passeu-lo a la integració de verificació de Didit que ja teniu:

```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);
}
```

Quan el flux de verificació acabi, feu una crida a `GET /v1/transactions/{transaction_id}/` (o espereu el [webhook de transacció](#webhooks)) per obtenir l'estat actualitzat de la transacció.

### Exemple: reverificació biomètrica per a una retirada d'alt valor

Aquest és el flux en la seva millor versió: protegeix les transaccions de risc - imports elevats, beneficiaris nous, dispositius nous - amb una nova comprovació biomètrica de la mateixa persona que vau verificar en l'alta.

La usurpació de comptes es veu exactament així: contrasenya correcta, codi 2FA correcte (obtingut per phishing o mitjançant una suplantació de la SIM), i una retirada cap a una cartera que mai no havíeu vist.
En aquest punt, totes les defenses basades en credencials ja han fallat.
Però el rostre no falla: l'atacant no pot superar una selfie amb prova de vida que ha de coincidir amb la cara capturada durant l'alta KYC del titular legítim.

**1. Crea la regla una vegada, a la consola**

**Transactions → Rules → Create Rule**: condició `amount gte 5000`, acció **Change status → `AWAITING_USER`**, i seleccioneu el vostre flux de treball **Biometric Authentication** a l'acció.
Afegiu les altres condicions de risc que vulgueu - contrapart per primera vegada, nova empremta digital del dispositiu, canvi de país de la IP - com a condicions addicionals o com a regles addicionals.

**2. Envia la retirada com de costum**

El vostre codi d'enviament no canvia. Quan s'activa la regla, la resposta inclou `status: "AWAITING_USER"` i un bloc `action_required` de tipus `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. Inicia la nova comprovació**

Passeu el `session_token` (o el `url`) a la vostra integració de verificació de Didit existent, tal com s'explica a [Iniciar una sessió de verificació](#iniciar-una-sessió-de-verificació).
Com que la sessió es crea per al mateix `vendor_data` que el subjecte de la transacció, Didit ja disposa de la cara de referència verificada de l'usuari de l'alta - la selfie de prova de vida aprovada, la foto del xip del passaport o el retrat del document.
L'usuari fa una selfie en directe; Didit executa una prova de vida i una coincidència facial 1:1 amb aquesta referència emmagatzemada. Sense tornar a escanejar el document ni tornar a fer l'alta - uns segons de fricció, només en les transaccions que ho mereixen.

**4. Resol segons el resultat**

Quan la sessió es completa, la transacció es torna a avaluar automàticament: `APPROVED` si s'ha superat la nova comprovació, `DECLINED` si no, amb un webhook `transaction.status.updated` en qualsevol dels dos casos (`reason: remediation_completed` a la cronologia de la transacció).
La vostra aplicació rep el resultat mitjançant callbacks del tipus `onActionCompleted`, `GET /v1/transactions/{transaction_id}/` o el webhook.

Això funciona perquè l'alta KYC, la recomprovació biomètrica i la decisió sobre la transacció s'executen a la mateixa plataforma: la cara capturada durant l'alta és la referència per a la verificació addicional, el graf de dispositius vincula la sessió amb la transacció i el motor de regles ho connecta tot. Sense codi d'integració de proveïdors, sense exportació d'imatges facials i sense un contracte biomètric separat.
La recomprovació es factura com a funcionalitat estàndard d'Autenticació biomètrica ([\$0.10](https://docs.didit.me/ca/getting-started/pricing.md)) només quan realment s'executa.

## Web SDK

`submitTransaction` està disponible avui al singleton del Web SDK:

```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);
```

El resultat és `{ transactionId, status, travelRuleStatus?, actionRequired? }`.
Al payload del nivell del SDK escriu `category`, `details` i `customProperties`; el SDK els mapeja automàticament als àlies del protocol (`type`, `info` i `props`).

Els errors es tipifiquen mitjançant `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 natius

> [!NOTE]
>
> L'API nativa `submitTransaction` s'inclou a l'SDK d'iOS i Android 4.1.0 (i a les versions de React Native i Flutter que fixen aquesta versió).
> A diferència del Web SDK, els payloads natius usen directament els noms de camp del protocol: `type`, `info` i `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
```

Tots els SDK també exposen `getTransaction(transactionToken, transactionId)` per tornar a llegir la transacció amb el mateix token: els SDK la criden per tu després d'un flux d'acció iniciat, i tu pots cridar-la tu mateix en qualsevol moment.

Al mòbil, el transport de l'empremta del dispositiu ja s'aplica a totes les crides de xarxa del SDK, de manera que els enviaments de transaccions porten automàticament la mateixa intel·ligència de dispositiu.

## Sense els SDK

Tot el que fan els SDK és HTTPS simple; aquí no cal cap d'ells.
Hi ha dues formes d'integració equivalents:

**Només del servidor (sense cap token).** Envia la transacció amb [`POST /v3/transactions/`](https://docs.didit.me/ca/management-api/transactions/create) des del teu backend amb la teva clau d'API. La resposta (i cada `GET /v3/transactions/{transaction_id}/` posterior) inclou el mateix bloc `action_required`. També pots passar el context `device` explícit per a cada participant, ja que no hi ha cap SDK que el reculli per tu.

**Del costat del client amb la teva pròpia pila HTTP.** Genera un token amb [`POST /v3/transactions/sdk-token/`](https://docs.didit.me/ca/management-api/transactions/sdk-token) i després crida [`POST /v1/transactions/`](https://docs.didit.me/ca/management-api/transactions/submit-sdk) i [`GET /v1/transactions/{transaction_id}/`](https://docs.didit.me/ca/management-api/transactions/get-sdk) des del dispositiu amb la capçalera `X-Transaction-Token`: s'apliquen la mateixa vinculació de subjecte i la mateixa derivació d'IP i user-agent al servidor.

A continuació, gestiona tu mateix `action_required`:

| `action_required.type` | Què cal fer |
| - | - |
| `wallet_ownership` | Entrega `url` a l'usuari i obre-la en una **superfície de navegador real**: una pestanya normal al web, `SFSafariViewController` o Chrome Custom Tabs al mòbil, o fora de banda mitjançant un enllaç de correu electrònic o un codi QR. Mai no la rendericis dins d'una webview integrada: els enllaços profunds de les carteres no poden sortir-ne. |
| `verification_session` | Envia l'usuari a `url`, que és la pàgina de verificació allotjada estàndard de Didit, o passa `session_token` a qualsevol integració de verificació de Didit que ja tinguis. |

I detecta la finalització sense cap callback del client:

* **Webhooks (recomanat).** `transaction.status.updated` s'activa quan l'acció pendent resol la transacció, i `travel_rule.status.updated` s'activa en cada transició de l'intercanvi; tots dos inclouen prou informació per actualitzar els teus registres sense fer sondeig.
* **Sondeig.** Torna a llegir la transacció (`/v3/` des del teu backend, o `/v1/` amb el token mentre sigui vàlid): `action_required` torna a `null` quan es resol, i `status` / `travel_rule.status` porten el resultat.

En aquest disseny no hi ha dependències que només funcionin una vegada: el widget, la sessió de verificació i el teu backend convergeixen tots al mateix estat persistent de la transacció, de manera que una pestanya tancada o un webhook perdut sempre es pot recuperar tornant a llegir la transacció.

## Webhooks

El teu backend no necessita que l'aplicació li notifiqui res.
Quan una transacció enviada requereix una acció de l'usuari, el [webhook de transacció](https://docs.didit.me/ca/transaction-monitoring/webhooks.md) (`transaction.created` o `transaction.status.updated`) inclou el mateix bloc `action_required`, de manera que els teus sistemes coneixen l'acció pendent encara que l'aplicació es tanqui abans d'acabar el flux.
Una acció `verification_session` arriba amb l'estat `AWAITING_USER`, però una acció `wallet_ownership` no canvia per si sola l'estat de la transacció; per tant, llegeix `action_required` en lloc de l'estat.
Les transicions de Travel Rule emeten a més [`travel_rule.status.updated`](https://docs.didit.me/ca/transaction-monitoring/travel-rule#webhooks).

## Següents passos

> [card] [Enviament de transaccions](https://docs.didit.me/ca/transaction-monitoring/transactions.md)
>
> L'API de creació de transaccions del servidor i la referència completa del payload.

> [card] [Travel Rule](https://docs.didit.me/ca/transaction-monitoring/travel-rule.md)
>
> L'intercanvi gestionat entre l'ordenant i el beneficiari, la configuració i el cicle de vida.

> [card] [Widget de titularitat de cartera](https://docs.didit.me/ca/transaction-monitoring/wallet-ownership-widget.md)
>
> El flux allotjat de prova de control que hi ha darrere de les accions wallet\_ownership.

> [card] [Webhooks](https://docs.didit.me/ca/transaction-monitoring/webhooks.md)
>
> transaction.created, transaction.status.updated i la verificació de la signatura.

> [card] [Regles](https://docs.didit.me/ca/transaction-monitoring/rules.md)
>
> Accions de regla, incloses les que creen sessions de verificació.

> [card] [Estats](https://docs.didit.me/ca/transaction-monitoring/statuses.md)
>
> Què significa AWAITING\_USER al cicle de vida de la transacció.


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