انتقل إلى المحتوى
Zoi ID

ابنِ مع Zoi ID

واجهتا API على مضيف واحد، وعملاء محددو الأنواع بست لغات، ومسار مُستضاف، وبيئة اختبار. الدليل الكامل متاح للقراءة، ومفاتيح بيئة الاختبار تأتي مع الوصول المبكر.

عنوان URL الأساسيhttps://kyc.zoi.network

البدء السريع

  1. 1

    أنشئ جلسة

    يستدعي خادمك POST /v1/partner/sessions بمفتاح الشريك.

  2. 2

    شغّل المسار

    مرّر رمز العميل إلى SDK، أو افتح verificationUrl في متصفح.

  3. 3

    استقبل النتيجة

    تحقق من توقيع Webhook، ثم احفظ معرّف الهوية وحالتها.

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

واجهتا API

واجهة الشركاء

من خادم إلى خادم، تحت /v1/partner. أرسل مفتاح الشريك كرمز Bearer. لا تضعه في أي تطبيق أبدًا.

واجهة العميل

تستخدمها حزمة SDK، تحت /v1/client. ترسل رمز عميل صالحًا لجلسة واحدة لمدة 60 دقيقة.

القواعد

  • الأوقات بالمللي ثانية منذ epoch، في الحقول التي تنتهي أسماؤها بـ At.
  • التواريخ بصيغة YYYY-MM-DD. الأجزاء غير المعروفة تُكتب XX، مثل 1990-XX-XX.
  • طلبات POST التي تنشئ أو تقرر شيئًا تقبل الترويسة Idempotency-Key. تُحفظ المفاتيح 24 ساعة.
  • تغييرات الجلسة ترسل رقم المراجعة المتوقع. إذا تغيّر، تحصل على revision_conflict.

مسار الجلسة

ينشئ خادمك الجلسة. تنفذ حزمة SDK الخطوات. يتحقق الخادم من الترتيب ويرد بـ step_not_allowed عندما تأتي خطوة مبكرًا.

1. أنشئ جلسة من خادمك

استدعاؤها مرة أخرى للمستخدم نفسه يستأنف الجلسة المفتوحة ويعطي رمز عميل جديدًا.

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. الخطوات التي تنفذها SDK

  1. 01

    قراءة الجلسة والخطوات المتبقية.

    GET /v1/client/session

  2. 02

    تسجيل الموافقة، مع الاختيار المنفصل والاختياري لمقارنة الوجه.

    POST /v1/client/consent

  3. 03

    فحص التطبيق والجهاز. والنتيجة جزء من القرار.

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

  4. 04

    إرسال الوثيقة: قراءة الشريحة بعد مسح MRZ، أو إرسال صور.

    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

    تسجيل فيديو قصير واحد بالكاميرا الأمامية بينما يعرض التطبيق حركات الرأس.

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

  6. 06

    إرسال دولة الإقامة، والعنوان في المستوى المعزز.

    POST /v1/client/profile

  7. 07

    فقط مع متطلب العنوان: ارفع وثيقة، ثم أكّد العنوان. تُتخطى إذا كانت وثيقة الهوية قد قدّمت العنوان أصلًا.

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

  8. 08

    فقط مع متطلب الأموال: احفظ الإجابات، وأضف الوثائق إذا طلبتها السياسة، ثم أكمل الخطوة.

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

  9. 09

    الإجابة عن سؤالين: هل الشخص معرض سياسيًا، وهل يتصرف لحسابه الخاص.

    POST /v1/client/declarations

  10. 10

    الإرسال برقم المراجعة الحالي.

    POST /v1/client/submit

يمكن للمستخدم أيضًا أن يطلب من شخص مراجعة نتيجة آلية (POST /review-request) أو أن يسحب موافقته، فتُلغى الجلسة وتُحذف ملفاتها فورًا (POST /withdraw).

إثبات العنوان ومصدر الأموال

أضف requirements إلى POST /v1/partner/sessions للتحقق مما هو أبعد من الهوية. تطلب address إثبات العنوان. وتسأل source_of_funds من أين يأتي المال، وتسأل source_of_wealth أيضًا عن إجمالي الثروة وكيف تكوّنت. القيمتان الخاصتان بالأموال معًا خطوة واحدة، وكلتاهما تُجمع مع 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" }
}

