Zum Inhalt springen
Zoi ID

Mit Zoi ID entwickeln

Zwei APIs auf einem Host, typisierte Clients in sechs Sprachen, ein gehosteter Ablauf und eine Sandbox. Der vollständige Leitfaden ist frei lesbar, Sandbox-Schlüssel gibt es mit dem Frühzugang.

Basis-URLhttps://kyc.zoi.network

Schnellstart

  1. 1

    Sitzung erstellen

    Ihr Backend ruft POST /v1/partner/sessions mit Ihrem Partnerschlüssel auf.

  2. 2

    Ablauf ausführen

    Geben Sie das Client-Token an das SDK oder öffnen Sie verificationUrl in einem Browser.

  3. 3

    Ergebnis empfangen

    Prüfen Sie die Webhook-Signatur und speichern Sie dann die Identitäts-ID und ihren Status.

shell
curl https://kyc.zoi.network/v1/partner/sessions \
  -H "Authorization: Bearer $ZOI_ID_PARTNER_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: signup-user_42" \
  -d '{ "externalUserId": "user_42", "level": "standard", "appId": "com.example.app" }'

Zwei APIs

Partner-API

Server zu Server, unter /v1/partner. Senden Sie Ihren Partnerschlüssel als Bearer-Token. Legen Sie ihn nie in eine App.

Client-API

Wird vom SDK genutzt, unter /v1/client. Es sendet ein Client-Token, das für eine Sitzung 60 Minuten gilt.

Konventionen

  • Zeiten sind Epoch-Millisekunden, in Feldern, deren Name auf At endet.
  • Daten haben das Format YYYY-MM-DD. Unbekannte Teile sind XX, zum Beispiel 1990-XX-XX.
  • POST-Anfragen, die etwas anlegen oder entscheiden, akzeptieren einen Idempotency-Key-Header. Schlüssel werden 24 Stunden gespeichert.
  • Änderungen an einer Sitzung senden die erwartete Revision. Hat sie sich geändert, bekommen Sie revision_conflict.

Sitzungsablauf

Ihr Backend erstellt die Sitzung. Das SDK führt die Schritte aus. Der Server prüft die Reihenfolge und antwortet mit step_not_allowed, wenn ein Schritt zu früh kommt.

1. Sitzung im Backend erstellen

Ein erneuter Aufruf für denselben Nutzer setzt die offene Sitzung fort und gibt ein neues Client-Token.

POST /v1/partner/sessions
POST /v1/partner/sessions
Authorization: Bearer <partner key>
Content-Type: application/json
Idempotency-Key: 6f1c2a9e-signup-42

{
  "externalUserId": "user_42",
  "level": "standard",
  "locale": "en",
  "appId": "com.example.app"
}

200 OK
{
  "sessionId": "ses_...",
  "clientToken": "...",
  "clientTokenExpiresAt": 1767225600000,
  "status": "created",
  "apiBaseUrl": "https://kyc.zoi.network",
  "resumed": false
}

2. Schritte, die das SDK ausführt

  1. 01

    Sitzung und die noch nötigen Schritte lesen.

    GET /v1/client/session

  2. 02

    Einwilligung speichern, mit der eigenen, freiwilligen Wahl zum Gesichtsvergleich.

    POST /v1/client/consent

  3. 03

    App und Gerät prüfen. Das Ergebnis fließt in die Entscheidung ein.

    POST /v1/client/attestation/challenge, POST /v1/client/attestation

  4. 04

    Dokument senden: Chip nach einem MRZ-Scan lesen oder Fotos senden.

    POST /v1/client/document/mrz-scan, POST /v1/client/document/chip/challenge, POST /v1/client/document/chip or POST /v1/client/document/photos

  5. 05

    Ein kurzes Video mit der Frontkamera aufnehmen, während die App die Kopfbewegungen zeigt.

    POST /v1/client/liveness/challenge, POST /v1/client/liveness

  6. 06

    Wohnsitzland senden, auf der erweiterten Stufe auch die Adresse.

    POST /v1/client/profile

  7. 07

    Nur mit der Anforderung Adresse: ein Dokument hochladen, dann die Adresse bestätigen. Entfällt, wenn das Ausweisdokument sie schon geliefert hat.

    POST /v1/client/address/document, POST /v1/client/address/confirm

  8. 08

    Nur mit einer Anforderung zur Mittelherkunft: Antworten speichern, Dokumente hinzufügen, wenn die Policy sie verlangt, dann den Schritt abschließen.

    POST /v1/client/funds/answers, POST /v1/client/funds/document, POST /v1/client/funds/remove, POST /v1/client/funds/complete

  9. 09

    Zwei Fragen beantworten: politisch exponierte Person und Handeln im eigenen Namen.

    POST /v1/client/declarations

  10. 10

    Mit der aktuellen Revision absenden.

    POST /v1/client/submit

