Chuyển đến nội dung
Zoi ID

Xây dựng với Zoi ID

Hai API trên cùng một host, client có kiểu cho sáu ngôn ngữ, một luồng lưu trữ sẵn và sandbox. Toàn bộ hướng dẫn đã mở để đọc, còn khóa sandbox đi kèm truy cập sớm.

Base URLhttps://kyc.zoi.network

Bắt đầu nhanh

  1. 1

    Tạo phiên

    Backend của bạn gọi POST /v1/partner/sessions bằng khóa đối tác.

  2. 2

    Chạy luồng

    Chuyển token client cho SDK, hoặc mở verificationUrl trong trình duyệt.

  3. 3

    Nhận kết quả

    Kiểm tra chữ ký webhook, rồi lưu mã danh tính và trạng thái của nó.

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

Hai API

API đối tác

Máy chủ với máy chủ, dưới /v1/partner. Gửi khóa đối tác dưới dạng token Bearer. Không bao giờ đặt nó trong ứng dụng.

API client

Do SDK sử dụng, dưới /v1/client. SDK gửi một token client có hiệu lực cho một phiên trong 60 phút.

Quy ước

  • Thời gian tính bằng mili giây epoch, trong các trường có tên kết thúc bằng At.
  • Ngày tháng theo dạng YYYY-MM-DD. Phần chưa biết là XX, ví dụ 1990-XX-XX.
  • Các yêu cầu POST tạo hoặc quyết định điều gì đó nhận header Idempotency-Key. Khóa được giữ 24 giờ.
  • Thay đổi trên một phiên gửi kèm phiên bản mong đợi. Nếu phiên bản đã đổi, bạn nhận revision_conflict.

Luồng phiên

Backend của bạn tạo phiên. SDK chạy các bước. Máy chủ kiểm tra thứ tự và trả về step_not_allowed khi một bước đến quá sớm.

1. Tạo phiên từ backend

Gọi lại cho cùng người dùng sẽ tiếp tục phiên đang mở và cấp token client mới.

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. Các bước SDK chạy

  1. 01

    Đọc phiên và các bước còn cần.

    GET /v1/client/session

  2. 02

    Ghi nhận sự đồng ý, kèm lựa chọn riêng, không bắt buộc, về việc so khuôn mặt.

    POST /v1/client/consent

  3. 03

    Kiểm tra ứng dụng và thiết bị. Kết quả là một phần của quyết định.

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

  4. 04

    Gửi giấy tờ: đọc chip sau khi quét MRZ, hoặc gửi ảnh.

    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

    Quay một video ngắn bằng camera trước trong khi ứng dụng hiển thị các cử động đầu.

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

  6. 06

    Gửi quốc gia cư trú, và địa chỉ ở cấp nâng cao.

    POST /v1/client/profile

  7. 07

    Chỉ khi có yêu cầu địa chỉ: tải lên giấy tờ, rồi xác nhận địa chỉ. Bỏ qua khi giấy tờ tùy thân đã cung cấp địa chỉ.

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

  8. 08

    Chỉ khi có yêu cầu về nguồn tiền: lưu câu trả lời, thêm giấy tờ khi chính sách yêu cầu, rồi hoàn tất bước.

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

  9. 09

    Trả lời hai câu hỏi: có phải người có ảnh hưởng chính trị không, và có tự hành động cho mình không.

    POST /v1/client/declarations

  10. 10

    Gửi đi với phiên bản hiện tại.

    POST /v1/client/submit

Người dùng cũng có thể yêu cầu một người xem lại kết quả tự động (POST /review-request) hoặc rút lại sự đồng ý, việc này hủy phiên và xóa ngay các tệp (POST /withdraw).

Xác minh địa chỉ và nguồn tiền

Thêm requirements vào POST /v1/partner/sessions để xác minh nhiều hơn danh tính. address yêu cầu chứng minh địa chỉ. source_of_funds hỏi tiền đến từ đâu, còn source_of_wealth hỏi thêm tổng tài sản và cách tạo dựng nó. Hai giá trị về nguồn tiền gộp lại thành một bước, và cả hai đều kết hợp được với address.

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

