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

# Submissão de transações no SDK - KYT no lado do cliente

> Submeta transações monitorizadas na sua app com um token limitado. Didit oferece análise de dispositivo automática e verificação da carteira.

Os SDKs da Didit podem submeter [transações monitorizadas](https://docs.didit.me/pt/transaction-monitoring/transactions.md) diretamente a partir do dispositivo do utilizador final, incluindo transferências [Travel Rule](https://docs.didit.me/pt/transaction-monitoring/travel-rule.md) e triagem de criptoativos.
O seu backend emite um token de âmbito limitado e de curta duração; o SDK trata do resto.
Anexa automaticamente informação sobre o dispositivo e, quando uma transferência Travel Rule precisa de prova de controlo da carteira, lança automaticamente esse fluxo no momento.
Uma sessão de verificação obrigatória nunca é lançada automaticamente - é devolvida na resposta para que a sua aplicação a lance com a sua própria integração de verificação da Didit; veja [Ações obrigatórias do utilizador](#ações-necessárias-do-utilizador) abaixo.

## Visão geral

A submissão do lado do cliente assenta em três garantias:

* **Vinculação do sujeito resistente a spoofing.** O token fica associado a um único `vendor_data` (o identificador do seu utilizador) no momento da emissão. A identidade do sujeito de cada transação submetida com o token é imposta do lado do servidor a partir desta associação, pelo que um cliente adulterado não consegue submeter em nome de outro utilizador, e o o payload nunca precisa de incluir credenciais.
* **Informação automática sobre o dispositivo.** O SDK recolhe uma impressão digital do dispositivo e envia-a com a submissão, e a Didit deriva do lado do servidor o endereço IP e o user agent. O contexto combinado do dispositivo fica associado ao sujeito da transação sem biblioteca adicional, sem token de dispositivo separado e sem trabalho de integração da sua parte. Alimenta as [regras](https://docs.didit.me/pt/transaction-monitoring/rules.md) baseadas no dispositivo e a correlação de transações e sessões relacionadas pelo dispositivo.
* **Prova de controlo da carteira lançada automaticamente.** Quando a transação criada exige que o utilizador final prove o controlo de uma carteira, a resposta inclui um bloco `action_required` do tipo `wallet_ownership` e o SDK abre esse fluxo de imediato, atualizando depois a transação quando terminar - sem ciclos de polling nem envio de ligações que tenha de construir. Uma ação `verification_session` obrigatória é diferente: nunca é lançada automaticamente e é devolvida no mesmo bloco para que a sua aplicação a lance com a sua própria integração de verificação da Didit.

> [!NOTE]
>
> A chamada do lado do servidor, [`POST /v3/transactions/`](https://docs.didit.me/pt/transaction-monitoring/transactions), continua a ser a escolha certa quando não há nenhum utilizador final presente: transferências de back-office, importações em lote ou eventos reportados pelos seus próprios sistemas.
> Ambos os caminhos criam os mesmos registos de transação, executam as mesmas regras e emitem os mesmos [webhooks](https://docs.didit.me/pt/transaction-monitoring/webhooks.md).

## Fluxo

**1. Emitir um token de transação**

O seu backend chama `POST /v3/transactions/sdk-token/` com a sua chave de API e o `vendor_data` do utilizador, e entrega o `sdk_token` devolvido à sua aplicação.

**2. Submeter a partir do dispositivo**

A aplicação chama `submitTransaction` no SDK, que envia o payload para `POST /v1/transactions/` com o cabeçalho `X-Transaction-Token` e o enriquecimento automático do dispositivo.

**3. Tratar qualquer ação obrigatória**

Se `action_required.type` for `wallet_ownership`, o SDK lança automaticamente o widget e depois atualiza a transação através de `GET /v1/transactions/{transaction_id}/`.
Se for `verification_session`, o SDK não lança nada - leia `action_required` e inicie o seu próprio fluxo de verificação com o respetivo `session_token` (ou `url`, na web).

## Emitir um token de transação

Emita os tokens a partir do seu backend com `POST /v3/transactions/sdk-token/` - a mesma autenticação por chave de API que o resto dos endpoints de transação `/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"
}
```

| Campo | Tipo | Descrição |
| - | - | - |
| `vendor_data` | string | **Obrigatório.** O seu identificador interno para o utilizador final ao qual este token fica associado. Cada transação submetida com o token tem a identidade do sujeito imposta a partir deste valor. |
| `ttl_seconds` | integer | Tempo de vida do token em segundos. Por defeito, `900` (15 minutos); máximo de `86400` (24 horas). |
| `max_uses` | integer | Opcional. Número máximo de submissões bem-sucedidas permitidas com este token. Omita para utilizações ilimitadas dentro do TTL. |

Os aliases em camelCase (`vendorData`, `ttlSeconds`, `maxUses`) também são aceites, tal como no resto da API `/v3/`.

> [!WARNING]
>
> **Nunca distribua a sua chave de API numa aplicação.**
> A chave de API fica no seu backend e emite os tokens; apenas o `sdk_token` de curta duração chega ao dispositivo.
> Emita um token novo por sessão do utilizador em vez de reutilizar tokens de longa duração, e mantenha `max_uses` restrito quando o número de submissões esperadas for conhecido.

## Submeter a partir do dispositivo

Os SDK enviam um pedido a `POST /v1/transactions/` com o token no cabeçalho `X-Transaction-Token`.
O payload enviado na rede usa camelCase. Os métodos `submitTransaction` montam-no por si, por isso só precisa de trabalhar diretamente com ele ao chamar o endpoint a partir do seu próprio cliente 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 campo de primeiro nível corresponde ao seu equivalente em [`POST /v3/transactions/`](https://docs.didit.me/pt/transaction-monitoring/transactions):

| Campo no payload | Equivalente em `/v3/` | Notas |
| - | - | - |
| `txnId` | `transaction_id` | **Obrigatório.** O identificador único da transação, atribuído por si. |
| `txnDate` | `transaction_at` | Carimbo de data/hora ISO-8601; por omissão, a data e hora atuais. |
| `zoneId` | `time_zone` | Identificador de fuso horário IANA, por exemplo `Europe/Madrid`. |
| `type` | `transaction_category` | **Obrigatório.** `finance`, `kyc`, `travelRule`, `userPlatformEvent`, `gamblingBet` e as restantes categorias. |
| `info` | `transaction_details` | **Obrigatório.** `direction`, `amount`, `currency` (obrigatório), além de `currencyType`, `amountInDefaultCurrency`, `defaultCurrencyCode`, `paymentDetails`, `paymentTxnId`, `type` (tipo de ação), `cryptoParams`. |
| `subject`, `counterparty` | `subject`, `counterparty` | Participantes: `type` (tipo de entidade), `externalUserId`, `fullName`, `firstName`, `lastName`, `dob`, `address`, `institutionInfo`, `device`, `paymentMethod` (`type`, `accountId`, `issuingCountry`). Um `paymentMethod.type` de `unhosted_wallet` na contraparte declara o destino como carteira autocustodiada - veja [abaixo](#declarar-uma-carteira-autocustodiada). |
| `props` | `custom_properties` | Pares chave-valor personalizados, utilizáveis nas regras como `custom_values.<key>`. |
| `travelRule` | `travel_rule_details` | `status`, `protocol`, `required`, `obligationsCount`, `originatorData`, `beneficiaryData`, `metadata`; `status` é obrigatório sempre que enviar este bloco, caso contrário o pedido falha com `400`. Quando o Travel Rule está ativado, a Didit ignora qualquer `status` que envie e conduz a troca por si. |
| `includeCryptoScreening` | `include_crypto_screening` | Substituição da triagem de criptoativos por transação. |

A resposta tem o mesmo detalhe de transação em snake\_case dos endpoints `/v3/`, acrescido do bloco `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 uma carteira autocustodiada

O exemplo acima declara o destino como a carteira autocustodiada do próprio utilizador: o `paymentMethod.type` da contraparte é `unhosted_wallet`.
Esta declaração altera a forma como corre a [troca de Travel Rule](https://docs.didit.me/pt/transaction-monitoring/travel-rule.md#carteiras-autocustodiadas-declaradas):

* A Didit não encaminha a transferência através de um VASP contraparte, porque não existe nenhum VASP por trás de uma carteira autocustodiada, e cria a transferência diretamente em `UNCONFIRMED_OWNERSHIP`.
* Quando `auto_wallet_verification` está ativo nas suas [definições de Travel Rule](https://docs.didit.me/pt/transaction-monitoring/travel-rule.md#configuração) (o valor por omissão), é criada automaticamente uma sessão do [widget de propriedade da carteira](https://docs.didit.me/pt/transaction-monitoring/wallet-ownership-widget.md) e devolvida em `action_required`, tal como na resposta acima.
* O SDK abre o widget automaticamente. Quando o utilizador prova o controlo da carteira, a transferência faz a comparação de nomes e passa a `COMPLETED`, ou a `COUNTERPARTY_MISMATCHED_DATA` se o nome não corresponder ao titular do livro de endereços da carteira.

Sem esta declaração, a Didit tenta primeiro os [canais](https://docs.didit.me/pt/transaction-monitoring/travel-rule.md#visão-geral) por ordem e só recorre ao fluxo de propriedade se não encontrar nenhum VASP contraparte.
Declare a carteira sempre que o seu produto já souber que o destino do levantamento é a carteira do próprio utilizador: poupa a tentativa de encaminhamento falhada e leva o utilizador diretamente ao fluxo de prova.

### Aplicação das regras ao sujeito

Três aspetos são sempre decididos pelo token, nunca pelo payload:

* A aplicação em que a transação é criada.
* A identidade do sujeito: o `subject.externalUserId` do payload é ignorado e substituído pelo `vendor_data` do token.
* O contexto do dispositivo do sujeito (ver abaixo); os campos derivados no servidor prevalecem em caso de conflito com o que for enviado em `subject.device`.

Um `401` devolvido por este endpoint significa que o token foi rejeitado: em falta, desconhecido, revogado, acima do seu `max_uses` ou expirado.
As respostas de token expirado contêm sempre a palavra `expired` no detalhe do erro, e é assim que os SDK distinguem os erros `expired_token` dos erros `invalid_token`.

### Ler a transação

`GET /v1/transactions/{transaction_id}/` devolve o mesmo detalhe, com a mesma autenticação por `X-Transaction-Token`.
Os SDK chamam-no automaticamente depois de um fluxo de ação lançado terminar ou de a sua janela fechar, para que o estado final nunca dependa de um único evento ou notificação.
Pode usá-lo você próprio para voltar a verificar `status` e `action_required` em qualquer momento enquanto o token for válido. A partir do seu backend, [`GET /v3/transactions/{transaction_id}/`](https://docs.didit.me/pt/transaction-monitoring/transactions) devolve o mesmo registo.

## Inteligência de dispositivo automática

Cada submissão inclui inteligência de dispositivo sem qualquer trabalho de integração da sua parte:

* O SDK recolhe uma impressão digital do dispositivo que respeita a privacidade (o esquema `didit-fp-v2`: sinais de plataforma e do navegador, um identificador persistente do dispositivo, uma pontuação de bot e um hash composto) e envia-a com o pedido.
* A Didit deriva o endereço IP, o user agent e o accept-language do lado do servidor, a partir da própria ligação, pelo que não podem ser falsificados através do payload.

O resultado combinado é normalizado no contexto do dispositivo do participante que é o sujeito e inserido ou atualizado no grafo de dispositivos, o mesmo que as sessões de KYC alimentam.
Isto torna as transações correlacionáveis por dispositivo em toda a sua base de utilizadores («relacionadas por dispositivo»), alimenta sinais de dispositivo e de rede nas [regras](https://docs.didit.me/pt/transaction-monitoring/rules.md) e no [enriquecimento de IP](https://docs.didit.me/pt/transaction-monitoring/ip-enrichment.md), e aparece no detalhe da transação na [Consola](https://docs.didit.me/pt/transaction-monitoring/console.md).

As transações `/v3/` submetidas pelo servidor podem continuar a enviar objetos `device_context` explícitos por participante. No caminho do SDK, obtém a mesma informação numa versão mais completa, sem configuração adicional.

## Ações necessárias do utilizador

Algumas transações exigem que o utilizador final faça algo antes de poderem ser concluídas: uma regra pode exigir uma sessão de verificação, ou uma transferência da Travel Rule pode exigir prova de que a carteira de destino pertence de facto ao utilizador.
Ambos os casos aparecem no mesmo local, no bloco `action_required` da resposta de submissão (e em todas as leituras seguintes da transação):

```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 aparece | O que o utilizador faz |
| - | - | - |
| `verification_session` | Uma [ação de regra](https://docs.didit.me/pt/transaction-monitoring/rules.md) exigiu verificação adicional do utilizador e a transação passou para `AWAITING_USER`. | Conclui uma sessão de verificação alojada (por exemplo, uma nova verificação biométrica), iniciada pela sua aplicação - veja [Iniciar uma sessão de verificação](#iniciar-uma-sessão-de-verificação) abaixo. |
| `wallet_ownership` | Uma transferência da Travel Rule precisa de prova, por parte do utilizador final, de controlo da carteira - o destino foi [declarado como carteira autocustodiada](#declarar-uma-carteira-autocustodiada) (`UNCONFIRMED_OWNERSHIP`), ou uma transferência de saída não encontrou VASP da contraparte - e `auto_wallet_verification` está ativado nas suas [definições da Travel Rule](https://docs.didit.me/pt/transaction-monitoring/travel-rule.md#configuração). A Didit cria automaticamente uma sessão do [widget de prova de propriedade da carteira](https://docs.didit.me/pt/transaction-monitoring/wallet-ownership-widget.md) associada à transação. | Prova o controlo da carteira através de assinatura de mensagem (numa cadeia suportada), captura de ecrã ou autodeclaração, conforme permitirem as suas definições da Travel Rule, mas nunca por teste Satoshi numa sessão criada automaticamente - o SDK abre esta automaticamente por si. |

Se ambos puderem aplicar-se à mesma transação, `wallet_ownership` tem prioridade, porque a prova de propriedade em falta bloqueia a própria troca da Travel Rule.
`action_required` passa a `null` quando a ação pendente estiver resolvida.
O campo `remediation` existente mantém-se por compatibilidade com versões anteriores; `action_required` é o bloco canónico.

### Contrato de abertura automática

`autoLaunchAction` (predefinição `true`) abre automaticamente apenas a ação `wallet_ownership`:

* **Web** - aberta no modal sobreposto do próprio SDK, com a conclusão detetada por mensagens com validação de origem e por uma consulta limitada no tempo à transação (nunca dependente de um único evento).
* **iOS / Android** (e os wrappers React Native e Flutter, que delegam nos SDK nativos) - apresentada como a **experiência nativa de propriedade da carteira** do SDK (SwiftUI / Jetpack Compose) quando `useNativeWalletOwnership` é `true` (o valor predefinido), recorrendo ao `SFSafariViewController` / a um Chrome Custom Tab e, por fim, ao navegador predefinido. Depois de o fluxo ser fechado ou de a aplicação voltar ao primeiro plano, o SDK volta a ler a transação automaticamente.

Uma ação `verification_session` nunca é aberta automaticamente: aparece sempre em `action_required` para que a sua aplicação a inicie com a sua própria integração de verificação da Didit.

> [!WARNING]
>
> O widget de propriedade da carteira nunca deve ser apresentado dentro de uma webview móvel incorporada - as ligações profundas para carteiras não conseguem sair dela e o fluxo de prova fica sem saída.
> Os SDK já usam a superfície de navegador correta na abertura automática; mantenha essa regra se tratar o `url` por conta própria.
> Consulte o [guia do widget de propriedade da carteira](https://docs.didit.me/pt/transaction-monitoring/wallet-ownership-widget.md#abrir-o-url-devolvido) para mais detalhes.

Depois de o fluxo de propriedade da carteira, aberto automaticamente pelo SDK, terminar ou ser fechado, o SDK volta a ler a transação e entrega o resultado atualizado ao seu callback `onActionCompleted`.
Uma ação `verification_session` não faz parte desse ciclo, uma vez que é a sua aplicação que a inicia - veja a secção seguinte.

### Iniciar uma sessão de verificação

Quando `action_required.type` for `verification_session`, leia o bloco a partir de `result.actionRequired` e entregue-o à sua integração de verificação Didit existente:

```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 o fluxo de verificação terminar, chame `GET /v1/transactions/{transaction_id}/` (ou aguarde pelo [webhook da transação](#webhooks)) para obter o estado atualizado da transação.

### Exemplo: verificação biométrica reforçada para um levantamento de valor elevado

Este é o fluxo no seu melhor: sujeitar as transações de risco (montantes elevados, novos beneficiários, novos dispositivos) a uma nova verificação biométrica da mesma pessoa que foi verificada durante o onboarding.

A tomada de controlo de uma conta tem exatamente este aspeto: palavra-passe correta, código 2FA correto (obtido por phishing ou com troca de cartão SIM), um levantamento para uma carteira que nunca viu.
Nesse ponto, todas as defesas baseadas em credenciais já falharam.
Mas o rosto não falha: o atacante não consegue passar uma selfie com prova de vida que tem de corresponder ao rosto capturado durante o onboarding KYC do titular legítimo.

**1. Crie a regra uma única vez, na consola**

**Transactions → Rules → Create Rule**: condição `amount gte 5000`, ação **Change status → `AWAITING_USER`**, e selecione o seu fluxo **Autenticação biométrica** na ação.
Acrescente as outras condições de risco que quiser (primeira vez com uma contraparte, nova impressão digital do dispositivo, alteração do país do IP) como condições ou regras adicionais.

**2. Submeta o levantamento como habitualmente**

Nada muda no seu código de submissão. Quando a regra disparar, a resposta devolve `status: "AWAITING_USER"` e um bloco `action_required` do 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. Inicie a nova verificação**

Entregue o `session_token` (ou o `url`) à sua integração de verificação Didit existente, tal como indicado em [Iniciar uma sessão de verificação](#iniciar-uma-sessão-de-verificação).
Como a sessão é criada com o mesmo `vendor_data` do titular da transação, a Didit já tem o rosto de referência verificado do utilizador do onboarding (a selfie aprovada com prova de vida, a fotografia do passaporte armazenada no chip ou o retrato do documento).
O utilizador tira uma única selfie em tempo real; a Didit executa a prova de vida e uma correspondência facial 1:1 com essa referência guardada. Não há nova leitura do documento nem novo onboarding - apenas alguns segundos de fricção, e só nas transações que o justifiquem.

**4. Conclua conforme o resultado**

Quando a sessão terminar, a transação é reavaliada automaticamente: `APPROVED` se a nova verificação for aprovada, `DECLINED` se falhar, com um webhook `transaction.status.updated` em qualquer dos casos (`reason: remediation_completed` na cronologia da transação).
A sua aplicação obtém o resultado através de callbacks do tipo `onActionCompleted`, de `GET /v1/transactions/{transaction_id}/` ou do webhook.

Isto funciona porque o onboarding KYC, a reverificação biométrica e a decisão sobre a transação correm numa única plataforma: o rosto capturado no onboarding é a referência para a autenticação reforçada, o grafo de dispositivos liga a sessão à transação, e o motor de regras une tudo - sem código de integração entre fornecedores, sem exportação de imagens faciais e sem um contrato separado para biometria.
A reverificação é faturada como uma funcionalidade padrão de Autenticação biométrica ([\$0.10](https://docs.didit.me/pt/getting-started/pricing.md)) apenas quando efetivamente é executada.

## Web SDK

`submitTransaction` está disponível atualmente no singleton do 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);
```

O resultado é `{ transactionId, status, travelRuleStatus?, actionRequired? }`.
No payload ao nível do SDK, escreve `category`, `details` e `customProperties`; o SDK mapeia-os por si para os aliases do protocolo (`type`, `info`, `props`).

Os erros são tipados através de `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;
    }
  }
}
```

## SDKs nativos

> [!NOTE]
>
> A API nativa `submitTransaction` é disponibilizada com os SDKs iOS e Android 4.1.0 (e com as versões React Native e Flutter que a fixam).
> Ao contrário do Web SDK, os payloads nativos usam diretamente os nomes dos campos do protocolo: `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
```

Todos os SDKs também expõem `getTransaction(transactionToken, transactionId)` para reler a transação com o mesmo token - os SDKs chamam-no por si após um fluxo de ação iniciado, e pode chamá-lo diretamente em qualquer momento.

Em dispositivos móveis, o transporte da impressão digital do dispositivo já é aplicado a todas as chamadas de rede dos SDKs, pelo que as submissões de transações transportam automaticamente a mesma inteligência de dispositivo.

## Sem os SDKs

Tudo o que os SDKs fazem é HTTPS simples - nada aqui exige os SDKs.
Duas formas de integração equivalentes:

**Apenas do lado do servidor (sem tokens).** Submeta com [`POST /v3/transactions/`](https://docs.didit.me/pt/management-api/transactions/create) a partir do seu backend, usando a sua chave de API. A resposta (e cada `GET /v3/transactions/{transaction_id}/` seguinte) inclui o mesmo bloco `action_required`. Também pode enviar o contexto `device` explícito por participante, uma vez que não existe SDK que o recolha por si.

**Do lado do cliente, com a sua própria infraestrutura HTTP.** Gere um token com [`POST /v3/transactions/sdk-token/`](https://docs.didit.me/pt/management-api/transactions/sdk-token) e, depois, chame [`POST /v1/transactions/`](https://docs.didit.me/pt/management-api/transactions/submit-sdk) e [`GET /v1/transactions/{transaction_id}/`](https://docs.didit.me/pt/management-api/transactions/get-sdk) a partir do dispositivo com o cabeçalho `X-Transaction-Token` - aplicam-se as mesmas regras de vinculação ao sujeito e de derivação de IP e agente do utilizador no servidor.

Depois, trate o `action_required` por conta própria:

| `action_required.type` | O que fazer |
| - | - |
| `wallet_ownership` | Entregue o `url` ao utilizador e abra-o numa **superfície de navegador real** - um separador normal na web, `SFSafariViewController` / Chrome Custom Tabs em dispositivos móveis, ou fora de banda, através de uma ligação de e-mail ou de um código QR. Nunca o renderize dentro de uma webview incorporada: os deep links de carteiras não conseguem sair de uma. |
| `verification_session` | Envie o utilizador para o `url` - é a página de verificação alojada padrão da Didit - ou forneça o `session_token` a qualquer integração de verificação da Didit que já utilize. |

E detete a conclusão sem qualquer callback do cliente:

* **Webhooks (recomendados).** `transaction.status.updated` dispara quando a ação pendente resolve a transação, e `travel_rule.status.updated` dispara em cada transição do intercâmbio - ambos incluem informação suficiente para atualizar os seus registos sem necessidade de polling.
* **Polling.** Releia a transação (`/v3/` a partir do seu backend, ou `/v1/` com o token enquanto este for válido): `action_required` volta a `null` quando for resolvido, e `status` / `travel_rule.status` contêm o resultado.

Não existem dependências de utilização única neste desenho: o widget, a sessão de verificação e o seu backend convergem todos para o mesmo estado durável da transação, pelo que é sempre possível recuperar de um separador fechado ou de um webhook perdido ao reler a transação.

## Webhooks

O seu backend não precisa que a aplicação lhe comunique o resultado.
Quando uma transação submetida exige ação do utilizador, o [webhook de transação](https://docs.didit.me/pt/transaction-monitoring/webhooks.md) - `transaction.created` ou `transaction.status.updated` - inclui o mesmo bloco `action_required`, para que os seus sistemas saibam da ação pendente mesmo que a aplicação seja fechada antes de o fluxo terminar.
Uma ação `verification_session` vem com o estado `AWAITING_USER`, mas uma ação `wallet_ownership` não altera por si só o estado da transação, pelo que deve ler o `action_required` em vez do estado.
As transições do Travel Rule emitem adicionalmente [`travel_rule.status.updated`](https://docs.didit.me/pt/transaction-monitoring/travel-rule#webhooks).

## Próximos passos

> [card] [Submissão de transações](https://docs.didit.me/pt/transaction-monitoring/transactions.md)
>
> A API Create Transaction do lado do servidor e a referência completa do payload.

> [card] [Travel Rule](https://docs.didit.me/pt/transaction-monitoring/travel-rule.md)
>
> O intercâmbio gerido entre ordenante e beneficiário, as definições e o ciclo de vida.

> [card] [Widget de propriedade da carteira](https://docs.didit.me/pt/transaction-monitoring/wallet-ownership-widget.md)
>
> O fluxo alojado de prova de controlo por trás das ações wallet\_ownership.

> [card] [Webhooks](https://docs.didit.me/pt/transaction-monitoring/webhooks.md)
>
> transaction.created, transaction.status.updated e verificação de assinatura.

> [card] [Regras](https://docs.didit.me/pt/transaction-monitoring/rules.md)
>
> Ações de regras, incluindo as que criam sessões de verificação.

> [card] [Estados](https://docs.didit.me/pt/transaction-monitoring/statuses.md)
>
> O que AWAITING\_USER significa no ciclo de vida da transação.


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