> ## 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 - KYT من جانب العميل

> أرسل المعاملات المراقَبة من تطبيقك عبر token محدد النطاق مع Didit: تحليل الجهاز تلقائياً، وربط الموضوع المقاوم للانتحال، وتدفق ملكية المحفظة.

تتيح حزم Didit SDK إرسال [المعاملات المراقَبة](https://docs.didit.me/ar/transaction-monitoring/transactions.md) مباشرةً من جهاز المستخدم النهائي، بما في ذلك تحويلات [Travel Rule](https://docs.didit.me/ar/transaction-monitoring/travel-rule.md) وفحص العملات المشفرة.
يُصدر خادمك الخلفي token قصير الأجل محدد النطاق، وتتولى حزمة SDK باقي العملية.
تُرفق الحزمة ذكاء الجهاز تلقائياً، وعندما تحتاج تحويلة Travel Rule إلى إثبات السيطرة على المحفظة، تفتح مسار الإثبات فوراً تلقائياً.
لا تُشغَّل جلسة التحقق المطلوبة تلقائياً أبداً، بل تعود في الاستجابة ليشغّلها تطبيقك باستخدام تكامل التحقق الخاص بـ Didit؛ راجع [الإجراءات المطلوبة من المستخدم](#الإجراءات-المطلوبة-من-المستخدم) أدناه.

## نظرة عامة

يقوم الإرسال من جهة العميل على ثلاثة ضمانات:

* **ربط هوية مقاوم للانتحال.** يُحدَّد الـtoken عند الإصدار بقيمة `vendor_data` واحدة (معرّف المستخدم لديك). تُفرض هوية الموضوع لكل معاملة تُرسل بهذا الـtoken من الخادم استناداً إلى هذا الربط، فلا يستطيع عميل تم العبث به تقديم معاملات نيابةً عن مستخدم آخر، ولا تحتاج الـpayload إلى حمل أي بيانات اعتماد.
* **ذكاء جهاز تلقائي.** تجمع الحزمة بصمة الجهاز وترسلها مع الإرسال، ويستخلص Didit عنوان IP ووكيل المستخدم من الخادم. يُرفق سياق الجهاز المجمّع بموضوع المعاملة دون أي مكتبة إضافية، أو رمز جهاز منفصل، أو أي ربط برمجي من جانبك. ويدعم هذا السياق [القواعد](https://docs.didit.me/ar/transaction-monitoring/rules.md) المبنية على الجهاز، والربط بين الجلسات والمعاملات حسب الجهاز.
* **إثبات السيطرة على المحفظة الذي يُفتح تلقائياً.** عندما تتطلب المعاملة التي أُنشئت أن يُثبت المستخدم النهائي سيطرته على محفظة، تتضمن الاستجابة كتلة `action_required` من النوع `wallet_ownership`، فتفتح الحزمة هذا المسار فوراً، ثم تحدّث المعاملة عند انتهائه، دون الحاجة إلى حلقات استعلام متكررة أو إلى بناء آلية لإرسال الروابط بنفسك. يختلف إجراء `verification_session` المطلوب عن ذلك: فهو لا يُشغَّل تلقائياً أبداً، ويعود في الكتلة نفسها ليشغّله تطبيقك باستخدام تكامل التحقق الخاص به.

> [!NOTE]
>
> يبقى [`POST /v3/transactions/`](https://docs.didit.me/ar/transaction-monitoring/transactions) على الخادم الخيار الصحيح عندما لا يكون مستخدم نهائي حاضراً، مثل التحويلات الإدارية، واستيراد الدفعات، أو الأحداث التي تبلغ بها أنظمتك الداخلية.
> يُنشئ المساران سجلات المعاملات نفسها، ويطبّقان القواعد نفسها، ويصدران [أحداث webhook](https://docs.didit.me/ar/transaction-monitoring/webhooks.md) نفسها.

## التدفق

**1. إصدار رمز المعاملة**

يستدعي خادمك الخلفي `POST /v3/transactions/sdk-token/` باستخدام مفتاح API الخاص بك وقيمة `vendor_data` للمستخدم، ويمرر `sdk_token` المُعاد إلى تطبيقك.

**2. الإرسال من الجهاز**

يستدعي التطبيق `submitTransaction` في SDK، فترسل الحزمة الـpayload إلى `POST /v1/transactions/` مع الرأس `X-Transaction-Token` مع الإثراء التلقائي لبيانات الجهاز.

**3. معالجة أي إجراء مطلوب**

إذا كانت قيمة `action_required.type` هي `wallet_ownership`، فإن الحزمة تفتح الواجهة تلقائياً، ثم تحدّث المعاملة عبر `GET /v1/transactions/{transaction_id}/`.
وإذا كانت `verification_session`، فلن تشغّل الحزمة أي شيء؛ اقرأ `action_required` وابدأ مسار التحقق الخاص بك باستخدام `session_token` (أو `url` على الويب).

## إصدار رمز المعاملة

أصدِر tokens من خادمك الخلفي باستخدام `POST /v3/transactions/sdk-token/`، وذلك عبر المصادقة نفسها المعتمدة على مفتاح API في بقية الـendpoint الخاصة بالمعاملات في `/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"
}
```

| الحقل | النوع | الوصف |
| - | - | - |
| `vendor_data` | string | **مطلوب.** معرّفك الداخلي للمستخدم النهائي الذي يقتصر نطاق هذا الـtoken عليه. تُفرض هوية الموضوع لكل معاملة تُرسل بالـtoken انطلاقاً من هذه القيمة. |
| `ttl_seconds` | integer | مدة صلاحية الـtoken بالثواني. القيمة الافتراضية `900` (15 دقيقة)، والحد الأقصى `86400` (24 ساعة). |
| `max_uses` | integer | اختياري. الحد الأقصى لعدد عمليات الإرسال الناجحة المسموح بها بهذا الـtoken. احذفه للحصول على عدد غير محدود من الاستخدامات ضمن مدة الصلاحية. |

تُقبل أيضاً الأسماء بصيغة camelCase (`vendorData`، `ttlSeconds`، `maxUses`) كما في بقية واجهات `/v3/`.

> [!WARNING]
>
> **لا تضمّن مفتاح API في أي تطبيق أبداً.**
> يبقى مفتاح API على خادمك الخلفي، ويُستخدم لإصدار الـtokens فقط، ولا ينتقل إلى الجهاز سوى `sdk_token` القصير الأجل.
> أصدِر token جديداً لكل جلسة مستخدم بدلاً من إعادة استخدام tokens طويلة الأجل، واجعل `max_uses` محدوداً عندما يكون عدد عمليات الإرسال المتوقعة معروفاً.

## الإرسال من الجهاز

ترسل حزم SDK الطلبات إلى `POST /v1/transactions/` مع الـ token في الترويسة `X-Transaction-Token`.
الـ payload بصيغة camelCase، والطرق `submitTransaction` تبنيه عنك، ولا تحتاج إلى التعامل معه مباشرة إلا عند استدعاء الـ endpoint من عميل HTTP خاص بك:

```bash theme={null}
curl -X POST https://verification.didit.me/v1/transactions/ \
  -H "X-Transaction-Token: SDK_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "txnId": "wd-2026-07-07-0042",
    "type": "travelRule",
    "info": {
      "direction": "out",
      "amount": "0.25",
      "currency": "ETH",
      "currencyType": "crypto",
      "cryptoParams": { "crypto_chain": "ETH" }
    },
    "subject": { "fullName": "Jane Doe" },
    "counterparty": {
      "fullName": "Ana Diaz",
      "paymentMethod": { "type": "unhosted_wallet", "accountId": "0xBeneficiaryWallet01" }
    },
    "travelRule": {
      "status": "AWAITING_COUNTERPARTY",
      "beneficiaryData": { "wallet_address": "0xBeneficiaryWallet01", "name": "Ana Diaz" }
    },
    "includeCryptoScreening": true
  }'
```

يُطابق كل حقل من الحقول على المستوى الأعلى نظيره في [`POST /v3/transactions/`](https://docs.didit.me/ar/transaction-monitoring/transactions):

| حقل الإرسال | المكافئ في `/v3/` | ملاحظات |
| - | - | - |
| `txnId` | `transaction_id` | **مطلوب.** معرّفك الفريد للمعاملة. |
| `txnDate` | `transaction_at` | طابع زمني بصيغة ISO-8601؛ القيمة الافتراضية هي الوقت الحالي. |
| `zoneId` | `time_zone` | معرّف المنطقة الزمنية وفق IANA، مثل `Europe/Madrid`. |
| `type` | `transaction_category` | **مطلوب.** `finance`، و`kyc`، و`travelRule`، و`userPlatformEvent`، و`gamblingBet`، وبقية الفئات الأخرى. |
| `info` | `transaction_details` | **مطلوب.** `direction`، و`amount`، و`currency` (مطلوب)، إضافةً إلى `currencyType`، و`amountInDefaultCurrency`، و`defaultCurrencyCode`، و`paymentDetails`، و`paymentTxnId`، و`type` (نوع الإجراء)، و`cryptoParams`. |
| `subject`، و`counterparty` | `subject`، و`counterparty` | المشاركون: `type` (نوع الكيان)، و`externalUserId`، و`fullName`، و`firstName`، و`lastName`، و`dob`، و`address`، و`institutionInfo`، و`device`، و`paymentMethod` (`type`، و`accountId`، و`issuingCountry`). تُعلن القيمة `unhosted_wallet` في `paymentMethod.type` الخاص بالطرف المقابل أن الوجهة محفظة ذاتية الاستضافة - راجع [أدناه](#الإعلان-عن-محفظة-ذاتية-الاستضافة). |
| `props` | `custom_properties` | أزواج مفتاح-قيمة مخصصة، تُستخدم في القواعد بصيغة `custom_values.<key>`. |
| `travelRule` | `travel_rule_details` | `status`، و`protocol`، و`required`، و`obligationsCount`، و`originatorData`، و`beneficiaryData`، و`metadata`؛ الحقل `status` مطلوب كلما أرسلت هذه الكتلة، وإلا فشل الطلب بالرمز `400`. عند تفعيل Travel Rule تتجاهل Didit أي `status` ترسله، وتتولى إدارة التبادل بنفسها. |
| `includeCryptoScreening` | `include_crypto_screening` | تجاوز لفحص العملات الرقمية على مستوى كل معاملة. |

تأتي الاستجابة بتفاصيل المعاملة نفسها بصيغة snake\_case التي تستخدمها نقاط `/v3/`، مضافًا إليها الكتلة `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"
  }
}
```

### الإعلان عن محفظة ذاتية الاستضافة

يُعلن المثال أعلاه أن الوجهة هي المحفظة ذاتية الاستضافة للمستخدم: قيمة `paymentMethod.type` لدى الطرف المقابل هي `unhosted_wallet`.
يغيّر هذا الإعلان طريقة تنفيذ [تبادل Travel Rule](https://docs.didit.me/ar/transaction-monitoring/travel-rule.md#المحافظ-ذاتية-الاستضافة-المُصرَّح-بها):

* تتخطى Didit توجيه الطرف المقابل عبر VASP كليًّا، إذ لا يوجد VASP خلف المحفظة ذاتية الاستضافة، وتُنشئ التحويل مباشرةً بالحالة `UNCONFIRMED_OWNERSHIP`.
* عند تفعيل `auto_wallet_verification` في [إعدادات Travel Rule](https://docs.didit.me/ar/transaction-monitoring/travel-rule.md#الإعداد) (وهو الإعداد الافتراضي)، تُنشأ تلقائيًّا جلسة [أداة إثبات ملكية المحفظة](https://docs.didit.me/ar/transaction-monitoring/wallet-ownership-widget.md) وتُعاد في `action_required`، تمامًا كما في الاستجابة أعلاه.
* يُشغّل SDK الأداة تلقائيًّا؛ وبعد أن يُثبت المستخدم سيطرته على المحفظة، تُجرى مطابقة الاسم للتحويل ويتقدم إلى `COMPLETED`، أو إلى `COUNTERPARTY_MISMATCHED_DATA` إذا لم يطابق الاسم صاحب المحفظة في دفتر عناوينها.

من دون هذا الإعلان، تجرّب Didit [المسارات](https://docs.didit.me/ar/transaction-monitoring/travel-rule.md#نظرة-عامة) بالترتيب، ولا تلجأ إلى مسار الملكية إلا إذا تعذّر العثور على VASP طرف مقابل.
صرّح بالمحفظة كلما كان منتجك يعلم مسبقًا أن وجهة السحب هي محفظة المستخدم نفسه، فذلك يوفّر مرحلة التوجيه الفاشلة، ويوصل المستخدم إلى مسار الإثبات فورًا.

### فرض الجهة المعنية

تُحسم ثلاثة أمور دائمًا بواسطة الـ token، لا بواسطة الـ payload:

* التطبيق الذي تُنشأ المعاملة ضمنه.
* هوية الجهة المعنية: القيمة `subject.externalUserId` في الـ payload تُتجاهل وتُستبدل بالقيمة `vendor_data` الخاصة بالـ token.
* سياق الجهاز الخاص بالجهة المعنية (انظر أدناه)؛ القيم المشتقة من الخادم تغلب عند التعارض مع أي قيمة واردة في `subject.device`.

يعني الرمز `401` من هذا الـ endpoint أن الـ token رُفض: إما لأنه مفقود أو غير معروف أو ملغى أو تجاوز حد `max_uses` الخاص به أو منتهي الصلاحية.
تحتوي ردود الـ token المنتهي دائمًا على الكلمة `expired` في تفاصيل الخطأ، وبذلك تميّز SDK بين أخطاء `expired_token` و`invalid_token`.

### قراءة المعاملة مرة أخرى

يعيد `GET /v1/transactions/{transaction_id}/` التفاصيل نفسها، بالمصادقة نفسها عبر `X-Transaction-Token`.
تستدعي SDK هذا الـ endpoint تلقائيًّا بعد اكتمال مسار الإجراء الذي أُطلق أو انتهاء نافذته، فلا تتوقف الحالة النهائية على حدث واحد أو إشعار واحد.
استخدمه بنفسك لإعادة التحقق من `status` و`action_required` في أي وقت ما دام الـ token صالحًا؛ ومن الخادم الخاص بك، يعيد [`GET /v3/transactions/{transaction_id}/`](https://docs.didit.me/ar/transaction-monitoring/transactions) السجل نفسه.

## ذكاء الجهاز التلقائي

تتضمن كل عملية إرسال بيانات ذكاء الجهاز دون الحاجة إلى أي عمل تكامل من جانبك:

* يجمع SDK بصمة جهاز تحافظ على الخصوصية (مخطط `didit-fp-v2`: إشارات المنصة والمتصفح، ومعرّف جهاز دائم، ودرجة الروبوتية، وتجزئة مركّبة)، ويرسلها مع الطلب.
* تستنتج Didit عنوان IP ووكيل المستخدم (user agent) ورأس accept-language من جهة الخادم، انطلاقًا من الاتصال نفسه، لذلك لا يمكن تزويرها عبر الـ payload.

تُطبَّع النتيجة المدمجة في سياق جهاز المشارك subject في المعاملة، ثم تُضاف إلى مخطط الأجهزة أو تُحدَّث فيه، وهو المخطط نفسه الذي تغذّيه جلسات KYC.
وبذلك تصبح المعاملات قابلة للربط حسب الجهاز عبر قاعدة مستخدميك بأكملها ("مرتبطة بالجهاز")، وتزوّد [القواعد](https://docs.didit.me/ar/transaction-monitoring/rules.md) بإشارات الجهاز والشبكة، و[إثراء IP](https://docs.didit.me/ar/transaction-monitoring/ip-enrichment.md)، وتظهر في تفاصيل المعاملة داخل [Console](https://docs.didit.me/ar/transaction-monitoring/console.md).

يمكن للمعاملات المرسلة من الخادم عبر `/v3/` أن تواصل تمرير كائنات `device_context` صريحة لكل مشارك؛ أما عبر مسار SDK فتحصل على نسخة أقوى من البيانات نفسها دون أي جهد إضافي.

## الإجراءات المطلوبة من المستخدم

قد تتطلب بعض المعاملات أن يقوم المستخدم النهائي بإجراء قبل أن تُسوّى: يمكن لقاعدة أن تشترط جلسة تحقق، ويمكن لتحويل وفق Travel Rule أن يشترط إثباتاً بأن المحفظة الوجهة تعود فعلاً للمستخدم.
تظهر الحالتان في المكان نفسه: كتلة `action_required` في استجابة الإرسال (وفي كل قراءة لاحقة للمعاملة):

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

| النوع | متى يظهر | ما يفعله المستخدم |
| - | - | - |
| `verification_session` | عندما طبّق [إجراء قاعدة](https://docs.didit.me/ar/transaction-monitoring/rules.md) تحققاً إضافياً من المستخدم فانتقلت المعاملة إلى `AWAITING_USER`. | يُكمل جلسة تحقق مستضافة (مثل إعادة التحقق البيومتري)، يُطلقها تطبيقك - راجع [تشغيل جلسة التحقق](#تشغيل-جلسة-التحقق) أدناه. |
| `wallet_ownership` | يحتاج تحويل وفق Travel Rule إلى إثبات من المستخدم النهائي على السيطرة على المحفظة - إذ صُرّح بأن الوجهة [محفظة ذاتية الاستضافة](#الإعلان-عن-محفظة-ذاتية-الاستضافة) (`UNCONFIRMED_OWNERSHIP`)، أو أن تحويلاً صادراً لم يجد طرفاً مقابلاً من VASP - و`auto_wallet_verification` مفعّل في [إعدادات Travel Rule](https://docs.didit.me/ar/transaction-monitoring/travel-rule.md#الإعداد) لديك. يُنشئ Didit تلقائياً جلسة [أداة إثبات ملكية المحفظة](https://docs.didit.me/ar/transaction-monitoring/wallet-ownership-widget.md) مرتبطة بالمعاملة. | يُثبت السيطرة على المحفظة بتوقيع رسالة (على سلسلة مدعومة) أو بلقطة شاشة أو بالتصريح الذاتي، بحسب ما تسمح به إعدادات Travel Rule لديك، ولكن ليس باختبار Satoshi على جلسة مُنشأة تلقائياً - يُشغّل SDK هذه الجلسة تلقائياً نيابةً عنك. |

إذا انطبق الاحتمالان على المعاملة نفسها، يكون لـ`wallet_ownership` الأولوية، لأن غياب إثبات الملكية يمنع تبادل Travel Rule نفسه.
يصبح `action_required` بقيمة `null` بعد حلّ الإجراء المعلّق.
يُبقى الحقل `remediation` الموجود للتوافق مع الإصدارات السابقة؛ و`action_required` هو الكتلة المعتمدة.

### عقد التشغيل التلقائي

`autoLaunchAction` (القيمة الافتراضية `true`) يشغّل تلقائياً الإجراء `wallet_ownership` فقط:

* **الويب** - يُفتح في النافذة المنبثقة الخاصة بـ SDK، ويُكتشف الإكمال عبر المراسلة المتحقَّق من مصدرها إضافةً إلى استطلاع محدود للمعاملة (ولا يعتمد أبداً على حدث واحد).
* **iOS / Android** (وغلافا React Native وFlutter، اللذان يفوّضان إلى SDK الأصلية) - يُعرض كـ**تجربة إثبات ملكية المحفظة الأصلية** لدى SDK (SwiftUI / Jetpack Compose) عندما تكون `useNativeWalletOwnership` قيمتها `true` (وهي الافتراضية)، مع الرجوع إلى `SFSafariViewController` / Chrome Custom Tab، وأخيراً إلى المتصفح الافتراضي. بعد إغلاق التدفق أو عودة التطبيق إلى المقدمة، يعيد SDK قراءة المعاملة تلقائياً.

لا يُشغَّل إجراء `verification_session` تلقائياً أبداً: يعود دائماً في `action_required`، ليبدأه تطبيقك بتكامل التحقق الخاص بـ Didit.

> [!WARNING]
>
> لا يجوز عرض أداة إثبات ملكية المحفظة أبداً داخل عرض ويب مضمّن في تطبيق جوال، لأن روابط المحافظ العميقة لا تستطيع الخروج منه، ويتوقف تدفق الإثبات عند طريق مسدود.
> تستخدم SDKs بالفعل سطح المتصفح الصحيح عند التشغيل التلقائي؛ احتفظ بهذه القاعدة إذا تعاملت مع `url` بنفسك.
> راجع [دليل أداة إثبات ملكية المحفظة](https://docs.didit.me/ar/transaction-monitoring/wallet-ownership-widget.md#فتح-الرابط-المُعاد) للتفاصيل.

بعد أن يكتمل تدفق إثبات ملكية المحفظة الذي يشغّله SDK تلقائياً أو يُغلق، يعيد SDK قراءة المعاملة ويمرّر النتيجة المحدَّثة إلى دالة الاستدعاء `onActionCompleted` لديك.
لا يُعدّ إجراء `verification_session` جزءاً من هذه الدورة، لأن تطبيقك هو من يشغّله - راجع القسم التالي.

### تشغيل جلسة التحقق

عندما يكون `action_required.type` هو `verification_session`، اقرأ الكتلة من `result.actionRequired` وسلّمها إلى تكامل التحقق القائم لديك من Didit:

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

بعد أن ينتهي تدفق التحقق لديك، استدعِ `GET /v1/transactions/{transaction_id}/` (أو انتظر [webhook المعاملة](#الإشعارات-عبر-webhook)) للحصول على الحالة المحدَّثة للمعاملة.

### مثال: التحقق البيومتري الإضافي لسحب عالي القيمة

هذا هو التدفق في أفضل صوره: أخضِع المعاملات عالية المخاطر - المبالغ الكبيرة، والمستفيدون الجدد، والأجهزة الجديدة - لإعادة تحقق بيومتري من الشخص نفسه الذي تحققت منه عند التسجيل.

الاستيلاء على الحساب يبدو تماماً هكذا: كلمة مرور صحيحة، ورمز 2FA صحيح (تم الحصول عليه بالتصيّد أو عبر تبديل بطاقة SIM)، وسحب إلى محفظة لم تراها من قبل.
كل دفاع يعتمد على بيانات الاعتماد يكون قد فشل بحلول تلك اللحظة.
أما الوجه فلا يزال صامداً: لا يستطيع المهاجم اجتياز صورة سيلفي خضعت لكشف الحيوية يجب أن تطابق الوجه الملتقط أثناء تسجيل KYC للمالك الشرعي.

**1. أنشئ القاعدة مرة واحدة، في الكونسول**

**Transactions → Rules → Create Rule**: الشرط `amount gte 5000`، والإجراء **Change status → `AWAITING_USER`**، واختر سير العمل **المصادقة البيومترية** على الإجراء.
أضف أي شروط مخاطر أخرى تريدها - طرفاً مقابلاً لأول مرة، وبصمة جهاز جديدة، وتغيّر دولة عنوان IP - كشروط إضافية أو كقواعد إضافية.

**2. أرسل السحب كالمعتاد**

لا يتغير شيء في كود الإرسال لديك. عندما تُطبَّق القاعدة، تعود الاستجابة بـ `status: "AWAITING_USER"` وكتلة `action_required` من النوع `verification_session`:

```json theme={null}
{
  "status": "AWAITING_USER",
  "action_required": {
    "type": "verification_session",
    "url": "https://verify.didit.me/session/...",
    "session_id": "11111111-2222-3333-4444-555555555555",
    "session_token": "SESSION_TOKEN",
    "status": "Not Started"
  }
}
```

**3. شغّل إعادة التحقق**

سلّم `session_token` (أو `url`) إلى تكامل التحقق القائم لديك من Didit، تماماً كما في [تشغيل جلسة التحقق](#تشغيل-جلسة-التحقق).
بما أن الجلسة تُنشأ بالـ `vendor_data` نفسه الخاص بموضوع المعاملة، فإن Didit يحتفظ بالفعل بالوجه المرجعي المتحقَّق منه للمستخدم من التسجيل - صورة السيلفي المعتمدة مع كشف الحيوية، أو الصورة الموجودة على شريحة جواز السفر، أو الصورة الشخصية الموجودة على المستند.
يلتقط المستخدم صورة سيلفي حية واحدة؛ يُجري Didit كشف الحيوية ومطابقة الوجه بنسبة 1:1 مع ذلك المرجع المخزّن. لا إعادة لمسح المستند ولا إعادة تسجيل - عبء لا يتجاوز بضع ثوانٍ، فقط على المعاملات التي تستحقه.

**4. احسم المعاملة بحسب النتيجة**

عند اكتمال الجلسة، يُعاد تقييم المعاملة تلقائياً: `APPROVED` إذا نجحت إعادة التحقق، و`DECLINED` إذا فشلت، مع إرسال webhook بالحدث `transaction.status.updated` في الحالتين (`reason: remediation_completed` في الجدول الزمني للمعاملة).
يستلم تطبيقك النتيجة عبر دوال الاستدعاء على نمط `onActionCompleted`، أو عبر `GET /v1/transactions/{transaction_id}/`، أو عبر الـ webhook.

هذا يعمل لأن التسجيل عبر KYC وإعادة فحص القياسات الحيوية وقرار المعاملة تعمل كلها على منصة واحدة. الوجه الملتقط أثناء التسجيل هو المرجع لخطوة التحقق المعزَّز، ومخطط الجهاز يربط الجلسة بالمعاملة، ومحرك القواعد يجمع بينهما، فلا حاجة إلى وسيط من مزوّد خارجي، ولا إلى تصدير صور الوجه، ولا إلى عقد منفصل للقياسات الحيوية.
تُحتسب إعادة الفحص كميزة قياسية للمصادقة البيومترية ([\$0.10](https://docs.didit.me/ar/getting-started/pricing.md)) فقط عندما تُنفَّذ فعلاً.

## Web SDK

`submitTransaction` متاحة اليوم في كائن Web SDK الفريد (المفرد):

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

النتيجة هي `{ transactionId, status, travelRuleStatus?, actionRequired? }`.
في حمولة مستوى SDK تكتب `category` و`details` و`customProperties`، ويتولى SDK تحويلها إلى الأسماء المستخدمة في الشبكة (`type` و`info` و`props`) نيابةً عنك.

تُصنَّف الأخطاء عبر `DiditTransactionError`:

```typescript theme={null}
import { DiditSdk, DiditTransactionError } from "@didit-protocol/sdk-web";

try {
  await DiditSdk.shared.submitTransaction({ transactionToken, transaction });
} catch (error) {
  if (error instanceof DiditTransactionError) {
    switch (error.type) {
      case "expired_token": // mint a fresh token and retry
        break;
      case "invalid_token": // rejected: unknown, revoked, or over max_uses
        break;
      case "validation": // inspect error.fieldErrors for per-field details
        break;
      case "network": // retry with backoff
        break;
    }
  }
}
```

## SDKs الأصلية

> [!NOTE]
>
> تأتي واجهة `submitTransaction` الأصلية مع iOS وAndroid SDK 4.1.0 (وكذلك إصدارات React Native وFlutter التي تثبّتها).
> على عكس Web SDK، تستخدم الحمولات الأصلية أسماء الحقول الشبكية مباشرة: `type` و`info` و`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
```

يعرض كل SDK أيضًا `getTransaction(transactionToken, transactionId)` لإعادة قراءة المعاملة بالـ token نفسه. تستدعيها الـSDKs نيابةً عنك بعد تشغيل مسار إجراء، ويمكنك استدعاءها بنفسك في أي وقت.

على الأجهزة المحمولة، يُطبَّق نقل بصمة الجهاز بالفعل على جميع استدعاءات الشبكة في SDK، لذلك تحمل طلبات إرسال المعاملات بيانات ذكاء الجهاز نفسها تلقائيًا.

## بدون SDKs

كل ما تفعله SDKs هو HTTPS عادي، ولا يتطلب أيٌّ من ذلك استخدامها.
هناك شكلان متكافئان للتكامل:

**من جانب الخادم فقط (دون أي token).** أرسل المعاملة عبر [`POST /v3/transactions/`](https://docs.didit.me/ar/management-api/transactions/create) من الخادم الخلفي باستخدام مفتاح API الخاص بك. تحمل الاستجابة، وكذلك كل قراءة لاحقة عبر `GET /v3/transactions/{transaction_id}/`، كتلة `action_required` نفسها. يمكنك أيضًا تمرير سياق `device` صريح لكل مشارك، لأنه لا يوجد SDK يجمعه نيابةً عنك.

**من جانب العميل باستخدام مكدس HTTP الخاص بك.** أنشئ token عبر [`POST /v3/transactions/sdk-token/`](https://docs.didit.me/ar/management-api/transactions/sdk-token)، ثم استدعِ [`POST /v1/transactions/`](https://docs.didit.me/ar/management-api/transactions/submit-sdk) و[`GET /v1/transactions/{transaction_id}/`](https://docs.didit.me/ar/management-api/transactions/get-sdk) من الجهاز مع الرأس `X-Transaction-Token`. تنطبق الآلية نفسها لربط الموضوع نفسه واشتقاق عنوان IP ووكيل المستخدم من جهة الخادم.

ثم تعامل مع `action_required` بنفسك:

| `action_required.type` | الإجراء المطلوب |
| - | - |
| `wallet_ownership` | أرسل `url` إلى المستخدم وافتحه على **واجهة متصفح حقيقية**: علامة تبويب عادية على الويب، أو `SFSafariViewController` / Chrome Custom Tabs على الأجهزة المحمولة، أو خارج نطاق التطبيق عبر رابط بريد إلكتروني أو رمز QR. لا تعرضه أبدًا داخل عرض ويب مضمّن، لأن روابط المحفظة العميقة لا يمكنها الخروج منه. |
| `verification_session` | وجّه المستخدم إلى `url`، وهي صفحة التحقق المستضافة القياسية من Didit، أو مرّر `session_token` إلى أي تكامل تحقق من Didit تشغّله بالفعل. |

ثم اكتشف الإتمام دون أي استدعاء من العميل:

* **webhook (موصى به).** يُطلَق `transaction.status.updated` عندما يحسم الإجراء المعلّق المعاملة، ويُطلَق `travel_rule.status.updated` عند كل انتقال في التبادل. يتضمن كلاهما ما يكفي لتحديث سجلاتك دون أي استطلاع.
* **الاستطلاع.** أعد قراءة المعاملة (`/v3/` من خادمك الخلفي، أو `/v1/` مع token ما دام صالحًا). تعود `action_required` إلى `null` بعد الحسم، وتحمل `status` و`travel_rule.status` النتيجة.

لا توجد تبعيات لمرة واحدة في هذا التصميم: تلتقي الأداة وجلسة التحقق وخادمك الخلفي كلها عند حالة المعاملة الدائمة نفسها، لذلك يمكن دائمًا استعادة الحالة بعد إغلاق تبويب أو فوات webhook بإعادة قراءة المعاملة.

## الإشعارات عبر webhook

لا يحتاج خادمك الخلفي إلى أن يعيد التطبيق الإبلاغ. عندما تتطلب معاملة مُرسلة إجراءً من المستخدم، يتضمن [webhook المعاملة](https://docs.didit.me/ar/transaction-monitoring/webhooks.md)، أي `transaction.created` أو `transaction.status.updated`، كتلة `action_required` نفسها، فتُطلِع أنظمتك على الإجراء المعلّق حتى لو أُغلق التطبيق قبل انتهاء المسار. يأتي إجراء `verification_session` مع الحالة `AWAITING_USER`، لكن إجراء `wallet_ownership` لا يغيّر حالة المعاملة وحده، لذلك اقرأ `action_required` بدلاً من الحالة.
تصدر انتقالات Travel Rule أيضًا [`travel_rule.status.updated`](https://docs.didit.me/ar/transaction-monitoring/travel-rule#webhooks).

## الخطوات التالية

> [card] [إرسال المعاملات](https://docs.didit.me/ar/transaction-monitoring/transactions.md)
>
> واجهة إنشاء المعاملة من جهة الخادم ومرجع الحمولة الكامل.

> [card] [Travel Rule](https://docs.didit.me/ar/transaction-monitoring/travel-rule.md)
>
> التبادل المُدار بين المُرسِل والمستفيد، والإعدادات، ودورة الحياة.

> [card] [أداة ملكية المحفظة](https://docs.didit.me/ar/transaction-monitoring/wallet-ownership-widget.md)
>
> مسار إثبات التحكم المستضاف الذي يقف وراء إجراءات wallet\_ownership.

> [card] [webhook](https://docs.didit.me/ar/transaction-monitoring/webhooks.md)
>
> transaction.created وtransaction.status.updated، والتحقق من التوقيع.

> [card] [القواعد](https://docs.didit.me/ar/transaction-monitoring/rules.md)
>
> إجراءات القواعد، بما فيها الإجراءات التي تنشئ جلسات التحقق.

> [card] [الحالات](https://docs.didit.me/ar/transaction-monitoring/statuses.md)
>
> ما معنى AWAITING\_USER في دورة حياة المعاملة.


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