Chính sách tùy chọn

Nếu không có chính sách, mặc định đối tác của bạn được áp dụng, nếu không thì dùng mặc định của chúng tôi. Chính sách được sao chép vào phiên khi phiên được tạo.

  • addressPolicy: maxAgeDays (mặc định 90, từ 1 đến 366), acceptedTypes (mặc định: giấy tờ tiện ích, viễn thông và ngân hàng, giấy tờ thuế, thư từ cơ quan nhà nước và giấy xác nhận cư trú) và signedOnly (mặc định false).
  • fundsPolicy: questions, evidence (none, optional hoặc required, mặc định none), acceptedTypes, maxAgeDays (mặc định 180), currency (ISO 4217, mặc định USD) và brackets cho các khoảng giá trị.
  • Một phiên đang mở với yêu cầu khác mà chưa được gửi sẽ bị hủy và một phiên mới bắt đầu, giống như khi đổi cấp độ.
  • Kiểm thử từng kết quả với các kịch bản sandbox như address_signed_pdf, address_document_review, funds_signed_documents và funds_review.

Phiên bổ sung

Chỉ xác minh các yêu cầu cho người dùng có danh tính đang hoạt động với bạn. Gửi reuseIdentity: true cùng với requirements.

  • Người dùng đồng ý và chỉ hoàn tất các bước mới. Không yêu cầu lại giấy tờ, selfie hay các nội dung tự khai.
  • Khi được duyệt, danh tính giữ nguyên id, ngày xác minh và thời hạn hiệu lực, đồng thời nhận kết quả mới. Bạn nhận session.approved và identity.updated.
  • Khi không có danh tính đang hoạt động, lệnh gọi trả về 409 step_not_allowed.
  • Khởi động từ quy tắc rủi ro của chính bạn, như khoản nạp lớn, cờ PEP hoặc quốc gia rủi ro. Quy tắc của bạn nằm trong backend của bạn, ở một chỗ duy nhất.
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", ... } }

Kết quả và phạm vi

Phiên được duyệt mang theo address và funds, và danh tính giữ bản mới nhất của từng mục. Các trường tóm tắt có ở mọi phạm vi. Chi tiết thì cần phạm vi.

  • Phạm vi address: các dòng địa chỉ. Không có phạm vi này, bạn nhận verified, method, evidenceLevel, documentType, documentDate, verifiedAt và country.
  • Phạm vi financial_data: answers, documents và findings. Không có phạm vi này, bạn nhận kinds, evidenceLevel, reviewed, documentCount và verifiedAt. Không bao giờ thuộc eligibility.
  • evidenceLevel là high, medium, low hoặc none. Các phương thức địa chỉ, từ mạnh nhất: wallet, id_document, signed_pdf, official_code, document_review và declared.
  • Giấy tờ không được gửi qua API. Thành viên có vai trò tuân thủ có thể xem các trang giấy tờ nguồn tiền trong bảng điều khiển khi môi trường có phạm vi financial_data. Mọi lượt xem đều được ghi log.
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": []
  }
}

Luồng web

Không dùng SDK? Hãy đưa người dùng đến trang do chúng tôi lưu trữ. Trang này dùng cùng API, cùng các bước kiểm tra và cùng hàng đợi xét duyệt.

1. Lấy liên kết

POST /v1/partner/sessions trả về verificationUrl. Truyền redirectUrl để đưa người dùng quay lại khi xong. Địa chỉ này phải bắt đầu bằng một trong các origin chuyển hướng bạn đã đăng ký.

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",
  ...
}
  • Token của client nằm trong phần fragment của URL, sau #. Trình duyệt không bao giờ gửi fragment lên máy chủ, nên token không nằm trong log hay trong header Referer. Trang đọc token, xóa nó khỏi thanh địa chỉ và chỉ giữ trong bộ nhớ.
  • Thêm lang vào fragment để chọn một trong 19 ngôn ngữ. Nếu không có, trang dùng ngôn ngữ của trình duyệt.
  • Tải lại trang sẽ mất token. Hãy tạo liên kết mới cho cùng phiên từ backend của bạn.
  • Trang gửi channel: "web" cùng với sự đồng ý. Trình duyệt không đọc được chip giấy tờ, nên phiên web dùng ảnh giấy tờ hoặc ví danh tính số. Các phiên này bỏ qua kiểm tra thiết bị, và ảnh giấy tờ từ web luôn do người xem xét được đào tạo quyết định.
  • Trên máy tính, trang hiển thị mã QR để người dùng tiếp tục trên điện thoại.

