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

# Mekanisme Retry

> Kebijakan retry webhook otomatis

## Pengenalan

Jika endpoint webhook Anda gagal merespons atau mengembalikan status code non-2xx, CSKU AI akan secara otomatis mencoba mengirim ulang webhook.

<Callout>
  Mekanisme retry memastikan webhook Anda terkirim meskipun terjadi masalah sementara.
</Callout>

## Konfigurasi Retry

| Parameter      | Nilai                    |
| -------------- | ------------------------ |
| Maksimum Retry | 3 kali                   |
| Jeda Retry     | 30 detik antar percobaan |
| Metode Retry   | Exponential backoff      |

## Proses Retry

```
Request Awal → Gagal
       ↓
   Tunggu 30 detik
       ↓
   Retry 1 → Gagal
       ↓
   Tunggu 30 detik
       ↓
   Retry 2 → Gagal
       ↓
   Tunggu 30 detik
       ↓
   Retry 3 → Gagal
       ↓
   Tandai sebagai kegagalan permanen
```

<Callout type="warning">
  Setelah 3 percobaan yang gagal, webhook ditandai sebagai `failed_permanent` dan tidak akan di-retry lagi.
</Callout>

## Memicu Retry

Webhook di-retry ketika:

<AccordionGroup>
  <Accordion title="Tidak Ada Respons">
    Server Anda tidak merespons dalam periode timeout.
  </Accordion>

  <Accordion title="Status Non-2xx">
    Server Anda mengembalikan status code di luar rentang 200-299.
  </Accordion>

  <Accordion title="Error Jaringan">
    Koneksi gagal karena masalah jaringan.
  </Accordion>

  <Accordion title="Error Server">
    Server Anda mengembalikan status 500 atau error serupa.
  </Accordion>
</AccordionGroup>

## Respons Sukses

Webhook dianggap berhasil terkirim ketika server Anda mengembalikan:

```
HTTP 200 OK
```

<Note>
  Konten body respons tidak penting. Hanya status code yang dipertimbangkan.
</Note>

## Kegagalan Permanen

Setelah 3 percobaan yang gagal, webhook ditandai sebagai kegagalan permanen dan tidak akan di-retry. Anda dapat:

* Memeriksa webhook yang gagal di dashboard CSKU AI Anda
* Query tabel `tbl_resend_webhook` untuk percobaan yang gagal
* Mengirim ulang webhook secara manual jika diperlukan

## Idempotensi

<Callout type="error">
  <strong>Kritis:</strong> Endpoint webhook Anda harus idempoten. Event yang sama mungkin diterima beberapa kali.
</Callout>

### Mengapa Idempotensi Penting

Karena retry, webhook Anda mungkin menerima event yang sama beberapa kali. Tanpa idempotensi, Anda bisa:

* Memproses pesan yang sama dua kali
* Mengirim notifikasi duplikat
* Membuat record database duplikat
* Menagih pelanggan dua kali

### Mengimplementasikan Idempotensi

#### Menggunakan Message ID

```javascript theme={null}
const processedEvents = new Set();

app.post('/webhook', async (req, res) => {
  const { event, message } = req.body;
  
  // Buat identifier event unik
  const eventId = `${event}_${message.id}`;
  
  // Periksa apakah sudah diproses
  if (processedEvents.has(eventId)) {
    console.log('Event duplikat, melewati');
    return res.status(200).send('OK');
  }
  
  try {
    // Proses event
    await handleEvent(event, message);
    
    // Tandai sebagai sudah diproses
    processedEvents.add(eventId);
    
    // Opsional: Persist ke database
    await db.processedEvents.create({ event_id: eventId });
    
    res.status(200).send('OK');
  } catch (error) {
    console.error('Error memproses webhook:', error);
    res.status(500).send('Error');
  }
});
```

#### Menggunakan Database untuk Persistensi

```javascript theme={null}
app.post('/webhook', async (req, res) => {
  const { event, message } = req.body;
  const eventId = `${event}_${message.id}`;
  
  try {
    // Periksa database untuk event yang ada
    const existing = await db.processedEvents.findOne({ event_id: eventId });
    
    if (existing) {
      console.log('Event sudah diproses, melewati');
      return res.status(200).send('OK');
    }
    
    // Proses event
    await handleEvent(event, message);
    
    // Simpan sebagai sudah diproses
    await db.processedEvents.create({ event_id: eventId, processed_at: new Date() });
    
    res.status(200).send('OK');
  } catch (error) {
    console.error('Error memproses webhook:', error);
    // Jangan kembalikan 200 pada error untuk memicu retry
    res.status(500).send('Error');
  }
});
```

### Membersihkan Event yang Diproses

```javascript theme={null}
// Pembersihan in-memory (batasi ke 10,000 event)
if (processedEvents.size > 10000) {
  const eventsArray = Array.from(processedEvents);
  processedEvents.clear();
  eventsArray.slice(5000).forEach(id => processedEvents.add(id));
}

// Pembersihan database (hapus event lebih dari 7 hari)
await db.processedEvents.deleteMany({
  processed_at: { $lt: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000) }
});
```

## Praktik Terbaik untuk Retry