Der Nutzer kann auch eine menschliche Prüfung eines automatischen Ergebnisses verlangen (POST /review-request) oder die Einwilligung widerrufen; das bricht die Sitzung ab und löscht ihre Dateien sofort (POST /withdraw).

Adressnachweis und Mittelherkunft

Fügen Sie requirements zu POST /v1/partner/sessions hinzu, um mehr als die Identität zu prüfen. address fordert einen Adressnachweis an. source_of_funds fragt, woher das Geld stammt, und source_of_wealth fragt zusätzlich nach dem Gesamtvermögen und wie es entstanden ist. Beide Werte zur Herkunft bilden zusammen einen Schritt, und beide lassen sich mit address kombinieren.

POST /v1/partner/sessions
POST /v1/partner/sessions
Authorization: Bearer <partner key>
Content-Type: application/json

{
  "externalUserId": "user_42",
  "level": "standard",
  "appId": "com.example.app",
  "requirements": ["address", "source_of_funds"],
  // Optional. Without them your partner default applies, else ours.
  "addressPolicy": { "maxAgeDays": 90, "signedOnly": false },
  "fundsPolicy": { "evidence": "optional", "currency": "EUR" }
}

Optionale Policies

Ohne Policy gilt Ihr Partner-Standard, sonst unserer. Die Policy wird beim Erstellen in die Sitzung kopiert.

  • addressPolicy: maxAgeDays (Standard 90, 1 bis 366), acceptedTypes (Standard: Versorger-, Telekom- und Bankdokumente, Steuerdokumente, Behördenschreiben und Wohnsitzbescheinigungen) und signedOnly (Standard false).
  • fundsPolicy: questions, evidence (none, optional oder required, Standard none), acceptedTypes, maxAgeDays (Standard 180), currency (ISO 4217, Standard USD) und brackets für die Spannen.
  • Eine offene, noch nicht abgesendete Sitzung mit anderen Anforderungen wird abgebrochen, und eine neue startet, wie bei einem Stufenwechsel.
  • Testen Sie jedes Ergebnis mit Sandbox-Szenarien wie address_signed_pdf, address_document_review, funds_signed_documents und funds_review.

Folgesitzungen

Prüfen Sie nur die Anforderungen für einen Nutzer, dessen Identität bei Ihnen bereits aktiv ist. Senden Sie reuseIdentity: true zusammen mit requirements.

  • Der Nutzer willigt ein und erledigt nur die neuen Schritte. Dokument, Selfie und Erklärungen werden nicht erneut abgefragt.
  • Bei Genehmigung behält die Identität ihre ID, ihr Verifizierungsdatum und ihre Gültigkeit und erhält das neue Ergebnis. Sie bekommen session.approved und identity.updated.
  • Ohne aktive Identität antwortet der Aufruf mit 409 step_not_allowed.
  • Starten Sie sie aus Ihren eigenen Risikoregeln heraus, etwa bei einer hohen Einzahlung, einem PEP-Treffer oder einem Risikoland. Ihre Regeln bleiben in Ihrem Backend, an einem Ort.
POST /v1/partner/sessions
POST /v1/partner/sessions
Authorization: Bearer <partner key>
Content-Type: application/json
Idempotency-Key: edd-user_42-2026-11

{
  "externalUserId": "user_42",
  "appId": "com.example.app",
  "requirements": ["source_of_funds", "source_of_wealth"],
  "reuseIdentity": true,
  "fundsPolicy": { "evidence": "required" }
}

// No active identity for user_42:
409 { "error": { "code": "step_not_allowed", ... } }

Ergebnisse und Scopes