سياسات اختيارية

دون سياسة، تُطبَّق إعداداتك الافتراضية كشريك، وإلا فإعداداتنا. وتُنسخ السياسة إلى الجلسة عند إنشائها.

  • addressPolicy: maxAgeDays (افتراضيًا 90، من 1 إلى 366)، وacceptedTypes (افتراضيًا: وثائق الخدمات والاتصالات والبنوك، والوثائق الضريبية، والرسائل الحكومية، وشهادات الإقامة)، وsignedOnly (افتراضيًا false).
  • fundsPolicy: questions، وevidence (none أو optional أو required، افتراضيًا none)، وacceptedTypes، وmaxAgeDays (افتراضيًا 180)، وcurrency (ISO 4217، افتراضيًا USD)، وbrackets للنطاقات.
  • الجلسة المفتوحة بمتطلبات أخرى التي لم تُرسل بعد تُلغى وتبدأ جلسة جديدة، كما عند تغيير المستوى.
  • اختبر كل نتيجة بسيناريوهات بيئة الاختبار مثل address_signed_pdf وaddress_document_review وfunds_signed_documents وfunds_review.

جلسات المتابعة

تحقّق من المتطلبات فقط لمستخدم هويته نشطة لديك أصلًا. أرسل reuseIdentity: true مع requirements.

  • يوافق المستخدم ويكمل الخطوات الجديدة فقط. لا يُطلب منه من جديد وثيقة ولا سيلفي ولا إقرارات.
  • عند الموافقة تحتفظ الهوية بمعرّفها وتاريخ التحقق وصلاحيتها، وتحصل على النتيجة الجديدة. وتتلقى session.approved وidentity.updated.
  • دون هوية نشطة، يرد الاستدعاء بـ 409 step_not_allowed.
  • ابدأها من قواعد المخاطر الخاصة بك، مثل إيداع كبير أو علامة PEP أو دولة عالية المخاطر. وتبقى قواعدك في خادمك، في مكان واحد.
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", ... } }

النتائج والصلاحيات

تحمل الجلسات المعتمدة address وfunds، وتحتفظ الهوية بأحدث نتيجة لكل منهما. حقول الملخص تأتي مع كل صلاحية. أما التفاصيل فتحتاج إلى صلاحية.

  • الصلاحية address: أسطر العنوان. ومن دونها تحصل على verified وmethod وevidenceLevel وdocumentType وdocumentDate وverifiedAt وcountry.
  • الصلاحية financial_data: answers وdocuments وfindings. ومن دونها تحصل على kinds وevidenceLevel وreviewed وdocumentCount وverifiedAt. ولا تدخل أبدًا في eligibility.
  • قيمة evidenceLevel هي high أو medium أو low أو none. طرق العنوان، من الأقوى إلى الأضعف: wallet وid_document وsigned_pdf وofficial_code وdocument_review وdeclared.
  • لا تُرسل الوثائق عبر API. يستطيع الأعضاء ذوو دور الامتثال عرض صفحات وثائق مصدر الأموال في لوحة التحكم عندما تملك البيئة صلاحية financial_data. وكل عملية عرض تُسجَّل.
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": []
  }
}

مسار الويب

لا تستخدم SDK؟ أرسل المستخدم إلى صفحتنا المستضافة. تستخدم نفس الواجهة البرمجية ونفس الفحوصات ونفس قائمة المراجعة.

1. احصل على الرابط

يعيد POST /v1/partner/sessions القيمة verificationUrl. مرّر redirectUrl لإعادة المستخدم إليك عند الانتهاء. يجب أن يبدأ بأحد أصول إعادة التوجيه المسجلة لديك.

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",
  ...
}
  • رمز العميل موجود في جزء الـ fragment من الرابط، بعد #. لا ترسل المتصفحات هذا الجزء إلى أي خادم أبدًا، لذا لا يظهر الرمز في السجلات ولا في ترويسة Referer. تقرأه الصفحة، ثم تحذفه من شريط العنوان وتحتفظ به في الذاكرة فقط.
  • أضف lang إلى الـ fragment لاختيار إحدى اللغات الـ 19. بدونه تستخدم الصفحة لغة المتصفح.
  • إعادة التحميل تُفقد الرمز. أنشئ رابطًا جديدًا للجلسة نفسها من الخادم الخلفي لديك.
  • ترسل الصفحة channel: "web" مع الموافقة. المتصفحات لا تستطيع قراءة شرائح الهوية، لذا تستخدم جلسات الويب صور الوثيقة أو محفظة هوية رقمية. وتتخطى فحص الجهاز، وصور الوثائق القادمة من الويب يحسمها دائمًا مراجع مدرب.
  • على الكمبيوتر، تعرض الصفحة رمز QR حتى يتابع المستخدم على هاتفه.