2. Khi người dùng xong

Khi luồng kết thúc (đã gửi, đã hủy hoặc trạng thái cuối), trang gửi tin nhắn này đến từng đối tượng sau nếu có: window.ReactNativeWebView, window.webkit.messageHandlers.zoiId, window.ZoiIdAndroid và window.opener. Sau đó trang mở redirectUrl với thêm zoiIdSession và zoiIdStatus.

Đừng bao giờ tin tin nhắn hay các tham số chuyển hướng. Chỉ tin webhook của bạn hoặc 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. Cài đặt webview

Android WebView

Bật javaScriptEnabled và domStorageEnabled, đặt mediaPlaybackRequiresUserGesture thành false. Trong WebChromeClient, cấp onPermissionRequest cho RESOURCE_VIDEO_CAPTURE sau khi ứng dụng của bạn có quyền CAMERA. Thêm bộ chọn tệp cho phương án dự phòng bằng ảnh.

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 trở lên. Đặt allowsInlineMediaPlayback thành true và thêm NSCameraUsageDescription vào ứng dụng. Từ iOS 15 trở lên, bạn có thể quyết định quyền máy ảnh trong UI delegate.

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

Đặt mediaCapturePermissionGrantType="grant", allowsInlineMediaPlayback và javaScriptEnabled. Ứng dụng của bạn cần quyền máy ảnh.

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.
    }
  }}
/>

Không hỗ trợ iframe, vì quyền máy ảnh và nguy cơ clickjacking. Hãy dùng tab trình duyệt, cửa sổ popup hoặc webview.

Trạng thái

Phiên

created
Phiên đã có, chưa tải lên gì.
in_progress
Đã đồng ý, các bước đang được tải lên.
processing
Đã gửi, đang chạy kiểm tra.
review
Đang chờ người xem xét được đào tạo.
approved
Kết thúc. Danh tính đã được xác minh.
rejected
Kết thúc. Xem các mã lý do.
resubmit
Người dùng phải làm lại các bước trong requiredSteps.
expired
Không có quyết định cuối cùng kịp thời.
cancelled
Bị hủy bởi đối tác, người dùng hoặc việc xóa dữ liệu.

Danh tính

active
Có thể dùng.
suspended
Tạm giữ, ví dụ sau khi có kết quả trùng trừng phạt mới.
expired
Giấy tờ hoặc thời hạn xác minh đã hết. Một phiên mới sẽ xác minh lại.
revoked
Kết thúc.

SDK

SDK React Native (Expo) cung cấp client API có kiểu, đọc chip NFC và kiểm tra thiết bị. SDK là headless, nên ứng dụng của bạn toàn quyền kiểm soát màn hình và camera. Với máy chủ, hãy dùng các client có kiểu cho TypeScript, Python, Go, PHP, Java và 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;
}

Webhook

Chúng tôi gửi webhook khi một phiên hoặc danh tính thay đổi. Luôn kiểm tra chữ ký trước khi tin vào nội dung.

  • Mỗi webhook có header Zoi-Id-Signature: t=unix seconds,v1=hex.
  • v1 là HMAC-SHA256 của chuỗi t.raw body với secret webhook của bạn. Dùng body gốc, trước khi phân tích JSON.
  • Trong lúc đổi secret, header có một v1 cho mỗi secret đang dùng. Chấp nhận webhook nếu một trong số đó khớp.
  • Từ chối nếu t lệch quá 300 giây so với hiện tại.
  • Các lần gửi thất bại được thử lại trong 72 giờ. Mỗi lần thử lại được ký lại với t mới.
  • Danh tính có revision. Chỉ áp dụng sự kiện nếu phiên bản của nó mới hơn phiên bản bạn đã lưu.
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"
  }
}