Genehmigte Sitzungen enthalten address und funds, und die Identität behält jeweils das neueste Ergebnis. Die Zusammenfassungsfelder kommen mit jedem Scope. Für die Details braucht es einen Scope.

  • Scope address: die Adresszeilen. Ohne ihn erhalten Sie verified, method, evidenceLevel, documentType, documentDate, verifiedAt und country.
  • Scope financial_data: answers, documents und findings. Ohne ihn erhalten Sie kinds, evidenceLevel, reviewed, documentCount und verifiedAt. Er ist nie Teil von eligibility.
  • evidenceLevel ist high, medium, low oder none. Adressmethoden, die stärkste zuerst: wallet, id_document, signed_pdf, official_code, document_review und declared.
  • Dokumente werden nicht über die API gesendet. Mitglieder mit der Compliance-Rolle können Dokumentseiten zur Mittelherkunft im Dashboard ansehen, wenn die Umgebung den Scope financial_data hat. Jede Ansicht wird protokolliert.
GET /v1/partner/sessions/:id
"session": {
  "id": "ses_...",
  "status": "approved",
  "requirements": ["address", "source_of_funds"],
  "reusesIdentity": false,
  "address": {
    "verified": true,
    "method": "signed_pdf",
    "evidenceLevel": "high",
    "documentType": "bank_statement",
    "documentDate": "2026-10-14",
    "verifiedAt": 1767225600000,
    "country": "DE",
    // scope "address"
    "address": { "line1": "Musterstrasse 12", "city": "Berlin", "postalCode": "10115", "country": "DE" }
  },
  "funds": {
    "kinds": ["source_of_funds"],
    "evidenceLevel": "medium",
    "reviewed": true,
    "documentCount": 2,
    "verifiedAt": 1767225600000,
    // scope "financial_data"
    "answers": {
      "employmentStatus": "employed",
      "income": { "min": 50000, "max": 100000, "currency": "EUR" },
      "sources": ["salary", "savings"],
      ...
    },
    "documents": [
      { "documentType": "payslip", "documentDate": "2026-10-31", "amount": 5200, "currency": "EUR",
        "signature": "valid", "signedByKnownIssuer": true, "nameMatch": "match", "findings": [], ... },
      ...
    ],
    "findings": []
  }
}

Web-Ablauf

Kein SDK? Schicken Sie den Nutzer auf unsere gehostete Seite. Sie nutzt dieselbe API, dieselben Prüfungen und dieselbe Prüfwarteschlange.

1. Den Link holen

POST /v1/partner/sessions gibt verificationUrl zurück. Geben Sie redirectUrl mit, um den Nutzer am Ende zurückzuschicken. Die URL muss mit einem Ihrer registrierten Redirect-Origins beginnen.

POST /v1/partner/sessions
POST /v1/partner/sessions
Authorization: Bearer <partner key>
Content-Type: application/json

{
  "externalUserId": "user_42",
  "level": "standard",
  "locale": "en",
  "appId": "com.example.app",
  "redirectUrl": "https://example.com/kyc/done"
}

200 OK
{
  "sessionId": "ses_...",
  "clientToken": "...",
  "verificationUrl": "https://kyc.zoi.network/verify/#token=...&lang=en",
  ...
}
  • Das Client-Token steht im Fragment der URL, nach #. Browser senden das Fragment nie an einen Server, daher steht das Token nicht in Logs oder im Referer-Header. Die Seite liest es, entfernt es aus der Adressleiste und hält es nur im Speicher.
  • Fügen Sie lang zum Fragment hinzu, um eine der 19 Sprachen zu wählen. Ohne den Wert nutzt die Seite die Sprache des Browsers.
  • Beim Neuladen geht das Token verloren. Erstellen Sie in Ihrem Backend einen neuen Link für dieselbe Sitzung.
  • Die Seite sendet channel: "web" mit der Einwilligung. Browser können keine Ausweischips lesen, daher nutzen Web-Sitzungen Dokumentfotos oder eine digitale Ausweis-Wallet. Sie überspringen die Geräteprüfung, und über Dokumentfotos aus dem Web entscheidet immer ein geschulter Prüfer.
  • Auf einem Computer zeigt die Seite einen QR-Code, damit der Nutzer auf dem Handy weitermachen kann.