2. عندما ينتهي المستخدم

عند انتهاء المسار (أُرسل أو أُلغي أو وصل إلى حالة نهائية)، ترسل الصفحة هذه الرسالة إلى كل ما يوجد من التالي: window.ReactNativeWebView وwindow.webkit.messageHandlers.zoiId وwindow.ZoiIdAndroid وwindow.opener. ثم تفتح redirectUrl مع إضافة zoiIdSession وzoiIdStatus.

لا تثق أبدًا بالرسالة أو بمعاملات إعادة التوجيه. ثق فقط بالـ webhook الخاص بك أو بـ 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

Android WebView

فعّل javaScriptEnabled وdomStorageEnabled، واضبط mediaPlaybackRequiresUserGesture على false. في WebChromeClient، امنح onPermissionRequest للمورد RESOURCE_VIDEO_CAPTURE بعد أن يحصل تطبيقك على إذن CAMERA. أضف منتقي ملفات لبديل الصور.

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 أو أحدث. اضبط allowsInlineMediaPlayback على true وأضف NSCameraUsageDescription إلى تطبيقك. في iOS 15 وما بعده يمكنك تحديد إذن الكاميرا في 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

اضبط mediaCapturePermissionGrantType="grant" وallowsInlineMediaPlayback وjavaScriptEnabled. يحتاج تطبيقك إلى إذن الكاميرا.

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

الـ iframe غير مدعوم، بسبب أذونات الكاميرا وخطر clickjacking. استخدم تبويب متصفح أو نافذة منبثقة أو webview.

الحالات

الجلسة

created
الجلسة موجودة، ولم يُرفع شيء بعد.
in_progress
تمت الموافقة، والخطوات قيد الرفع.
processing
تم الإرسال، والفحوص جارية.
review
بانتظار مراجع مدرب.
approved
نهائية. تم التحقق من الهوية.
rejected
نهائية. راجع رموز الأسباب.
resubmit
على المستخدم إعادة الخطوات الموجودة في requiredSteps.
expired
لم يصدر قرار نهائي في الوقت المحدد.
cancelled
ألغاها الشريك أو المستخدم أو عملية حذف.

الهوية

active
قابلة للاستخدام.
suspended
معلّقة، مثلًا بعد ظهور تطابق محتمل جديد مع العقوبات.
expired
انتهت صلاحية الوثيقة أو فترة التحقق. جلسة جديدة تعيد التحقق.
revoked
نهائية.

SDK

تعطيك حزمة SDK لـ React Native (Expo) عميل API محدد الأنواع، وقراءة الشريحة عبر NFC، وفحوص الجهاز. وهي بلا واجهة، فيحتفظ تطبيقك بالتحكم الكامل في شاشاته وكاميرته. ولخوادمك، استخدم العملاء محددي الأنواع لـ TypeScript وPython وGo وPHP وJava و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

نرسل Webhook عندما تتغير جلسة أو هوية. تحقق دائمًا من التوقيع قبل أن تثق بالمحتوى.

  • كل Webhook يحمل الترويسة Zoi-Id-Signature: t=unix seconds,v1=hex.
  • v1 هو HMAC-SHA256 للنص t.raw body باستخدام سر Webhook الخاص بك. استخدم المحتوى الخام قبل أي تحليل لـ JSON.
  • أثناء تغيير السر، تحمل الترويسة قيمة v1 لكل سر نشط. اقبل Webhook إذا تطابقت أي واحدة منها.
  • ارفضه إذا كان t يبعد أكثر من 300 ثانية عن الوقت الحالي.
  • تُعاد محاولة التسليمات الفاشلة لمدة 72 ساعة. كل محاولة تُوقّع من جديد بقيمة t جديدة.
  • الهويات تحمل revision. طبّق الحدث فقط إذا كانت مراجعته أحدث من التي حفظتها.
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"
  }
}