Loại sự kiện

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

Kiểm tra chữ ký bằng TypeScript

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

Kiểm tra chữ ký bằng Go

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
}

Nguồn sự kiện

GET /v1/partner/events trả về mọi sự kiện trong 30 ngày qua, cũ nhất trước. Truyền after với nextCursor cuối cùng để đọc trang tiếp theo. Dùng sau khi gián đoạn lâu, hoặc để chắc chắn bạn không bỏ lỡ gì.

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
}

Báo cáo gian lận

Hãy báo cho chúng tôi khi một phiên đã có quyết định hóa ra là gian lận, ví dụ sau một khoản chargeback. Báo cáo giúp chúng tôi đo lường các lần duyệt sai. Báo cáo không thay đổi phiên, danh tính hay kết quả của người dùng.

  • Phạm vi sessions:write. Chỉ cho phiên đã có quyết định (được duyệt hoặc bị từ chối). Trước khi có quyết định, lệnh gọi trả về 409.
  • Body rỗng sẽ báo cáo confirmed_fraud với loại other. status cũng có thể là suspected_fraud, hoặc cleared để rút lại báo cáo của chính bạn.
  • Các loại: forged_document, impostor, deepfake_or_replay, synthetic_identity, account_takeover và other. Không kèm loại khi dùng cleared.
  • note là văn bản tự do cho chuyên viên phân tích của chúng tôi, tối đa 1000 ký tự. Tuyệt đối không ghi tên, số giấy tờ hay dữ liệu cá nhân khác vào đó.
  • Báo cáo mới trả về 201. Gửi lại cùng báo cáo sẽ trả về bản đã lưu với 200. Gửi Idempotency-Key để thử lại an toàn. Tối đa 20 báo cáo cho mỗi phiên.
  • Cùng báo cáo đó chỉ cần một cú nhấp trên trang phiên trong bảng điều khiển.
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
}

Xóa dữ liệu

DELETE /v1/partner/users/:externalUserId xóa dữ liệu của người dùng khi họ đóng tài khoản hoặc yêu cầu xóa. Câu trả lời liệt kê những gì đã xóa ngay, và những gì được giữ, vì sao và đến khi nào.

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
}

Lỗi

Mọi lỗi có cùng một dạng và mỗi mã có một trạng thái HTTP cố định.

error
{
  "error": {
    "code": "step_not_allowed",
    "message": "This step is not allowed now.",
    "requestId": "req_..."
  }
}
MãHTTPÝ nghĩa
bad_request400Yêu cầu không khớp với hợp đồng API.
unauthorized401Thiếu hoặc sai khóa hay token.
token_expired401Token client đã hết hạn. Tạo token mới từ backend.
forbidden403Khóa này không được làm việc này.
not_found404Không có phiên hoặc người dùng như vậy.
session_closed409Phiên đã kết thúc. Không thể tải lên nữa.
step_not_allowed409Sai thứ tự, hoặc bước này hiện không cần.
revision_conflict409Phiên bản của phiên đã đổi. Hãy đọc lại.
idempotency_conflict409Cùng một Idempotency-Key đã được dùng cho yêu cầu khác.
challenge_expired410Thử thách đã hết hạn hoặc đã dùng. Hãy xin cái mới.
payload_too_large413Tệp tải lên quá lớn.
unsupported_media_type415Loại tệp không được nhận.
rate_limited429Quá nhiều yêu cầu. Thử lại sau.
internal500Có lỗi ở phía chúng tôi.
dependency_unavailable503Một dịch vụ chúng tôi cần đang ngừng. Thử lại sau.

Mã lý do

Phiên có reasonCodes khi cần gửi lại hoặc bị từ chối. Hãy hiển thị thông báo đã dịch của riêng bạn cho từng mã. Không bao giờ hiển thị mã gốc cho người dùng.

  • 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