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

# Envio de transações via SDK - KYT no lado do cliente

> Envie transações monitoradas do app com o SDK da Didit e token restrito: inteligência de dispositivo, vínculo antifraude e propriedade de carteira.

Os SDKs da Didit podem enviar [transações monitoradas](https://docs.didit.me/pt-br/transaction-monitoring/transactions.md) diretamente do dispositivo do usuário final, incluindo transferências sujeitas à [Travel Rule](https://docs.didit.me/pt-br/transaction-monitoring/travel-rule.md) e triagem de cripto.
Seu backend emite um token de curta duração e escopo restrito, e o SDK cuida do resto.
Ele anexa a inteligência de dispositivo automaticamente e, quando uma transferência sujeita à Travel Rule exige prova de controle da carteira, inicia esse fluxo automaticamente na hora.
Uma sessão de verificação obrigatória nunca é iniciada automaticamente: ela é retornada na resposta para que seu app a inicie com a sua própria integração de verificação da Didit. Veja [Ações obrigatórias do usuário](#ações-exigidas-do-usuário) abaixo.

## Visão geral

O envio pelo cliente se apoia em três garantias:

* **Vinculação do titular à prova de falsificação.** No momento da emissão, o token fica restrito a um único `vendor_data` (o ID do seu usuário). A identidade do titular de cada transação enviada com ele é imposta no servidor a partir dessa vinculação. Assim, um cliente adulterado não consegue enviar em nome de outro usuário, e o payload nunca precisa carregar credenciais.
* **Inteligência de dispositivo automática.** O SDK coleta uma impressão digital do dispositivo e a envia junto com a transação, e a Didit obtém o endereço IP e o user agent no servidor. O contexto de dispositivo combinado é anexado ao titular da transação sem biblioteca extra, sem token de dispositivo separado e sem nenhuma integração adicional do seu lado. Ele alimenta [regras](https://docs.didit.me/pt-br/transaction-monitoring/rules.md) baseadas em dispositivo e a correlação por dispositivo entre sessões e transações.
* **Prova de propriedade da carteira iniciada automaticamente.** Quando a transação criada exige que o usuário final prove o controle de uma carteira, a resposta traz um bloco `action_required` do tipo `wallet_ownership`. O SDK abre esse fluxo imediatamente e atualiza a transação quando ele termina, sem que você precise criar loops de polling ou a entrega de links. Uma ação `verification_session` obrigatória é diferente: ela nunca é iniciada automaticamente e volta no mesmo bloco para que seu app a inicie com sua própria integração de verificação da Didit.

> [!NOTE]
>
> O [`POST /v3/transactions/`](https://docs.didit.me/pt-br/transaction-monitoring/transactions) no servidor continua sendo a escolha certa quando não há um usuário final presente: transferências de back-office, importações em lote ou eventos reportados pelos seus próprios sistemas.
> Os dois caminhos criam os mesmos registros de transação, executam as mesmas regras e emitem os mesmos [webhooks](https://docs.didit.me/pt-br/transaction-monitoring/webhooks.md).

## Fluxo

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

Seu backend chama `POST /v3/transactions/sdk-token/` com a sua chave de API e o `vendor_data` do usuário, e entrega o `sdk_token` retornado ao seu app.

**2. Envie pelo dispositivo**

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

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

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

## Emissão de um token de transação

Emita tokens a partir do seu backend com `POST /v3/transactions/sdk-token/`, usando a mesma autenticação por chave de API dos demais 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.** Seu identificador interno do usuário final ao qual este token está vinculado. Toda transação enviada com o token tem a identidade do titular definida por esse valor. |
| `ttl_seconds` | integer | Tempo de vida do token em segundos. O padrão é `900` (15 minutos); o máximo é `86400` (24 horas). |
| `max_uses` | integer | Opcional. Número máximo de envios bem-sucedidos permitidos com este token. Omita para usos ilimitados dentro do TTL. |

Aliases em camelCase (`vendorData`, `ttlSeconds`, `maxUses`) também são aceitos, como no restante da API `/v3/`.

> [!WARNING]
>
> **Nunca inclua sua chave de API em um app.**
> A chave de API fica no seu backend e emite os tokens; apenas o `sdk_token` de curta duração vai para o dispositivo.
> Emita um token novo por sessão de usuário em vez de reutilizar tokens de longa duração, e mantenha `max_uses` limitado quando o número de envios esperados for conhecido.

## Envio a partir do dispositivo

Os SDKs fazem um `POST /v1/transactions/` com o token no cabeçalho `X-Transaction-Token`.
O payload enviado usa camelCase. Os métodos `submitTransaction` o montam para você, então você só precisa lidar com ele diretamente 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 nível superior corresponde ao seu equivalente em [`POST /v3/transactions/`](https://docs.didit.me/pt-br/transaction-monitoring/transactions):

| Campo enviado | Equivalente em `/v3/` | Observações |
| - | - | - |
| `txnId` | `transaction_id` | **Obrigatório.** Seu identificador único da transação. |
| `txnDate` | `transaction_at` | Timestamp ISO-8601. O padrão é o momento atual. |
| `zoneId` | `time_zone` | ID de fuso horário IANA, por exemplo `Europe/Madrid`. |
| `type` | `transaction_category` | **Obrigatório.** `finance`, `kyc`, `travelRule`, `userPlatformEvent`, `gamblingBet` e as demais categorias. |
| `info` | `transaction_details` | **Obrigatório.** `direction`, `amount`, `currency` (obrigatória), além de `currencyType`, `amountInDefaultCurrency`, `defaultCurrencyCode`, `paymentDetails`, `paymentTxnId`, `type` (tipo de ação) e `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` da contraparte igual a `unhosted_wallet` declara o destino como autocustodiado. Veja [abaixo](#como-declarar-uma-carteira-autocustodiada). |
| `props` | `custom_properties` | Pares chave-valor personalizados, que podem ser usados em regras como `custom_values.<key>`. |
| `travelRule` | `travel_rule_details` | `status`, `protocol`, `required`, `obligationsCount`, `originatorData`, `beneficiaryData`, `metadata`. O `status` é obrigatório sempre que você envia este bloco, caso contrário a requisição falha com `400`. Quando a Travel Rule está ativada, a Didit ignora qualquer `status` que você enviar e conduz a troca por conta própria. |
| `includeCryptoScreening` | `include_crypto_screening` | Substituição, por transação, da configuração de análise de cripto. |

A resposta traz o mesmo detalhe da transação em snake\_case dos endpoints `/v3/`, além 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"
  }
}
```

### Como declarar uma carteira autocustodiada

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

* A Didit ignora totalmente o roteamento para o VASP da contraparte, já que não há VASP por trás de uma carteira autocustodiada, e cria a transferência diretamente em `UNCONFIRMED_OWNERSHIP`.
* Quando `auto_wallet_verification` está ativado nas suas [configurações da Travel Rule](https://docs.didit.me/pt-br/transaction-monitoring/travel-rule.md#configuração) (o padrão), uma sessão do [widget de propriedade da carteira](https://docs.didit.me/pt-br/transaction-monitoring/wallet-ownership-widget.md) é criada automaticamente e retornada em `action_required`, exatamente como na resposta acima.
* O SDK abre o widget automaticamente. Assim que o usuário comprova o controle da carteira, a transferência executa a verificação de nome e avança para `COMPLETED`, ou para `COUNTERPARTY_MISMATCHED_DATA` quando o nome não corresponde ao titular registrado no catálogo de endereços da carteira.

Sem a declaração, a Didit tenta os [canais](https://docs.didit.me/pt-br/transaction-monitoring/travel-rule.md#visão-geral) em ordem e só recorre ao fluxo de propriedade se nenhum VASP da contraparte for encontrado.
Declare a carteira sempre que o seu produto já souber que o destino do saque é a carteira do próprio usuário. Isso evita a tentativa de roteamento que falharia e leva o usuário imediatamente ao fluxo de comprovação.

### Controle do titular

Três coisas são sempre definidas pelo token, nunca pelo payload:

* A aplicação na qual a transação é criada.
* A identidade do titular: o `subject.externalUserId` do payload é ignorado e substituído pelo `vendor_data` do token.
* O contexto do dispositivo do titular (veja abaixo). Os campos derivados pelo servidor prevalecem em caso de conflito com qualquer dado enviado em `subject.device`.

Um `401` deste endpoint significa que o token foi rejeitado: ausente, desconhecido, revogado, acima do seu `max_uses` ou expirado.
As respostas de token expirado sempre contêm a palavra `expired` no detalhe do erro. É assim que os SDKs distinguem erros `expired_token` de erros `invalid_token`.

### Como consultar a transação novamente

`GET /v1/transactions/{transaction_id}/` retorna o mesmo detalhe com a mesma autenticação `X-Transaction-Token`.
Os SDKs o chamam automaticamente depois que um fluxo de ação iniciado é concluído ou que sua janela é fechada, de modo que o estado final nunca depende de um único evento ou notificação.
Você também pode usá-lo para verificar novamente `status` e `action_required` a qualquer momento enquanto o token for válido. No seu backend, [`GET /v3/transactions/{transaction_id}/`](https://docs.didit.me/pt-br/transaction-monitoring/transactions) retorna o mesmo registro.

## Inteligência de dispositivo automática

Todo envio inclui inteligência de dispositivo sem nenhum trabalho de integração da sua parte:

* O SDK coleta uma impressão digital do dispositivo que preserva a privacidade (o esquema `didit-fp-v2`: sinais da plataforma e do navegador, um ID persistente do dispositivo, uma pontuação de bot e um hash composto) e a envia com a requisição.
* A Didit obtém o endereço IP, o user agent e o accept-language no servidor, a partir da própria conexão, de modo que eles não podem ser falsificados pelo payload.

O resultado combinado é normalizado no contexto de dispositivo do participante titular e inserido ou atualizado no grafo de dispositivos, que também é alimentado pelas sessões de KYC.
Isso permite correlacionar transações por dispositivo em toda a sua base de usuários ("relacionadas por dispositivo"), alimenta [regras](https://docs.didit.me/pt-br/transaction-monitoring/rules.md) e o [enriquecimento de IP](https://docs.didit.me/pt-br/transaction-monitoring/ip-enrichment.md) com sinais de dispositivo e de rede, e aparece no detalhe da transação no [Console](https://docs.didit.me/pt-br/transaction-monitoring/console.md).

As transações `/v3/` enviadas pelo servidor podem continuar passando objetos `device_context` explícitos por participante. Ao usar o SDK, você recebe uma versão mais robusta dos mesmos dados sem nenhum esforço adicional.

## Ações exigidas do usuário

Algumas transações exigem que o usuário final faça algo antes de serem liquidadas: uma regra pode exigir uma sessão de verificação, ou uma transferência sujeita à Travel Rule pode exigir a comprovação de que a carteira de destino pertence de fato ao usuário.
Os dois casos aparecem no mesmo lugar, o bloco `action_required` da resposta do envio (e de toda leitura posterior 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 usuário faz |
| - | - | - |
| `verification_session` | Uma [ação de regra](https://docs.didit.me/pt-br/transaction-monitoring/rules.md) exigiu verificação adicional do usuário e a transação passou para `AWAITING_USER`. | Conclui uma sessão de verificação hospedada (por exemplo, uma nova checagem biométrica), iniciada pela sua aplicação. Veja [Iniciando uma sessão de verificação](#iniciando-uma-sessão-de-verificação) abaixo. |
| `wallet_ownership` | Uma transferência sujeita à Travel Rule precisa que o usuário final comprove o controle da carteira, seja porque o destino foi [declarado como carteira autocustodiada](#como-declarar-uma-carteira-autocustodiada) (`UNCONFIRMED_OWNERSHIP`), seja porque uma transferência de saída não encontrou VASP de contraparte, e `auto_wallet_verification` está ativado nas suas [configurações da Travel Rule](https://docs.didit.me/pt-br/transaction-monitoring/travel-rule.md#configuração). A Didit cria automaticamente uma sessão do [widget de propriedade de carteira](https://docs.didit.me/pt-br/transaction-monitoring/wallet-ownership-widget.md) vinculada à transação. | Comprova o controle da carteira por assinatura de mensagem (em uma blockchain compatível), captura de tela ou autodeclaração, conforme permitido pelas suas configurações da Travel Rule. Nunca por teste Satoshi em uma sessão criada automaticamente. O SDK abre essa sessão automaticamente para você. |

Se os dois tipos puderem se aplicar à mesma transação, `wallet_ownership` tem prioridade, porque a falta da comprovação de propriedade bloqueia a própria troca de dados da Travel Rule.
`action_required` passa a ser `null` assim que a ação pendente é resolvida.
O campo `remediation` existente é mantido para compatibilidade com versões anteriores; `action_required` é o bloco canônico.

### Contrato de abertura automática

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

* **Web**: aberta no modal sobreposto do próprio SDK, com a conclusão detectada por mensagens com validação de origem mais uma consulta periódica limitada da transação (nunca dependendo de um único evento).
* **iOS / Android** (e os wrappers React Native e Flutter, que delegam aos SDKs nativos): apresentada como a **experiência nativa de propriedade de carteira** do SDK (SwiftUI / Jetpack Compose) quando `useNativeWalletOwnership` é `true` (o padrão), com fallback para `SFSafariViewController` / uma Chrome Custom Tab e, por último, para o navegador padrão. Depois que o fluxo é fechado ou a aplicação volta ao primeiro plano, o SDK consulta novamente a transação automaticamente.

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

> [!WARNING]
>
> O widget de propriedade de carteira nunca deve ser renderizado dentro de uma webview móvel incorporada: os deep links de carteira não conseguem sair dela e o fluxo de comprovação fica sem saída.
> Os SDKs já usam a interface de navegador correta ao abrir automaticamente; mantenha essa regra se você mesmo tratar a `url`.
> Consulte o [guia do widget de propriedade de carteira](https://docs.didit.me/pt-br/transaction-monitoring/wallet-ownership-widget.md#abra-a-url-retornada) para mais detalhes.

Depois que o fluxo de propriedade de carteira aberto automaticamente pelo SDK é concluído ou fechado, o SDK consulta novamente a transação e entrega o resultado atualizado ao seu callback `onActionCompleted`.
Uma ação `verification_session` não faz parte desse ciclo, já que é a sua aplicação que a inicia. Veja a próxima seção.

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

Quando `action_required.type` for `verification_session`, leia o bloco em `result.actionRequired` e entregue-o à sua integração de verificação da Didit já 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 seu fluxo de verificação terminar, chame `GET /v1/transactions/{transaction_id}/` (ou aguarde o [webhook de transação](#webhooks)) para obter o status atualizado da transação.

### Exemplo: biometria adicional para um saque de alto valor

Este é o uso ideal desse fluxo: condicionar transações de risco (valores altos, novos beneficiários, novos dispositivos) a uma nova checagem biométrica da mesma pessoa que você verificou no onboarding.

Uma tomada de controle de conta se parece exatamente com isto: senha correta, código 2FA correto (obtido por phishing ou SIM swap) e um saque para uma carteira que você nunca viu.
Nesse ponto, todas as defesas baseadas em credenciais já falharam.
A verificação facial não: o invasor não consegue passar por uma selfie com prova de vida que precisa corresponder ao rosto capturado durante o onboarding KYC do titular legítimo.

**1. Crie a regra uma única vez, no console**

**Transactions → Rules → Create Rule**: condição `amount gte 5000`, ação **Change status → `AWAITING_USER`**, e selecione seu fluxo de **Biometric Authentication** na ação.
Adicione as outras condições de risco que quiser (contraparte nova, nova impressão digital do dispositivo, mudança de país do IP) como condições ou regras adicionais.

**2. Envie o saque normalmente**

Nada muda no seu código de envio. Quando a regra é acionada, a resposta retorna com `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 checagem**

Entregue o `session_token` (ou a `url`) à sua integração de verificação da Didit já existente, exatamente como em [Iniciando uma sessão de verificação](#iniciando-uma-sessão-de-verificação).
Como a sessão é criada para o mesmo `vendor_data` do titular da transação, a Didit já tem o rosto de referência verificado do usuário desde o onboarding: a selfie aprovada na prova de vida, a foto do chip do passaporte ou o retrato do documento.
O usuário tira uma única selfie ao vivo; a Didit executa a prova de vida mais uma correspondência facial 1:1 com essa referência armazenada. Sem nova leitura de documento, sem refazer o onboarding: alguns segundos de fricção, apenas nas transações que merecem.

**4. Liquide conforme o resultado**

Quando a sessão é concluída, a transação é reavaliada automaticamente: `APPROVED` se a nova checagem foi aprovada, `DECLINED` se falhou, com um webhook `transaction.status.updated` em ambos os casos (`reason: remediation_completed` na linha do tempo da transação).
Sua aplicação obtém o resultado por callbacks semelhantes a `onActionCompleted`, por `GET /v1/transactions/{transaction_id}/` ou pelo webhook.

Isso funciona porque o onboarding de KYC, a reverificação biométrica e a decisão sobre a transação rodam em uma única plataforma: o rosto capturado no onboarding serve de referência para a verificação adicional, o grafo de dispositivos vincula a sessão à transação e o motor de regras une tudo isso. Não há integração entre fornecedores, nem exportação de imagens faciais, nem contrato separado de biometria.
A reverificação é cobrada como um recurso padrão de Autenticação biométrica ([\$0.10](https://docs.didit.me/pt-br/getting-started/pricing.md)) somente quando de fato é executada.

## SDK web

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

O resultado é `{ transactionId, status, travelRuleStatus?, actionRequired? }`.
No payload do SDK, você informa `category`, `details` e `customProperties`, e o SDK os mapeia para os aliases usados na transmissão (`type`, `info`, `props`) por você.

Os erros são tipados por meio 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` é distribuída com os SDKs de iOS e Android 4.1.0 (e com as versões de React Native e Flutter que fixam essa versão).
> Ao contrário do SDK web, os payloads nativos usam diretamente os nomes de campo usados na transmissão: `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 consultar novamente a transação com o mesmo token. Os SDKs fazem essa chamada por você depois de um fluxo de ação iniciado, e você também pode chamá-la a qualquer momento.

No mobile, o transporte da impressão digital do dispositivo já é aplicado a todas as chamadas de rede dos SDKs, então os envios de transações levam automaticamente a mesma inteligência de dispositivo.

## Sem os SDKs

Tudo o que os SDKs fazem é HTTPS simples, e nada aqui exige o uso deles.
Há dois formatos de integração equivalentes:

**Somente no servidor (sem nenhum token).** Envie com [`POST /v3/transactions/`](https://docs.didit.me/pt-br/management-api/transactions/create) a partir do seu backend usando sua chave de API. A resposta (e cada `GET /v3/transactions/{transaction_id}/` subsequente) traz o mesmo bloco `action_required`. Você também pode passar um contexto `device` explícito por participante, já que não há SDK para coletá-lo por você.

**No cliente, com sua própria pilha HTTP.** Gere um token com [`POST /v3/transactions/sdk-token/`](https://docs.didit.me/pt-br/management-api/transactions/sdk-token) e depois chame [`POST /v1/transactions/`](https://docs.didit.me/pt-br/management-api/transactions/submit-sdk) e [`GET /v1/transactions/{transaction_id}/`](https://docs.didit.me/pt-br/management-api/transactions/get-sdk) a partir do dispositivo com o cabeçalho `X-Transaction-Token`. Aplicam-se a mesma vinculação ao titular e a mesma derivação de IP/user-agent no servidor.

Em seguida, trate `action_required` você mesmo:

| `action_required.type` | O que fazer |
| - | - |
| `wallet_ownership` | Entregue a `url` ao usuário e abra-a em um **navegador de verdade**: uma aba normal na web, `SFSafariViewController` / Chrome Custom Tabs no mobile ou fora do app, por um link por e-mail ou um QR code. Nunca a renderize dentro de uma webview incorporada: os deep links de carteira não conseguem sair dela. |
| `verification_session` | Envie o usuário para a `url`, que é a página de verificação hospedada padrão da Didit, ou passe o `session_token` para qualquer integração de verificação da Didit que você já utilize. |

E detecte a conclusão sem nenhum callback no cliente:

* **Webhooks (recomendado).** `transaction.status.updated` é disparado quando a ação pendente conclui a transação, e `travel_rule.status.updated` é disparado a cada transição de intercâmbio. Ambos trazem informações suficientes para atualizar seus registros sem polling.
* **Polling.** Releia a transação (`/v3/` a partir do seu backend ou `/v1/` com o token enquanto ele for válido): `action_required` volta a ser `null` após a resolução, e `status` / `travel_rule.status` trazem o resultado.

Não há dependências de uso único neste design: o widget, a sessão de verificação e o seu backend convergem para o mesmo estado durável da transação, então uma aba fechada ou um webhook perdido sempre pode ser recuperado relendo a transação.

## Webhooks

Seu backend não depende de um retorno do app.
Quando uma transação enviada exige uma ação do usuário, o [webhook de transação](https://docs.didit.me/pt-br/transaction-monitoring/webhooks.md), `transaction.created` ou `transaction.status.updated`, inclui o mesmo bloco `action_required`, para que seus sistemas saibam da ação pendente mesmo se o app for fechado antes de o fluxo terminar.
Uma ação `verification_session` vem com o status `AWAITING_USER`, mas uma ação `wallet_ownership` não altera o status da transação por si só, então leia `action_required` em vez do status.
As transições da Travel Rule também emitem [`travel_rule.status.updated`](https://docs.didit.me/pt-br/transaction-monitoring/travel-rule#webhooks).

## Próximos passos

> [card] [Envio de transações](https://docs.didit.me/pt-br/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-br/transaction-monitoring/travel-rule.md)
>
> A troca gerenciada entre remetente e beneficiário, as configurações e o ciclo de vida.

> [card] [Widget de propriedade de carteira](https://docs.didit.me/pt-br/transaction-monitoring/wallet-ownership-widget.md)
>
> O fluxo hospedado de comprovação de controle por trás das ações wallet\_ownership.

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

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

> [card] [Status](https://docs.didit.me/pt-br/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.