أنواع الأحداث

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

التحقق من التوقيع بـ 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 });
}

التحقق من التوقيع بـ 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
}

سجل الأحداث

GET /v1/partner/events يعيد كل أحداث آخر 30 يومًا، الأقدم أولًا. مرّر after مع آخر nextCursor لقراءة الصفحة التالية. استخدمه بعد انقطاع طويل، أو للتأكد من أنك لم تفوّت شيئًا.

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
}

بلاغات الاحتيال

أخبرنا عندما يتبيّن أن جلسة محسومة كانت احتيالًا، مثلًا بعد استرجاع مدفوعات. تغذّي البلاغات قياسنا للموافقات الخاطئة. ولا تغيّر الجلسة ولا الهوية ولا نتيجة المستخدم.

  • الصلاحية sessions:write. للجلسات المحسومة فقط (المعتمدة أو المرفوضة). وقبل القرار يرد الاستدعاء بـ 409.
  • الطلب بمحتوى فارغ يبلّغ عن confirmed_fraud بالفئة other. ويمكن أن تكون status أيضًا suspected_fraud، أو cleared لسحب بلاغك.
  • الفئات: forged_document وimpostor وdeepfake_or_replay وsynthetic_identity وaccount_takeover وother. لا فئة مع cleared.
  • note نص حر لمحللينا، بحد أقصى 1000 حرف. لا تضع فيه أبدًا أسماء أو أرقام وثائق أو أي بيانات شخصية أخرى.
  • البلاغ الجديد يرد بـ 201. والبلاغ نفسه مرة أخرى يعيد البلاغ المحفوظ بـ 200. أرسل Idempotency-Key لإعادة المحاولة بأمان. بحد أقصى 20 بلاغًا لكل جلسة.
  • البلاغ نفسه على بُعد نقرة واحدة في صفحة الجلسة بلوحة التحكم.
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
}

الحذف

DELETE /v1/partner/users/:externalUserId يحذف بيانات المستخدم عندما يغلق حسابه أو يطلب الحذف. تذكر الإجابة ما حُذف الآن، وما يُحتفظ به، ولماذا، وحتى متى.

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
}

الأخطاء

لكل الأخطاء الشكل نفسه، ولكل رمز حالة HTTP ثابتة.

error
{
  "error": {
    "code": "step_not_allowed",
    "message": "This step is not allowed now.",
    "requestId": "req_..."
  }
}
الرمزHTTPالمعنى
bad_request400الطلب لا يطابق العقد.
unauthorized401المفتاح أو الرمز مفقود أو خاطئ.
token_expired401انتهت صلاحية رمز العميل. أنشئ رمزًا جديدًا من خادمك.
forbidden403هذا المفتاح لا يملك صلاحية ذلك.
not_found404لا توجد جلسة أو مستخدم بهذا المعرّف.
session_closed409الجلسة نهائية. لا مزيد من الرفع.
step_not_allowed409ترتيب خاطئ، أو الخطوة غير مطلوبة الآن.
revision_conflict409تغيرت مراجعة الجلسة. اقرأها من جديد.
idempotency_conflict409استُخدم Idempotency-Key نفسه مع طلب مختلف.
challenge_expired410انتهت صلاحية التحدي أو استُخدم من قبل. اطلب تحديًا جديدًا.
payload_too_large413الملف المرفوع كبير جدًا.
unsupported_media_type415نوع الملف غير مقبول.
rate_limited429طلبات كثيرة جدًا. حاول لاحقًا.
internal500حدث خطأ من جهتنا.
dependency_unavailable503خدمة نحتاجها متوقفة. حاول لاحقًا.

رموز الأسباب

تحمل الجلسات reasonCodes عندما تحتاج إلى إعادة إرسال أو عند رفضها. اعرض رسالتك المترجمة لكل رمز. لا تعرض الرمز الخام للمستخدمين أبدًا.

  • 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