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

# SDK-Transaktionsübermittlung – Clientseitiges KYT

> Übermitteln Sie mit Didit Transaktionen aus Ihrer App per gescopten Token, mit automatischer Geräteanalyse und Wallet-Nachweis.

Die Didit SDKs können [überwachte Transaktionen](https://docs.didit.me/de/transaction-monitoring/transactions.md) direkt vom Gerät des Endbenutzers übermitteln, einschließlich [Travel Rule](https://docs.didit.me/de/transaction-monitoring/travel-rule.md)-Transfers und Krypto-Screening.
Ihr Backend erstellt ein kurzlebiges, eingeschränktes Token, und das SDK übernimmt den Rest.
Es fügt die Geräteintelligenz automatisch hinzu. Wenn ein Travel-Rule-Transfer einen Nachweis der Wallet-Kontrolle erfordert, startet das SDK diesen Ablauf sofort automatisch.
Eine erforderliche Verifizierungssitzung wird niemals automatisch gestartet. Sie wird in der Antwort zurückgegeben, damit Ihre App sie mit Ihrer eigenen Didit-Verifizierungsintegration starten kann. Siehe unten [Erforderliche Benutzeraktionen](#erforderliche-benutzeraktionen).

## Übersicht

Die clientseitige Übermittlung beruht auf drei Garantien:

* **Fälschungssichere Zuordnung des Subjekts.** Das Token ist zum Zeitpunkt der Erstellung auf einen `vendor_data`-Wert (Ihre Benutzer-ID) beschränkt. Die Subjektidentität jeder Transaktion, die damit übermittelt wird, wird serverseitig aus dieser Zuordnung erzwungen. Ein manipulierter Client kann so nicht im Namen eines anderen Benutzers übermitteln, und der Payload muss nie Zugangsdaten enthalten.
* **Automatische Geräteintelligenz.** Das SDK erfasst einen Geräte-Fingerprint und sendet ihn zusammen mit der Übermittlung. Didit leitet die Adresse und den User-Agent serverseitig ab. Der kombinierte Gerätekontext wird dem Subjekt der Transaktion ohne zusätzliche Bibliothek, ohne separates Gerätetoken und ohne Aufwand auf Ihrer Seite zugeordnet. Er unterstützt gerätebasierte [Regeln](https://docs.didit.me/de/transaction-monitoring/rules.md) sowie die Korrelation verwandter Vorgänge nach Gerät über Sitzungen und Transaktionen hinweg.
* **Automatisch gestarteter Nachweis der Wallet-Kontrolle.** Wenn die erstellte Transaktion verlangt, dass der Endbenutzer die Kontrolle über eine Wallet nachweist, enthält die Antwort einen `action_required`-Block vom Typ `wallet_ownership`. Das SDK öffnet diesen Ablauf sofort und aktualisiert die Transaktion anschließend, sobald er abgeschlossen ist. Sie müssen dafür keine Polling-Schleifen und keine Linkzustellung selbst implementieren. Eine erforderliche `verification_session`-Aktion ist anders: Sie wird niemals automatisch gestartet, sondern im selben Block zurückgegeben, damit Ihre App sie mit ihrer eigenen Didit-Verifizierungsintegration starten kann.

> [!NOTE]
>
> Die serverseitige [`POST /v3/transactions/`](https://docs.didit.me/de/transaction-monitoring/transactions) bleibt die richtige Wahl, wenn kein Endbenutzer anwesend ist: bei Backoffice-Transfers, Batch-Importen oder Ereignissen, die von Ihren eigenen Systemen gemeldet werden.
> Beide Wege erstellen dieselben Transaktionsdatensätze, führen dieselben Regeln aus und senden dieselben [Webhooks](https://docs.didit.me/de/transaction-monitoring/webhooks.md).

## Ablauf

**1. Transaktions-Token erstellen**

Ihr Backend ruft `POST /v3/transactions/sdk-token/` mit Ihrem API-Schlüssel und der `vendor_data` des Benutzers auf und übergibt den zurückgegebenen `sdk_token` an Ihre App.

**2. Vom Gerät übermitteln**

Die App ruft `submitTransaction` im SDK auf. Das SDK sendet den Payload mit dem Header `X-Transaction-Token` und der automatischen Geräteanreicherung an `POST /v1/transactions/`.

**3. Erforderliche Aktion verarbeiten**

Wenn `action_required.type` `wallet_ownership` ist, startet das SDK das Widget automatisch und aktualisiert danach die Transaktion über `GET /v1/transactions/{transaction_id}/`.
Ist der Typ `verification_session`, startet das SDK nichts. Lesen Sie `action_required` aus und starten Sie Ihren eigenen Verifizierungsablauf mit dessen `session_token` (im Web mit `url`).

## Transaktions-Token erstellen

Erstellen Sie Token in Ihrem Backend mit `POST /v3/transactions/sdk-token/`. Die Authentifizierung mit dem API-Schlüssel ist dieselbe wie bei den übrigen Transaktionsendpunkten unter `/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"
}
```

| Feld | Typ | Beschreibung |
| - | - | - |
| `vendor_data` | string | **Erforderlich.** Ihre interne Kennung für den Endbenutzer, auf den dieses Token beschränkt ist. Die Subjektidentität jeder mit dem Token übermittelten Transaktion wird aus diesem Wert erzwungen. |
| `ttl_seconds` | integer | Lebensdauer des Tokens in Sekunden. Standardwert ist `900` (15 Minuten), maximal `86400` (24 Stunden). |
| `max_uses` | integer | Optional. Maximale Anzahl erfolgreicher Übermittlungen mit diesem Token. Weglassen für unbegrenzte Nutzung innerhalb der TTL. |

Die camelCase-Aliase (`vendorData`, `ttlSeconds`, `maxUses`) werden ebenfalls akzeptiert, wie in der übrigen `/v3/`-API.

> [!WARNING]
>
> **Liefern Sie Ihren API-Schlüssel niemals in einer App aus.**
> Der API-Schlüssel bleibt in Ihrem Backend und erstellt Token. Nur der kurzlebige `sdk_token` gelangt auf das Gerät.
> Erstellen Sie für jede Sitzung des Benutzers ein neues Token, statt langlebige Token wiederzuverwenden. Begrenzen Sie `max_uses` eng, wenn die Anzahl der erwarteten Übermittlungen bekannt ist.

## Vom Gerät aus einreichen

Die SDKs senden Anfragen an `POST /v1/transactions/` und übergeben das Token im Header `X-Transaction-Token`.
Das Wire-Payload verwendet camelCase. Die Methoden `submitTransaction` erstellen es für Sie. Sie arbeiten nur dann direkt damit, wenn Sie den Endpoint über Ihren eigenen HTTP-Client aufrufen:

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

Jedes Feld auf oberster Ebene entspricht dem jeweiligen Gegenstück in [`POST /v3/transactions/`](https://docs.didit.me/de/transaction-monitoring/transactions):

| Wire-Feld | Entsprechung in `/v3/` | Hinweise |
| - | - | - |
| `txnId` | `transaction_id` | **Erforderlich.** Ihre eindeutige Kennung für die Transaktion. |
| `txnDate` | `transaction_at` | ISO-8601-Zeitstempel; Standardwert ist die aktuelle Zeit. |
| `zoneId` | `time_zone` | IANA-Zeitzonen-ID, z. B. `Europe/Madrid`. |
| `type` | `transaction_category` | **Erforderlich.** `finance`, `kyc`, `travelRule`, `userPlatformEvent`, `gamblingBet` und die übrigen Kategorien. |
| `info` | `transaction_details` | **Erforderlich.** `direction`, `amount`, `currency` (erforderlich), außerdem `currencyType`, `amountInDefaultCurrency`, `defaultCurrencyCode`, `paymentDetails`, `paymentTxnId`, `type` (Aktionstyp), `cryptoParams`. |
| `subject`, `counterparty` | `subject`, `counterparty` | Teilnehmer: `type` (Entitätstyp), `externalUserId`, `fullName`, `firstName`, `lastName`, `dob`, `address`, `institutionInfo`, `device`, `paymentMethod` (`type`, `accountId`, `issuingCountry`). Ein `paymentMethod.type` von `unhosted_wallet` bei der Gegenpartei kennzeichnet das Ziel als selbstverwahrte Wallet - siehe [unten](#selbstverwahrte-wallet-deklarieren). |
| `props` | `custom_properties` | Benutzerdefinierte Schlüssel-Wert-Paare, in Regeln als `custom_values.<key>` verwendbar. |
| `travelRule` | `travel_rule_details` | `status`, `protocol`, `required`, `obligationsCount`, `originatorData`, `beneficiaryData`, `metadata`; `status` ist immer erforderlich, sobald Sie diesen Block senden, andernfalls schlägt die Anfrage mit `400` fehl. Ist Travel Rule aktiviert, ignoriert Didit jeden von Ihnen gesendeten `status` und steuert den Austausch selbst. |
| `includeCryptoScreening` | `include_crypto_screening` | Transaktionsspezifische Überschreibung für das Krypto-Screening. |

Die Antwort entspricht dem Transaktionsdetail im snake\_case-Format der `/v3/`-Endpoints und enthält zusätzlich den Block `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"
  }
}
```

### Selbstverwahrte Wallet deklarieren

Das Beispiel oben deklariert das Ziel als selbstverwahrte Wallet des Benutzers: Der `paymentMethod.type` der Gegenpartei ist `unhosted_wallet`.
Diese Deklaration verändert den Ablauf des [Travel-Rule-Austauschs](https://docs.didit.me/de/transaction-monitoring/travel-rule.md#deklarierte-selbstverwahrte-wallets):

* Didit überspringt das Routing an die VASP der Gegenpartei vollständig, denn hinter einer selbstverwahrten Wallet steht keine VASP. Die Übertragung wird direkt im Status `UNCONFIRMED_OWNERSHIP` angelegt.
* Ist `auto_wallet_verification` in Ihren [Travel-Rule-Einstellungen](https://docs.didit.me/de/transaction-monitoring/travel-rule.md#einrichtung) aktiviert (Standardwert), wird automatisch eine Sitzung für das [Widget zur Wallet-Eigentumsprüfung](https://docs.didit.me/de/transaction-monitoring/wallet-ownership-widget.md) erstellt und wie in der obigen Antwort in `action_required` zurückgegeben.
* Das SDK startet das Widget automatisch. Sobald der Benutzer nachweist, dass er die Wallet kontrolliert, führt die Übertragung den Namensabgleich durch und wechselt zu `COMPLETED`. Stimmt der Name nicht mit dem im Adressbuch der Wallet verzeichneten Inhaber überein, wechselt sie zu `COUNTERPARTY_MISMATCHED_DATA`.

Ohne diese Deklaration versucht Didit stattdessen die [Routen](https://docs.didit.me/de/transaction-monitoring/travel-rule.md#überblick) der Reihe nach und greift nur dann auf den Eigentumsnachweis zurück, wenn keine VASP der Gegenpartei gefunden wird.
Deklarieren Sie die Wallet immer dann, wenn Ihr Produkt bereits weiß, dass das Auszahlungsziel die eigene Wallet des Benutzers ist. So entfällt der fehlgeschlagene Routing-Versuch, und der Benutzer gelangt sofort in den Nachweisablauf.

### Verbindliche Vorgaben zum Subjekt

Drei Dinge werden immer durch das Token entschieden, nie durch das Payload:

* Die Anwendung, unter der die Transaktion angelegt wird.
* Die Identität des Subjekts: `subject.externalUserId` im Payload wird ignoriert und durch das `vendor_data` des Tokens ersetzt.
* Der Gerätekontext des Subjekts (siehe unten). Bei Konflikten mit allem, was in `subject.device` übergeben wird, setzen serverseitig abgeleitete Felder sich durch.

Ein `401` von diesem Endpoint bedeutet, dass das Token abgelehnt wurde: fehlend, unbekannt, widerrufen, über seinem `max_uses` oder abgelaufen.
Antworten bei abgelaufenen Tokens enthalten im Fehlerdetail immer das Wort `expired`. Daran erkennen die SDKs, ob ein Fehler `expired_token` oder `invalid_token` lautet.

### Transaktion abrufen

`GET /v1/transactions/{transaction_id}/` liefert dasselbe Detail und verwendet dieselbe Authentifizierung über `X-Transaction-Token`.
Die SDKs rufen diesen Endpoint automatisch auf, nachdem ein gestarteter Aktionsablauf abgeschlossen ist oder sein Zeitfenster geschlossen wurde. Der endgültige Status hängt daher nie von einem einzelnen Ereignis oder einer einzelnen Benachrichtigung ab.
Sie können `status` und `action_required` selbst jederzeit erneut prüfen, solange das Token gültig ist. Von Ihrem Backend aus liefert [`GET /v3/transactions/{transaction_id}/`](https://docs.didit.me/de/transaction-monitoring/transactions) denselben Datensatz.

## Automatische Geräteanalyse

Jede Einreichung enthält Geräteinformationen, ohne dass Sie dafür etwas integrieren müssen:

* Das SDK erfasst einen datenschutzfreundlichen Geräte-Fingerprint (das Schema `didit-fp-v2` mit Plattform- und Navigator-Signalen, einer persistenten Geräte-ID, einem Bot-Score und einem zusammengesetzten Hash) und sendet ihn mit der Anfrage.
* Didit leitet Adresse, User-Agent und accept-language serverseitig aus der Verbindung selbst ab. Sie lassen sich deshalb nicht über das Payload fälschen.

Das zusammengeführte Ergebnis wird im Gerätekontext des Subjekt-Teilnehmers normalisiert und per Upsert in den Gerätegraphen eingefügt bzw. dort aktualisiert, demselben Graphen, in den auch KYC-Sitzungen einfließen.
Dadurch lassen sich Transaktionen über Ihre gesamte Benutzerbasis hinweg nach Gerät verknüpfen („nach Gerät verknüpft“). Geräte- und Netzwerksignale fließen in [Regeln](https://docs.didit.me/de/transaction-monitoring/rules.md) und die [IP-Anreicherung](https://docs.didit.me/de/transaction-monitoring/ip-enrichment.md) ein und erscheinen im Transaktionsdetail in der [Console](https://docs.didit.me/de/transaction-monitoring/console.md).

Serverseitig übermittelte `/v3/`-Transaktionen können für jeden Teilnehmer weiterhin explizite `device_context`-Objekte übergeben. Über den SDK-Weg erhalten Sie dieselben Daten in umfassenderer Form ohne zusätzlichen Aufwand.

## Erforderliche Benutzeraktionen

Bei manchen Transaktionen muss der Endbenutzer etwas tun, bevor sie abgeschlossen werden können: Eine Regel kann eine Verifizierungssitzung verlangen, oder eine Travel-Rule-Überweisung kann einen Nachweis erfordern, dass die Zielwallet tatsächlich dem Benutzer gehört.
Beide Fälle erscheinen an derselben Stelle: im Block `action_required` der Antwort auf die Übermittlung (und bei jedem späteren Abruf der Transaktion).

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

| Typ | Wann er erscheint | Was der Benutzer tut |
| - | - | - |
| `verification_session` | Eine [Regelaktion](https://docs.didit.me/de/transaction-monitoring/rules.md) hat eine zusätzliche Benutzerverifizierung erforderlich gemacht, und die Transaktion wechselte in `AWAITING_USER`. | Der Benutzer schließt eine gehostete Verifizierungssitzung ab (zum Beispiel eine erneute biometrische Prüfung), die von Ihrer App gestartet wird – siehe unten [Start einer Verifizierungssitzung](#start-einer-verifizierungssitzung). |
| `wallet_ownership` | Eine Travel-Rule-Überweisung benötigt einen Nachweis der Wallet-Kontrolle durch den Endbenutzer: Das Ziel wurde als [selbstverwahrte Wallet deklariert](#selbstverwahrte-wallet-deklarieren) (`UNCONFIRMED_OWNERSHIP`), oder eine ausgehende Überweisung fand keinen VASP der Gegenpartei, und `auto_wallet_verification` ist in Ihren [Travel-Rule-Einstellungen](https://docs.didit.me/de/transaction-monitoring/travel-rule.md#einrichtung) aktiviert. Didit erzeugt automatisch eine [Sitzung des Widgets für den Wallet-Eigentumsnachweis](https://docs.didit.me/de/transaction-monitoring/wallet-ownership-widget.md), die an die Transaktion gebunden ist. | Der Benutzer weist die Kontrolle über die Wallet nach, durch Nachrichtensignatur (bei einer unterstützten Chain), Screenshot oder Selbstauskunft, soweit Ihre Travel-Rule-Einstellungen dies zulassen. Niemals jedoch per Satoshi-Test bei einer automatisch erzeugten Sitzung. Das SDK startet diese Aktion für Sie automatisch. |

Wenn beide Fälle auf dieselbe Transaktion zutreffen könnten, hat `wallet_ownership` Vorrang, weil der fehlende Eigentumsnachweis den Travel-Rule-Austausch selbst blockiert.
`action_required` wird zu `null`, sobald die ausstehende Aktion abgeschlossen ist.
Das bestehende Feld `remediation` bleibt aus Gründen der Abwärtskompatibilität erhalten; `action_required` ist der maßgebliche Block.

### Regeln für den automatischen Start

`autoLaunchAction` (Standard `true`) startet nur die Aktion `wallet_ownership` automatisch:

* **Web**: Sie wird im eigenen Overlay-Modal des SDK geöffnet. Der Abschluss wird über eine Nachrichtenübermittlung mit validiertem Ursprung sowie eine begrenzte Abfrage der Transaktion erkannt, niemals nur über ein einzelnes Ereignis.
* **iOS / Android** (sowie die Wrapper für React Native und Flutter, die an die nativen SDKs delegieren): Sie wird als **native Oberfläche für den Wallet-Eigentumsnachweis** des SDK (SwiftUI / Jetpack Compose) angezeigt, wenn `useNativeWalletOwnership` auf `true` steht (Standard). Bei einem Fallback wird `SFSafariViewController` bzw. ein Chrome Custom Tab verwendet, und zuletzt der Standardbrowser. Nachdem der Ablauf geschlossen wurde oder die App wieder in den Vordergrund kommt, liest das SDK die Transaktion automatisch erneut ein.

Eine `verification_session`-Aktion wird niemals automatisch gestartet. Sie kommt immer in `action_required` zurück, damit Ihre App sie mit der eigenen Didit-Verifizierungsintegration startet.

> [!WARNING]
>
> Das Widget für den Wallet-Eigentumsnachweis darf niemals in einer eingebetteten mobilen WebView angezeigt werden. Wallet-Deep-Links können diese nicht verlassen, und der Nachweisablauf endet in einer Sackgasse.
> Die SDKs verwenden beim automatischen Start bereits die korrekte Browser-Oberfläche. Behalten Sie diese Regel bei, wenn Sie die `url` selbst verarbeiten.
> Details finden Sie im [Leitfaden zum Widget für den Wallet-Eigentumsnachweis](https://docs.didit.me/de/transaction-monitoring/wallet-ownership-widget.md#die-zurückgegebene-url-öffnen).

Nachdem der automatisch gestartete Ablauf für den Wallet-Eigentumsnachweis des SDK abgeschlossen oder geschlossen wurde, liest das SDK die Transaktion erneut ein und übergibt das aktualisierte Ergebnis an Ihren Callback `onActionCompleted`.
Eine `verification_session`-Aktion gehört nicht zu diesem Zyklus, da Ihre App sie startet. Siehe dazu den nächsten Abschnitt.

### Start einer Verifizierungssitzung

Wenn `action_required.type` den Wert `verification_session` hat, lesen Sie den Block aus `result.actionRequired` und übergeben ihn an Ihre bestehende Didit-Verifizierungsintegration:

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

Sobald Ihr Verifizierungsablauf abgeschlossen ist, rufen Sie `GET /v1/transactions/{transaction_id}/` auf (oder warten Sie auf den [Transaktions-Webhook](#webhooks)), um den aktualisierten Status der Transaktion abzurufen.

### Beispiel: Biometrische Nachprüfung bei einer Auszahlung hoher Beträge

Dies ist der ideale Anwendungsfall: Riskante Transaktionen (hohe Beträge, neue Begünstigte, neue Geräte) werden durch eine biometrische Nachprüfung derselben Person abgesichert, die Sie beim Onboarding verifiziert haben.

Eine Kontoübernahme sieht genau so aus: korrektes Passwort, korrekter 2FA-Code (durch Phishing oder SIM-Swap erlangt) und eine Auszahlung an eine Wallet, an die Sie noch nie ausgezahlt haben.
Bis dahin sind alle auf Zugangsdaten basierenden Schutzmaßnahmen bereits gescheitert.
Ein Gesicht jedoch nicht: Der Angreifer kann keine Liveness-geprüfte Selfie-Aufnahme vorlegen, die mit dem Gesicht übereinstimmen muss, das beim KYC-Onboarding des rechtmäßigen Inhabers erfasst wurde.

**1. Regel einmalig in der Konsole anlegen**

**Transactions → Rules → Create Rule**: Bedingung `amount gte 5000`, Aktion **Change status → `AWAITING_USER`**. Wählen Sie bei der Aktion Ihren Workflow **Biometrische Authentifizierung** aus.
Fügen Sie beliebige weitere Risikobedingungen hinzu, etwa eine erstmalige Gegenpartei, einen neuen Geräte-Fingerprint oder einen Wechsel des IP-Landes, als zusätzliche Bedingungen oder zusätzliche Regeln.

**2. Auszahlung wie gewohnt einreichen**

Ihr Code für die Übermittlung ändert sich nicht. Wenn die Regel auslöst, kommt die Antwort mit `status: "AWAITING_USER"` und einem Block `action_required` vom Typ `verification_session` zurück:

```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. Nachprüfung starten**

Übergeben Sie den `session_token` (oder die `url`) an Ihre bestehende Didit-Verifizierungsintegration, genau wie unter [Start einer Verifizierungssitzung](#start-einer-verifizierungssitzung) beschrieben.
Da die Sitzung für dieselbe `vendor_data` wie das Subjekt der Transaktion erstellt wird, besitzt Didit bereits das verifizierte Referenzgesicht des Benutzers aus dem Onboarding: das genehmigte Liveness-Selfie, das Passfoto aus dem Chip oder das Dokumentporträt.
Der Benutzer macht ein einziges Live-Selfie. Didit führt eine Liveness-Prüfung und einen 1:1-Gesichtsabgleich gegen diese gespeicherte Referenz durch. Eine erneute Dokumentenaufnahme oder ein erneutes Onboarding ist nicht nötig. Das bedeutet einige Sekunden Aufwand, und zwar nur bei den Transaktionen, die es verdienen.

**4. Auf das Ergebnis reagieren**

Wenn die Sitzung abgeschlossen ist, wird die Transaktion automatisch neu bewertet: `APPROVED`, wenn die Nachprüfung bestanden wurde, `DECLINED`, wenn sie fehlgeschlagen ist. In beiden Fällen wird ein Webhook `transaction.status.updated` gesendet (`reason: remediation_completed` in der Zeitleiste der Transaktion).
Ihre App erhält das Ergebnis über Callbacks im Stil von `onActionCompleted`, über `GET /v1/transactions/{transaction_id}/` oder über den Webhook.

Das funktioniert, weil KYC-Onboarding, die erneute biometrische Prüfung und die Transaktionsentscheidung auf einer einzigen Plattform laufen: Das beim Onboarding erfasste Gesicht dient als Referenz für die Step-up-Prüfung, der Geräte-Graph verknüpft die Sitzung mit der Transaktion, und die Regel-Engine bringt beides zusammen – ohne Drittanbieter-Anbindung, ohne Export von Gesichtsbildern und ohne separaten Biometrievertrag.
Die erneute Prüfung wird nur dann als Standardfunktion „Biometrische Authentifizierung“ abgerechnet ([\$0.10](https://docs.didit.me/de/getting-started/pricing.md)), wenn sie tatsächlich ausgeführt wird.

## Web-SDK

`submitTransaction` ist im Web-SDK-Singleton bereits verfügbar:

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

Das Ergebnis ist `{ transactionId, status, travelRuleStatus?, actionRequired? }`.
In der Payload auf SDK-Ebene geben Sie `category`, `details` und `customProperties` an; das SDK ordnet sie für Sie den Wire-Aliasnamen (`type`, `info`, `props`) zu.

Fehler werden über `DiditTransactionError` typisiert:

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

## Native SDKs

> [!NOTE]
>
> Die native API `submitTransaction` wird mit dem iOS- und Android-SDK 4.1.0 ausgeliefert (sowie mit den React-Native- und Flutter-Releases, die auf diese Version gebunden sind).
> Anders als beim Web-SDK verwenden die nativen Payloads die Feldnamen des Wire-Formats direkt: `type`, `info` und `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
```

Jedes SDK stellt außerdem `getTransaction(transactionToken, transactionId)` bereit, um die Transaktion mit demselben Token erneut abzurufen. Die SDKs rufen sie nach einem gestarteten Aktionsablauf für Sie auf, und Sie können sie jederzeit selbst aufrufen.

Auf Mobilgeräten wird der Geräte-Fingerprint-Transport bereits auf alle Netzwerkaufrufe des SDK angewendet, sodass Transaktionsübermittlungen automatisch dieselben Geräteinformationen übertragen.

## Ohne die SDKs

Alles, was die SDKs tun, läuft über einfaches HTTPS – nichts davon setzt die SDKs voraus.
Es gibt zwei gleichwertige Integrationsformen:

**Nur serverseitig (ohne Tokens).** Übermitteln Sie die Transaktion mit [`POST /v3/transactions/`](https://docs.didit.me/de/management-api/transactions/create) von Ihrem Backend aus mit Ihrem API-Schlüssel. Die Antwort (und jeder spätere Aufruf von `GET /v3/transactions/{transaction_id}/`) enthält denselben `action_required`-Block. Da kein SDK vorhanden ist, das die Geräteinformationen für Sie erfasst, können Sie außerdem pro Teilnehmer einen expliziten `device`-Kontext übergeben.

**Clientseitig mit eigenem HTTP-Stack.** Erstellen Sie ein Token mit [`POST /v3/transactions/sdk-token/`](https://docs.didit.me/de/management-api/transactions/sdk-token) und rufen Sie dann vom Gerät aus [`POST /v1/transactions/`](https://docs.didit.me/de/management-api/transactions/submit-sdk) und [`GET /v1/transactions/{transaction_id}/`](https://docs.didit.me/de/management-api/transactions/get-sdk) mit dem Header `X-Transaction-Token` auf. Dieselbe Subjektbindung und die serverseitige Ableitung von IP-Adresse und User-Agent gelten auch hier.

Behandeln Sie anschließend `action_required` selbst:

| `action_required.type` | Was zu tun ist |
| - | - |
| `wallet_ownership` | Stellen Sie `url` dem Benutzer bereit und öffnen Sie sie in einer **echten Browser-Oberfläche** – ein normaler Tab im Web, `SFSafariViewController` / Chrome Custom Tabs auf Mobilgeräten oder außerbandig über einen E-Mail-Link oder QR-Code. Rendern Sie sie niemals innerhalb einer eingebetteten WebView: Wallet-Deep-Links können daraus nicht ausbrechen. |
| `verification_session` | Leiten Sie den Benutzer zu `url` weiter – das ist die standardmäßige gehostete Didit-Verifizierungsseite – oder übergeben Sie `session_token` an jede bereits betriebene Didit-Verifizierungsintegration. |

Und erkennen Sie den Abschluss ohne Client-Callback:

* **Webhooks (empfohlen).** `transaction.status.updated` wird ausgelöst, wenn die ausstehende Aktion die Transaktion auflöst, und `travel_rule.status.updated` wird bei jedem Übergang im Austausch ausgelöst. Beide enthalten genug Informationen, um Ihre Datensätze ohne Polling zu aktualisieren.
* **Polling.** Lesen Sie die Transaktion erneut aus (`/v3/` von Ihrem Backend oder `/v1/` mit dem Token, solange es gültig ist): `action_required` wird nach der Auflösung wieder `null`, und `status` bzw. `travel_rule.status` enthalten das Ergebnis.

In diesem Design gibt es keine einmaligen Abhängigkeiten: Das Widget, die Verifizierungssitzung und Ihr Backend laufen alle auf denselben dauerhaften Transaktionsstatus zusammen. Ein geschlossener Tab oder ein verpasster Webhook lässt sich daher immer wiederherstellen, indem Sie die Transaktion erneut lesen.

## Webhooks

Ihr Backend ist nicht auf die App angewiesen, um Rückmeldungen zu erhalten.
Wenn eine übermittelte Transaktion eine Aktion des Benutzers erfordert, enthält der [Transaktions-Webhook](https://docs.didit.me/de/transaction-monitoring/webhooks.md) – `transaction.created` oder `transaction.status.updated` – denselben `action_required`-Block. So erfahren Ihre Systeme von der ausstehenden Aktion, auch wenn die App geschlossen wird, bevor der Ablauf abgeschlossen ist.
Eine Aktion `verification_session` geht mit dem Status `AWAITING_USER` einher, eine Aktion `wallet_ownership` ändert den Transaktionsstatus jedoch nicht von selbst. Lesen Sie daher `action_required` statt des Status.
Statuswechsel der Travel Rule lösen zusätzlich [`travel_rule.status.updated`](https://docs.didit.me/de/transaction-monitoring/travel-rule#webhooks) aus.

## Nächste Schritte

> [card] [Transaktionen übermitteln](https://docs.didit.me/de/transaction-monitoring/transactions.md)
>
> Die serverseitige Create-Transaction-API und die vollständige Payload-Referenz.

> [card] [Travel Rule](https://docs.didit.me/de/transaction-monitoring/travel-rule.md)
>
> Der verwaltete Austausch zwischen Auftraggeber und Begünstigtem, Einstellungen und Lebenszyklus.

> [card] [Widget für den Wallet-Eigentumsnachweis](https://docs.didit.me/de/transaction-monitoring/wallet-ownership-widget.md)
>
> Der gehostete Ablauf zum Kontrollnachweis hinter den wallet\_ownership-Aktionen.

> [card] [Webhooks](https://docs.didit.me/de/transaction-monitoring/webhooks.md)
>
> transaction.created, transaction.status.updated und Signaturprüfung.

> [card] [Regeln](https://docs.didit.me/de/transaction-monitoring/rules.md)
>
> Regelaktionen, einschließlich der Aktionen, die Verifizierungssitzungen erstellen.

> [card] [Status](https://docs.didit.me/de/transaction-monitoring/statuses.md)
>
> Was AWAITING\_USER im Lebenszyklus der Transaktion bedeutet.


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