2. Wenn der Nutzer fertig ist

Wenn der Ablauf endet (gesendet, abgebrochen oder ein Endstatus), sendet die Seite diese Nachricht an jedes dieser Objekte, das existiert: window.ReactNativeWebView, window.webkit.messageHandlers.zoiId, window.ZoiIdAndroid und window.opener. Danach öffnet sie redirectUrl mit den Parametern zoiIdSession und zoiIdStatus.

Vertrauen Sie nie der Nachricht oder den Redirect-Parametern. Vertrauen Sie nur Ihrem Webhook oder GET /v1/partner/sessions/:id.

postMessage
// Sent to each bridge that exists:
//   window.ReactNativeWebView.postMessage(JSON string)
//   window.webkit.messageHandlers.zoiId.postMessage(object)
//   window.ZoiIdAndroid.postMessage(JSON string)
//   window.opener.postMessage(object, "<origin of your redirectUrl>")
//     (popups only, and only when the session has a redirectUrl)
{
  "source": "zoi-id",
  "type": "finished",        // or "cancelled"
  "sessionId": "ses_...",
  "status": "processing"     // the session status at that moment
}

// Then, if redirectUrl was set (the page uses the one stored with the
// session, never one from the address bar):
https://example.com/kyc/done?zoiIdSession=ses_...&zoiIdStatus=processing

3. Webview-Einstellungen

Android WebView

Schalten Sie javaScriptEnabled und domStorageEnabled ein und setzen Sie mediaPlaybackRequiresUserGesture auf false. Erlauben Sie in einem WebChromeClient bei onPermissionRequest die Ressource RESOURCE_VIDEO_CAPTURE, nachdem Ihre App die CAMERA-Berechtigung hat. Fügen Sie eine Dateiauswahl für den Foto-Ersatzweg hinzu.

Kotlin
webView.settings.javaScriptEnabled = true
webView.settings.domStorageEnabled = true
webView.settings.mediaPlaybackRequiresUserGesture = false
webView.webChromeClient = object : WebChromeClient() {
  override fun onPermissionRequest(request: PermissionRequest) {
    // Only after your app holds Manifest.permission.CAMERA.
    if (request.resources.contains(PermissionRequest.RESOURCE_VIDEO_CAPTURE)) {
      request.grant(arrayOf(PermissionRequest.RESOURCE_VIDEO_CAPTURE))
    } else {
      request.deny()
    }
  }
  override fun onShowFileChooser(
    view: WebView, callback: ValueCallback<Array<Uri>>, params: FileChooserParams,
  ): Boolean {
    // Open a picker and pass the result to callback (photo fallback).
    return true
  }
}

iOS WKWebView

iOS 14.3 oder neuer. Setzen Sie allowsInlineMediaPlayback auf true und tragen Sie NSCameraUsageDescription in Ihrer App ein. Ab iOS 15 können Sie die Kamera-Berechtigung im UI-Delegate entscheiden.

Swift
let config = WKWebViewConfiguration()
config.allowsInlineMediaPlayback = true
let webView = WKWebView(frame: .zero, configuration: config)
webView.uiDelegate = self // iOS 15+: decide camera access below

func webView(_ webView: WKWebView,
             requestMediaCapturePermissionFor origin: WKSecurityOrigin,
             initiatedByFrame frame: WKFrameInfo,
             type: WKMediaCaptureType,
             decisionHandler: @escaping (WKPermissionDecision) -> Void) {
  decisionHandler(origin.host == "kyc.zoi.network" ? .grant : .deny)
}
// Info.plist: NSCameraUsageDescription

React Native WebView

Setzen Sie mediaCapturePermissionGrantType="grant", allowsInlineMediaPlayback und javaScriptEnabled. Ihre App braucht die Kamera-Berechtigung.

TSX
<WebView
  source={{ uri: verificationUrl }}
  javaScriptEnabled
  allowsInlineMediaPlayback
  mediaCapturePermissionGrantType="grant"
  onMessage={(event) => {
    const message = JSON.parse(event.nativeEvent.data);
    if (message.source === "zoi-id") {
      // Close the webview, then ask your backend for the real status.
    }
  }}
/>

iframes werden nicht unterstützt, wegen der Kamera-Berechtigungen und wegen Clickjacking. Nutzen Sie einen Browser-Tab, ein Popup oder eine Webview.