<AccordionGroup>
  <Accordion title="Respons Cepat">
    Proses webhook dengan cepat dan respons dalam 5 detik untuk menghindari timeout.
  </Accordion>

  <Accordion title="Kembalikan 200 pada Sukses">
    Selalu kembalikan HTTP 200 setelah berhasil memproses webhook.
  </Accordion>

  <Accordion title="Kembalikan 500 pada Error Pemrosesan">
    Kembalikan status 5xx untuk memicu retry jika Anda mengalami error pemrosesan sementara.
  </Accordion>

  <Accordion title="Log Semua Percobaan">
    Log pengiriman webhook yang berhasil maupun gagal untuk debugging.
  </Accordion>

  <Accordion title="Implementasikan Backoff">
    Gunakan exponential backoff jika Anda melakukan retry operasi dalam handler webhook Anda.
  </Accordion>
</AccordionGroup>

## Contoh: Handler Retry Komprehensif

```javascript theme={null}
const processedEvents = new Map(); // Simpan eventId dengan timestamp

app.post('/webhook', async (req, res) => {
  const { event, message } = req.body;
  const eventId = `${event}_${message.id}`;
  const now = Date.now();
  
  // Periksa duplikat (dalam 1 jam)
  const existing = processedEvents.get(eventId);
  if (existing && (now - existing.timestamp) < 3600000) {
    console.log('Event duplikat:', eventId);
    return res.status(200).send('OK');
  }
  
  try {
    // Proses event
    await handleEvent(event, message);
    
    // Tandai sebagai sudah diproses
    processedEvents.set(eventId, { timestamp: now });
    
    // Bersihkan event lama (lebih dari 24 jam)
    const hourAgo = now - 86400000;
    for (const [id, data] of processedEvents.entries()) {
      if (data.timestamp < hourAgo) {
        processedEvents.delete(id);
      }
    }
    
    res.status(200).send('OK');
  } catch (error) {
    console.error('Error pemrosesan webhook:', error);
    
    // Tentukan apakah retry harus terjadi
    if (isTemporaryError(error)) {
      // Kembalikan 500 untuk memicu retry
      res.status(500).send('Error sementara');
    } else {
      // Kembalikan 200 untuk menghindari retry (kegagalan idempoten)
      res.status(200).send('OK');
    }
  }
});

function isTemporaryError(error) {
  // Retry pada masalah koneksi database, error jaringan, dll.
  return (
    error.code === 'ECONNREFUSED' ||
    error.code === 'ETIMEDOUT' ||
    error.name === 'DatabaseConnectionError'
  );
}
```

## Monitoring Retry

### Lacak Metrik Retry

```javascript theme={null}
const metrics = {
  attempts: 0,
  successes: 0,
  failures: 0,
  duplicates: 0
};

app.post('/webhook', async (req, res) => {
  metrics.attempts++;
  
  const { event, message } = req.body;
  const eventId = `${event}_${message.id}`;
  
  // Periksa duplikat
  if (isDuplicate(eventId)) {
    metrics.duplicates++;
    return res.status(200).send('OK');
  }
  
  try {
    await handleEvent(event, message);
    metrics.successes++;
    
    // Log metrik setiap 100 request
    if (metrics.attempts % 100 === 0) {
      console.log('Metrik webhook:', metrics);
    }
    
    res.status(200).send('OK');
  } catch (error) {
    metrics.failures++;
    console.error('Error webhook:', error);
    res.status(500).send('Error');
  }
});
```

### Contoh Output Metrik

```
Metrik webhook: {
  attempts: 100,
  successes: 95,
  failures: 3,
  duplicates: 2
}
```

<Callout type="info">
  Jumlah duplikat yang tinggi mungkin menandakan webhook Anda memproses lambat, menyebabkan retry.
</Callout>

## Troubleshooting Retry

### Duplikat Berlebihan

**Gejala:** Jumlah event duplikat yang tinggi

**Solusi:**

* Periksa waktu pemrosesan webhook (harus \< 5 detik)
* Optimalkan query database
* Gunakan pemrosesan async untuk operasi berat
* Periksa resource server (CPU, memory)

### Kegagalan Permanen

**Gejala:** Semua webhook ditandai sebagai kegagalan permanen

**Solusi:**

* Verifikasi URL webhook benar dan dapat diakses
* Periksa autentikasi (secret key)
* Uji endpoint dengan curl
* Tinjau log server untuk error
* Pastikan firewall mengizinkan koneksi masuk

### Kegagalan Intermiten

**Gejala:** Beberapa webhook berhasil, yang lain gagal

**Solusi:**

* Implementasikan penanganan error yang tepat
* Gunakan exponential backoff untuk retry
* Periksa stabilitas koneksi database
* Monitor penggunaan resource server
* Implementasikan health check

## Langkah Selanjutnya

<CardGroup cols={2}>
  <Card title="Praktik Terbaik" icon="check-circle" href="/webhooks/best-practices">
    Pastikan penanganan webhook yang andal
  </Card>

  <Card title="Troubleshooting" icon="alert-triangle" href="/webhooks/troubleshooting">
    Debug masalah webhook
  </Card>
</CardGroup>
