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

# webhooks مراقبة المعاملات (KYT): الأحداث والتوقيع

> أحداث webhook لمراقبة المعاملات في Didit: المُشغِّلات وبنية payload والتحقق من توقيع HMAC. 1,000 معاملة شهرياً مشمولة، ثم $0.001 لكل معاملة.

تُصدر مراقبة المعاملات ثلاثة أحداث webhook تغطي معاً دورة حياة المعاملة كاملةً، بالإضافة إلى حدث رابع اختياري يعلن عن اقتراحات قواعد جديدة. تعتمد جميعها على البنية التحتية القياسية لـ webhook في Didit مع توقيع HMAC-SHA256. راجع [مرجع webhooks](https://docs.didit.me/ar/integration/webhooks.md) للوجهات والتوقيع والاختبار.

## كتالوج الأحداث

| الحدث | وقت الإطلاق |
| - | - |
| `transaction.created` | أُنشئت معاملة جديدة واكتمل التقييم الأولي. يُطلق هذا الحدث أيضاً للمعاملات الواردة التي تنشئها Didit لك: تسجيل [Travel Rule Sunrise](https://docs.didit.me/ar/transaction-monitoring/travel-rule.md#التسجيل-اللاحق-للإيداع) بعد إيداع، أو استعلام `TRP` وارد، أو تحويل عبر مسار `INTERNAL` من عميل آخر لدى Didit. |
| `transaction.status.updated` | تغيّرت حالة المعاملة، سواء من مراجعة المحلل، أو من حل جلسة المعالجة، أو من إجراء قاعدة عندما تعيد Didit تقييم قواعدك بعد انتقال Travel Rule (مثلاً بعد إثبات ملكية المحفظة). لا يرسل هذا الحدث تغيير الحالة الذي يطبّق `timeout_outcome` عند انتهاء صلاحية تحويل؛ بدلاً من ذلك، استجب لـ `travel_rule.status.updated` مع `EXPIRED`. |
| `travel_rule.status.updated` | انتقل تبادل [Travel Rule](https://docs.didit.me/ar/transaction-monitoring/travel-rule.md) في التحويلات الصادرة والواردة على حد سواء (الساق الوارد لمسار `INTERNAL`، وتسجيلات Sunrise، واستعلامات `TRP` الواردة)، بما في ذلك الانتقال `NOT_APPLICABLE` للمبالغ دون الحد. بالنسبة إلى معاملة تنشئها Didit، تُرسل هذه الأحداث بعد `transaction.created` الخاص بها. |
| `transaction_rule_suggestions.created` | اقترحت عملية تشغيل اقتراحات القواعد تعديلات جديدة على قواعد تطبيقك. يُطلق فقط عند تفعيل **Webhook** في إعدادات إشعارات اقتراحات القواعد، وفقط للتشغيلات التي أنشأت اقتراحات. |

يمكن أيضاً أن تُطلق أحداث الكيانات (`user.status.updated` و`business.status.updated`) نتيجة لنشاط المعاملات؛ راجع [webhooks الكيانات](https://docs.didit.me/ar/entities/webhooks.md).

> [!NOTE]
>
> لا ترسل تطبيقات البيئة التجريبية أحداث webhook من نوع `transaction.created` أو `transaction.status.updated`. لاختبار هذه الأحداث، استخدم تطبيقاً في بيئة الإنتاج أو **API & Webhooks → Try Webhook** في الكونسول. ترسل تبادلات Travel Rule في البيئة التجريبية أحداث `travel_rule.status.updated` مع `environment: sandbox`؛ استخدم [Didit Test VASP](https://docs.didit.me/ar/transaction-monitoring/travel-rule-sandbox.md) لاختبار التبادل قبل الإطلاق.

## الغلاف

تستخدم webhooks في Didit جسماً **مسطّحاً**: حقول الحدث موجودة في المستوى الأعلى إلى جانب حقول الغلاف، ولا يوجد كائن `data` متداخل.
يحمل كل تسليم الحقول التالية:

| الحقل | الوصف |
| - | - |
| `webhook_type` | اسم الحدث، مثل `transaction.created`. |
| `event_id` | معرّف ثابت للحدث. يُعاد استخدام `event_id` نفسه عند إعادة المحاولة، لذا أزل التكرار بالاعتماد عليه. |
| `timestamp` / `created_at` | عدد ثواني حقبة Unix عند إرسال التسليم. |
| `application_id` | التطبيق الذي ينتمي إليه الحدث. |
| `environment` | `live` أو `sandbox`. |

## بنى الحمولة

### `transaction.created` و`transaction.status.updated`

يشترك الحدثان في حمولة واحدة: ملخص مختصر مع كتلة [`action_required`](https://docs.didit.me/ar/transaction-monitoring/sdk-transaction-submission#الإجراءات-المطلوبة-من-المستخدم) (أو `null`):

```json theme={null}
{
  "webhook_type": "transaction.created",
  "event_id": "9c0c8b8a-1111-4222-9333-666666666666",
  "timestamp": 1774970000,
  "created_at": 1774970000,
  "application_id": "11111111-2222-3333-4444-555555555555",
  "environment": "live",
  "transaction_id": "9c7d2a30-...",
  "txn_id": "tx-0001",
  "status": "IN_REVIEW",
  "score": 65,
  "severity": "MEDIUM",
  "amount": "500.00",
  "currency": "EUR",
  "direction": "OUTBOUND",
  "action_required": null
}
```

الحمولة مختصرة عمداً: عند الاستلام، اجلب السجل الكامل عبر [`GET /v3/transactions/{transaction_id}/`](https://docs.didit.me/ar/management-api/transactions/get)، فهو يعيد التفاصيل الكاملة (الأطراف و`rule_runs` و`provider_results` و`travel_rule` و`cost_breakdown` و`decision_reason_code`)، بما في ذلك *سبب* تغيّر الحالة.

### `transaction_rule_suggestions.created`

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

```json theme={null}
{
  "webhook_type": "transaction_rule_suggestions.created",
  "run_id": "5b2f0c1e-0000-0000-0000-000000000000",
  "open_count": 3,
  "suggestions": [
    {
      "suggestion_id": "9a7d3e22-0000-0000-0000-000000000000",
      "kind": "...",
      "title": "...",
      "rule_id": "...",
      "expected_impact": {}
    }
  ]
}
```

| الحقل | الوصف |
| - | - |
| `run_id` | تشغيل اقتراحات القواعد الذي أنتج الاقتراحات الجديدة. |
| `open_count` | عدد الاقتراحات المفتوحة لتطبيقك بعد هذا التشغيل. |
| `suggestions[]` | الاقتراحات التي أنشأها هذا التشغيل، ولكل منها `suggestion_id` و`kind` و`title` و`rule_id` (تكون null عندما يقترح الاقتراح قاعدة جديدة) و`expected_impact`. |

تُضاف حقول الغلاف المعتادة (`event_id` و`timestamp` و`application_id` و`environment`) إلى الجسم.

### `travel_rule.status.updated`

```json theme={null}
{
  "webhook_type": "travel_rule.status.updated",
  "event_id": "9c0c8b8a-1111-4222-9333-777777777777",
  "timestamp": 1774970000,
  "created_at": 1774970000,
  "application_id": "11111111-2222-3333-4444-555555555555",
  "environment": "live",
  "transaction_id": "22222222-3333-4444-5555-666666666666",
  "txn_id": "trv-2026-07-05-001",
  "travel_rule_status": "COMPLETED",
  "rail": "INTERNAL",
  "direction": "OUTBOUND"
}
```

يُطلق عند كل انتقال، بما في ذلك الانتقال إلى `FINISHED` أو `CANCELLED` أو `EXPIRED`. وبمجرد أن يصل تحويل إلى إحدى هذه الحالات، لا تُرسل أي أحداث Travel Rule أخرى له.
قد يكرر الحدث حالة الحدث السابق، مثلاً عندما لا تجد إعادة الإرسال مساراً مرة أخرى ويبقى التحويل في `COUNTERPARTY_VASP_NOT_FOUND`.
تكون قيمة `rail` إحدى القيم `INTERNAL` أو `TRP` أو `GTR` أو `TRUST` أو `VERIFYVASP` أو `SYGNA` أو `EMAIL` أو `TEST_VASP` (تحويل في البيئة التجريبية أجابه Didit Test VASP، ويحمل أيضاً `test_vasp: true` و`test_vasp_scenario`)، أو `""` طالما لم يُختر أي مسار (وهي دائماً `""` في تسجيلات Sunrise).

عند إنشاء معاملة، أو تسجيل Sunrise، أو استعلام `TRP` وارد، أو الساق الوارد لمسار `INTERNAL`، يرسل Didit `transaction.created` أولاً، ثم تأتي أحداث `travel_rule.status.updated` الخاصة بالمعاملة بعده.
يمكن أن ترسل قاعدة تغيّر حالة المعاملة خلال هذه الخطوة `transaction.status.updated` قبل `transaction.created`.
لا يرسل تسجيل Sunrise الذي يرتبط بمعاملة إيداع أنشأتها مسبقاً أي `transaction.created`.
يُسلَّم كل webhook ويُعاد إرساله بشكل مستقل، لذا قد يصل حدث أُعيد إرساله بعد حدث لاحق. اربط معالجك بـ `transaction_id`، وأعد قراءة المعاملة عندما تحتاج إلى حالتها الحالية.

## أمثلة على مسارات الحالة

### الموافقة المباشرة

```
transaction.created (APPROVED, score 0)
```

### المراجعة ثم الموافقة

```
transaction.created (IN_REVIEW, score 65)
...analyst reviews...
transaction.status.updated (status: APPROVED)
```

### التحقق المعزز

```
transaction.created (AWAITING_USER, action_required.type: verification_session)
...user completes the biometric re-check...
transaction.status.updated (status: APPROVED, action_required: null)
```

### تحويل Travel Rule

```
transaction.created (APPROVED)
travel_rule.status.updated (AWAITING_COUNTERPARTY, rail: EMAIL)
...counterparty accepts the pickup...
travel_rule.status.updated (COMPLETED)
...you broadcast on-chain and PATCH the hash...
travel_rule.status.updated (FINISHED)
```

يأتي الحدثان الأولان من الطلب نفسه، ويُوضع `transaction.created` في قائمة الانتظار أولاً. يُسلَّم كل webhook بشكل مستقل، لذا لا تعتمد على ترتيب الوصول. لا يحصل تحويل مُرسل عبر TRUST أبداً على حدث `COMPLETED`، لأن TRUST لا يعيد شيئاً يستطيع Didit قراءته: يبقى التحويل في `AWAITING_COUNTERPARTY` حتى الموعد النهائي للتأكيد، ثم ينتهي بالحالة `EXPIRED`. أما عبر GTR وVerifyVASP وSygna Bridge، فيقرأ Didit رد الطرف المقابل من الشبكة، ويأتي الحدث بعده.

## التحقق من التوقيع

يُوقَّع كل تسليم بخوارزمية HMAC-SHA256 باستخدام السر المشترك لوجهة webhook، ويحمل ثلاث ترويسات للتوقيع مع طابع زمني:

| الرأس | الآلية |
| - | - |
| `X-Signature-V2` | HMAC على JSON القياسي (مفاتيح مرتبة، مع الحفاظ على Unicode) - **موصى به**. |
| `X-Signature` | HMAC على JSON القياسي المُهَرَّب إلى ASCII (قديم). |
| `X-Signature-Simple` | HMAC على `"{timestamp}:{session_id}:{status}:{webhook_type}"` - **مهمل**. لا يتضمن payload المعاملات أي `session_id`، لذلك يوقّع هذا الرأس `"{timestamp}::{status}:{webhook_type}"`، فهو لا يحدد المعاملة ولا مبلغها ولا درجتها. ولا يتضمن payload من نوع `travel_rule.status.updated` أي `status` أيضاً، لذلك يوقّع `"{timestamp}:::travel_rule.status.updated"`. تحقق باستخدام `X-Signature-V2`، وراجع [إشعار الإهمال](https://docs.didit.me/ar/integration/webhooks.md#x-signature-simple-مهمَل). |
| `X-Timestamp` | ثوانٍ منذ عصر Unix (Unix epoch) - ارفض التسليمات الأقدم من 5 دقائق. |

```typescript theme={null}
import { createHmac, timingSafeEqual } from 'crypto';

function verify(rawBody: string, signatureV2: string, secret: string) {
  const canonical = JSON.stringify(
    Object.keys(JSON.parse(rawBody)).sort()
      .reduce((acc, k) => ({ ...acc, [k]: JSON.parse(rawBody)[k] }), {})
  );
  const expected = createHmac('sha256', secret).update(canonical).digest('hex');
  return timingSafeEqual(Buffer.from(signatureV2), Buffer.from(expected));
}
```

راجع [مرجع webhook](https://docs.didit.me/ar/integration/webhooks.md#التحقق-من-التوقيع) للاطلاع على كود التحقق الكامل، بما في ذلك فحص حداثة الطابع الزمني.

## إعادة المحاولات وضمانات التسليم

* عند استجابة `5xx` أو `404` أو انتهاء المهلة أو فشل الاتصال، يعيد Didit المحاولة حتى **مرتين**: بعد حوالي دقيقة من الفشل الأولي، ثم بعد حوالي 4 دقائق إضافية.
* تعيد التسليمات المعاد محاولتها استخدام `event_id` نفسه، لذا أزل التكرار بالاعتماد عليه في معالج الحدث لديك.
* بعد الفشل النهائي يتوقف تسليم ذلك الحدث وتُبلَّغ مؤسستك، ولا تُعطَّل الوجهات أبداً بصمت.
* أعِد استجابة `2xx` بسرعة (ضع الطلب في قائمة الانتظار ثم عالجه)، فإقرارات الاستلام البطيئة تعرّض الطلبات لانتهاء المهلة وتؤدي إلى تسليمات مكررة.

## idempotency

عالج كل webhook مرة واحدة فقط، وذلك بتخزين `event_id` في مخزن إزالة التكرار الخاص بك. قد تحدث إعادة تسليم للحدث نفسه إذا استغرقت endpoint وقتاً طويلاً في الإقرار بالاستلام (ACK) أو انقطع الاتصال في منتصف الطلب.

## الاشتراك

```bash theme={null}
curl -X POST https://verification.didit.me/v3/webhook/destinations/ \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "label": "TM events",
    "url": "https://yourapp.com/webhooks/didit-tm",
    "subscribed_events": [
      "transaction.created",
      "transaction.status.updated",
      "travel_rule.status.updated"
    ]
  }'
```

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

> [card] [مرجع webhook](https://docs.didit.me/ar/integration/webhooks.md)
>
> الوجهات والتوقيع وإعادة المحاولات.

> [card] [الحالات](https://docs.didit.me/ar/transaction-monitoring/statuses.md)
>
> آلة الحالات الكاملة.

> [card] [دليل التكامل](https://docs.didit.me/ar/transaction-monitoring/integration-guide.md)
>
> معالجة webhook في الإنتاج.


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