Status

Sitzung

created
Die Sitzung existiert, noch nichts hochgeladen.
in_progress
Einwilligung gegeben, Schritte werden hochgeladen.
processing
Abgesendet, Prüfungen laufen.
review
Wartet auf eine geschulte Prüferin oder einen Prüfer.
approved
Endgültig. Die Identität ist verifiziert.
rejected
Endgültig. Siehe die Grundcodes.
resubmit
Der Nutzer muss die Schritte in requiredSteps wiederholen.
expired
Keine endgültige Entscheidung rechtzeitig.
cancelled
Abgebrochen durch den Partner, den Nutzer oder eine Löschung.

Identität

active
Nutzbar.
suspended
Gesperrt, zum Beispiel nach einem neuen Sanktionstreffer.
expired
Das Dokument oder der Prüfzeitraum ist abgelaufen. Eine neue Sitzung prüft erneut.
revoked
Endgültig.

SDK

Das React Native (Expo) SDK bietet Ihnen einen typisierten API-Client, NFC-Chiplesen und Geräteprüfungen. Es ist headless, Ihre App behält also die volle Kontrolle über ihre Bildschirme und ihre Kamera. Für Ihre Server nutzen Sie die typisierten Clients für TypeScript, Python, Go, PHP, Java und Ruby.

@zoi/id-sdk
import { ZoiIdClient, nextStep } from "@zoi/id-sdk";

// clientToken and apiBaseUrl come from your backend (POST /v1/partner/sessions).
const client = new ZoiIdClient({ baseUrl: apiBaseUrl, clientToken });

const session = await client.getSession();
switch (nextStep(session)) {
  case "consent":   /* show the notice and the biometric choice */ break;
  case "document":  /* chip read or photos */ break;
  case "liveness":  /* record the video */ break;
  case "address":   /* upload a document, then confirm the address */ break;
  case "funds":     /* answers, then documents when the policy asks */ break;
  case "submit":    await client.submit(session.revision); break;
  case "done":      /* wait for your backend to get the webhook */ break;
}

Webhooks

Wir senden einen Webhook, wenn sich eine Sitzung oder Identität ändert. Prüfen Sie immer die Signatur, bevor Sie dem Inhalt trauen.

  • Jeder Webhook hat einen Zoi-Id-Signature-Header: t=unix seconds,v1=hex.
  • v1 ist der HMAC-SHA256 des Textes t.raw body mit Ihrem Webhook-Secret. Nutzen Sie den rohen Body, vor jedem JSON-Parsing.
  • Während ein Secret gewechselt wird, enthält der Header ein v1 pro aktivem Secret. Akzeptieren Sie den Webhook, wenn eines davon passt.
  • Lehnen Sie ihn ab, wenn t mehr als 300 Sekunden von jetzt abweicht.
  • Fehlgeschlagene Zustellungen werden 72 Stunden lang wiederholt. Jeder Versuch wird mit einem neuen t neu signiert.
  • Identitäten haben eine revision. Wenden Sie ein Event nur an, wenn seine Revision neuer ist als die gespeicherte.
identity.suspended
POST <your webhook URL>
Zoi-Id-Signature: t=1767225600,v1=5f2b...e9

{
  "v": 1,
  "eventId": "evt_...",
  "type": "identity.suspended",
  "createdAt": 1767225600000,
  "object": "identity",
  "externalUserId": "user_42",
  "identity": {
    "identityId": "idn_...",
    "revision": 7,
    "status": "suspended",
    "level": "standard",
    "verifiedAt": 1764547200000,
    "validUntil": 1859155200000,
    "ageOver18": true,
    "captureMethod": "nfc"
  }
}

Event-Typen

  • session.processing
  • session.review
  • session.approved
  • session.rejected
  • session.resubmit
  • session.expired
  • session.cancelled
  • identity.updated
  • identity.suspended
  • identity.reinstated
  • identity.expired
  • identity.revoked

Signatur in TypeScript prüfen

verify.ts
import { createHmac, timingSafeEqual } from "node:crypto";

const TOLERANCE_SECONDS = 300;

