> ## Documentation Index
> Fetch the complete documentation index at: https://docs.scanova.io/llms.txt
> Use this file to discover all available pages before exploring further.

# تكرار الطلبات والإرسال مرة أخرى

> كيف تعيد إرسال أحداث الخادم الفاشلة دون إنشاء تحويلات مكررة، باستخدام قيمة event_id ثابتة ومهلة انتظار تتضاعف مع كل محاولة جديدة.

تقع أعطال الشبكة وأخطاء الخادم المؤقتة. وواجهة أحداث الخادم مصممة لتتحمل إعادة الإرسال بأمان — ما دمت تتبع نمط `event_id`.

## كيف يعمل منع التكرار

يقبل كل حدث حقل `event_id`. وعندما تصل القيمة `event_id` نفسها أكثر من مرة، تضع المعالجة علامة تكرار على الوصول الثاني وتستبعده من التحليلات. وتبقى أعداد التحويلات لديك دقيقة حتى لو أعدت إرسال الطلب مرات عدة.

**القاعدة:** أنشئ `event_id` مرة واحدة قبل المحاولة الأولى، واستخدم القيمة نفسها في كل إعادة إرسال لذلك الحدث.

## نمط التنفيذ

```javascript theme={null}
// Node.js example
import crypto from 'node:crypto';

async function sendWithRetry(eventPayload, maxAttempts = 5) {
  // Generate event_id once — do not regenerate on retry
  const payload = {
    ...eventPayload,
    event_id: eventPayload.event_id ?? crypto.randomUUID(),
  };

  let delay = 1000; // start at 1 second

  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
    try {
      const response = await fetch('https://track.scanova.io/server-events', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'X-API-Key': process.env.SCANOVA_API_KEY,
        },
        body: JSON.stringify(payload),
        signal: AbortSignal.timeout(10_000), // 10s timeout
      });

      // Do not retry on client errors — fix the payload or auth first
      if (response.status >= 400 && response.status < 500) {
        const error = await response.json();
        throw new Error(`Permanent error ${response.status}: ${JSON.stringify(error)}`);
      }

      if (response.ok) return; // success

      // Server error (5xx) — retry
    } catch (err) {
      if (attempt === maxAttempts) throw err;
    }

    await new Promise(resolve => setTimeout(resolve, delay));
    delay = Math.min(delay * 2, 30_000); // cap at 30 seconds
  }
}
```

```python theme={null}
# Python example
import os
import time
import uuid
import requests

def send_with_retry(event_payload: dict, max_attempts: int = 5) -> None:
    payload = {**event_payload, "event_id": event_payload.get("event_id") or str(uuid.uuid4())}
    delay = 1.0

    for attempt in range(1, max_attempts + 1):
        try:
            response = requests.post(
                "https://track.scanova.io/server-events",
                headers={
                    "Content-Type": "application/json",
                    "X-API-Key": os.environ["SCANOVA_API_KEY"],
                },
                json=payload,
                timeout=10,
            )

            if 400 <= response.status_code < 500:
                raise ValueError(f"Permanent error {response.status_code}: {response.text}")

            if response.ok:
                return

        except ValueError:
            raise  # do not retry permanent errors
        except Exception:
            if attempt == max_attempts:
                raise

        time.sleep(delay)
        delay = min(delay * 2, 30)  # cap at 30 seconds
```

## جدول إعادة الإرسال

| المحاولة | الانتظار قبلها |
| -------- | -------------- |
| 1        | فورًا          |
| 2        | ثانية واحدة    |
| 3        | ثانيتان        |
| 4        | 4 ثوانٍ        |
| 5        | 8 ثوانٍ        |

اجعل حد الانتظار بين 30 و60 ثانية. وبعد 5 محاولات فاشلة، سجّل الحدث وأطلق تنبيهًا — ولا تعد الإرسال بلا نهاية.

## متى تعيد الإرسال ومتى تتوقف

| الحالة                               | الإجراء                                                         |
| ------------------------------------ | --------------------------------------------------------------- |
| انتهاء مهلة الشبكة أو خطأ في الاتصال | أعد الإرسال مع انتظار متزايد                                    |
| `429` Too Many Requests              | أعد الإرسال بعد المدة في ترويسة `Retry-After` (أو بعد 60 ثانية) |
| `500` و`502` و`503` و`504`           | أعد الإرسال مع انتظار متزايد                                    |
| `400` Bad Request                    | توقف — صحّح محتوى الطلب                                         |
| `401` Unauthorized                   | توقف — راجع مفتاح API لديك                                      |
| `403` Forbidden                      | توقف — تحقق من تطابق المفتاح مع site\_id                        |
| `422` Unprocessable Entity           | توقف — صحّح خطأ التحقق في الحقل                                 |

## حفظ event\_id للأحداث الحرجة

في التحويلات عالية القيمة (عمليات الشراء والاشتراكات)، أنشئ `event_id` واحفظه قبل استدعاء الشبكة — لتتمكن من إعادة الإرسال حتى بعد إعادة تشغيل العملية:

```javascript theme={null}
// Generate and store before the API call
const eventId = crypto.randomUUID();
await db.trackingEvents.create({ eventId, status: 'pending', orderId });

try {
  await sendWithRetry({ event_id: eventId, event_name: 'purchase', ... });
  await db.trackingEvents.update({ eventId, status: 'sent' });
} catch (err) {
  await db.trackingEvents.update({ eventId, status: 'failed' });
  // Retry from your job queue
}
```