export function verifyZoiIdSignature(
  rawBody: string,
  header: string | null,
  secret: string,
  nowSeconds = Math.floor(Date.now() / 1000),
): boolean {
  if (!header) return false;

  let t: string | undefined;
  const signatures: Buffer[] = [];
  for (const part of header.split(",")) {
    const [key, value] = part.trim().split("=");
    if (!value) continue;
    if (key === "t") t = value;
    if (key === "v1" && /^[0-9a-f]+$/i.test(value)) signatures.push(Buffer.from(value, "hex"));
  }
  if (!t || !/^\d+$/.test(t) || signatures.length === 0) return false;
  if (Math.abs(nowSeconds - Number(t)) > TOLERANCE_SECONDS) return false;

  const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest();
  return signatures.some((sig) => sig.length === expected.length && timingSafeEqual(sig, expected));
}

// Example handler: read the raw body first, parse JSON only after the check.
export async function POST(request: Request) {
  const rawBody = await request.text();
  const ok = verifyZoiIdSignature(rawBody, request.headers.get("Zoi-Id-Signature"), process.env.ZOI_ID_WEBHOOK_SECRET!);
  if (!ok) return new Response("bad signature", { status: 400 });

  const event = JSON.parse(rawBody);
  // Store event.eventId to skip duplicates, then apply it if the revision is newer.
  return new Response(null, { status: 204 });
}

Signatur in Go prüfen

verify.go
package zoiid

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"strconv"
	"strings"
	"time"
)

const toleranceSeconds = 300

// VerifySignature checks the Zoi-Id-Signature header against the raw body.
func VerifySignature(rawBody []byte, header, secret string, now time.Time) bool {
	var t string
	var sigs [][]byte
	for _, part := range strings.Split(header, ",") {
		key, value, ok := strings.Cut(strings.TrimSpace(part), "=")
		if !ok || value == "" {
			continue
		}
		switch key {
		case "t":
			t = value
		case "v1":
			if b, err := hex.DecodeString(value); err == nil {
				sigs = append(sigs, b)
			}
		}
	}
	ts, err := strconv.ParseInt(t, 10, 64)
	if err != nil || len(sigs) == 0 {
		return false
	}
	if d := now.Unix() - ts; d > toleranceSeconds || d < -toleranceSeconds {
		return false
	}

	mac := hmac.New(sha256.New, []byte(secret))
	mac.Write([]byte(t + "."))
	mac.Write(rawBody)
	expected := mac.Sum(nil)
	for _, sig := range sigs {
		if hmac.Equal(sig, expected) {
			return true
		}
	}
	return false
}

Event-Feed

GET /v1/partner/events liefert alle Events der letzten 30 Tage, das älteste zuerst. Geben Sie after mit dem letzten nextCursor an, um die nächste Seite zu lesen. Nutzen Sie es nach einem längeren Ausfall oder um zu prüfen, dass Ihnen nichts fehlt.

GET /v1/partner/events
GET /v1/partner/events?after=<nextCursor>&limit=100
Authorization: Bearer <partner key>

200 OK
{
  "items": [ /* webhook payloads, oldest first */ ],
  "nextCursor": "..."   // null when there is nothing more
}

Betrugsmeldungen

Sagen Sie uns, wenn sich eine entschiedene Sitzung als Betrug herausstellt, zum Beispiel nach einer Rückbuchung. Meldungen fließen in unsere Messung falscher Genehmigungen ein. Sie ändern weder die Sitzung noch die Identität noch das Ergebnis des Nutzers.

  • Scope sessions:write. Nur für entschiedene Sitzungen (genehmigt oder abgelehnt). Vor einer Entscheidung antwortet der Aufruf mit 409.
  • Ein leerer Body meldet confirmed_fraud mit der Kategorie other. status kann auch suspected_fraud sein, oder cleared, um Ihre eigene Meldung zurückzunehmen.
  • Kategorien: forged_document, impostor, deepfake_or_replay, synthetic_identity, account_takeover und other. Keine Kategorie bei cleared.
  • note ist Freitext für unsere Analysten, höchstens 1000 Zeichen. Tragen Sie dort nie Namen, Dokumentnummern oder andere personenbezogene Daten ein.
  • Eine neue Meldung antwortet mit 201. Dieselbe Meldung erneut liefert die gespeicherte mit 200. Senden Sie einen Idempotency-Key, um gefahrlos zu wiederholen. Höchstens 20 Meldungen pro Sitzung.
  • Dieselbe Meldung ist auf der Sitzungsseite im Dashboard nur einen Klick entfernt.
POST /v1/partner/sessions/:id/fraud-reports
POST /v1/partner/sessions/ses_.../fraud-reports
Authorization: Bearer <partner key>
Content-Type: application/json
Idempotency-Key: chargeback-8812

{
  "status": "confirmed_fraud",
  "category": "impostor",
  "note": "Chargeback on the first deposit"
}

201 Created
{
  "id": "flb_...",
  "sessionId": "ses_...",
  "status": "confirmed_fraud",
  "category": "impostor",
  "noteKept": true,
  "createdAt": 1767225600000
}

Löschung

DELETE /v1/partner/users/:externalUserId löscht die Daten eines Nutzers, wenn er sein Konto schließt oder die Löschung verlangt. Die Antwort zeigt, was jetzt gelöscht wurde und was behalten wird, warum und bis wann.

DELETE /v1/partner/users/:externalUserId
DELETE /v1/partner/users/user_42
Authorization: Bearer <partner key>

200 OK
{
  "deleted": ["images", "chip_files", "video", "personal_data", "address"],
  "retained": [
    { "category": "dedup_keys", "reason": "fraud_prevention", "until": 1924992000000 },
    { "category": "sanctions_screening_records", "reason": "legal_obligation", "until": 2082758400000 }
  ],
  "backupsPurgedWithinDays": 30
}

Fehler

Jeder Fehler hat dieselbe Form und pro Code einen festen HTTP-Status.

error
{
  "error": {
    "code": "step_not_allowed",
    "message": "This step is not allowed now.",
    "requestId": "req_..."
  }
}
CodeHTTPBedeutung
bad_request400Die Anfrage passt nicht zum Vertrag.
unauthorized401Schlüssel oder Token fehlt oder ist falsch.
token_expired401Das Client-Token ist abgelaufen. Erstellen Sie im Backend ein neues.
forbidden403Der Schlüssel darf das nicht.
not_found404Keine solche Sitzung oder kein solcher Nutzer.
session_closed409Die Sitzung ist abgeschlossen. Keine weiteren Uploads.
step_not_allowed409Falsche Reihenfolge, oder der Schritt wird gerade nicht gebraucht.
revision_conflict409Die Sitzungsrevision hat sich geändert. Lesen Sie sie neu.
idempotency_conflict409Derselbe Idempotency-Key wurde mit einer anderen Anfrage genutzt.
challenge_expired410Die Challenge ist abgelaufen oder schon benutzt. Fordern Sie eine neue an.
payload_too_large413Der Upload ist zu groß.
unsupported_media_type415Der Dateityp wird nicht angenommen.
rate_limited429Zu viele Anfragen. Versuchen Sie es später erneut.
internal500Bei uns ist etwas schiefgelaufen.
dependency_unavailable503Ein Dienst, den wir brauchen, ist nicht erreichbar. Versuchen Sie es später erneut.

Grundcodes

Sitzungen enthalten reasonCodes, wenn sie erneut eingereicht werden müssen oder abgelehnt sind. Zeigen Sie für jeden Code Ihre eigene übersetzte Meldung. Zeigen Sie Nutzern nie den rohen Code.

  • consent_missing
  • consent_withdrawn
  • attestation_failed
  • document_expired
  • document_unreadable
  • document_not_supported
  • document_suspected_tampering
  • document_number_visible
  • chip_verification_failed
  • chip_proof_missing
  • chip_data_mismatch
  • mrz_invalid
  • photo_quality
  • face_not_found
  • face_mismatch
  • liveness_failed
  • underage
  • location_not_supported
  • residence_not_supported
  • sanctions_match
  • pep_review
  • duplicate_identity
  • possible_duplicate
  • declaration_requires_review
  • user_requested_review
  • reviewer_other
  • address_unreadable
  • address_document_too_old
  • address_document_not_accepted
  • address_name_mismatch
  • address_signature_required
  • funds_document_unreadable
  • funds_document_too_old
  • funds_document_not_accepted
  • funds_more_evidence
  • funds_evidence_rejected