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

# Praktik Terbaik Webhook

> Pastikan penanganan webhook yang andal

## Pengenalan

Mengikuti praktik terbaik ini memastikan integrasi webhook Anda andal, aman, dan dapat diskalakan.

<Callout type="info">
  Menerapkan praktik-praktik ini akan membantu Anda menghindari masalah webhook yang umum.
</Callout>

## 1. Keamanan

### Selalu Validasi Autentikasi

```javascript theme={null}
const SECRET_KEY = process.env.WEBHOOK_SECRET_KEY;

app.post('/webhook', (req, res) => {
  const authHeader = req.headers.authorization;
  
  if (authHeader !== SECRET_KEY) {
    return res.status(401).json({ error: 'Unauthorized' });
  }
  
  // Proses webhook...
  res.status(200).send('OK');
});
```

<AccordionGroup>
  <Accordion title="Gunakan Environment Variables">
    Jangan pernah hardcode secret key. Gunakan environment variables atau secrets management.
  </Accordion>

  <Accordion title="Rotasi Key Secara Berkala">
    Ubah secret key Anda secara berkala untuk meningkatkan keamanan.
  </Accordion>

  <Accordion title="Gunakan HTTPS">
    Selalu gunakan HTTPS untuk endpoint webhook produksi.
  </Accordion>

  <Accordion title="Rate Limit">
    Implementasikan rate limiting untuk mencegah penyalahgunaan.
  </Accordion>
</AccordionGroup>

## 2. Keandalan

### Respons dengan Cepat

<Callout>
  Proses webhook secara efisien dan respons dalam 5 detik untuk menghindari timeout.
</Callout>

```javascript theme={null}
app.post('/webhook', async (req, res) => {
  // Acknowledge segera
  res.status(200).send('OK');
  
  // Proses di background
  setImmediate(async () => {
    await processWebhook(req.body);
  });
});
```

### Kembalikan 200 OK

Selalu kembalikan HTTP 200 untuk mengonfirmasi penerimaan:

```javascript theme={null}
app.post('/webhook', (req, res) => {
  try {
    // Proses webhook...
    res.status(200).send('OK');
  } catch (error) {
    // Tetap kembalikan 200 untuk error pemrosesan untuk menghindari retry
    console.error('Error pemrosesan:', error);
    res.status(200).send('OK');
  }
});
```

### Tangani Error dengan Baik

```javascript theme={null}
app.post('/webhook', async (req, res) => {
  try {
    // Validasi payload
    const { event, message } = req.body;
    
    if (!event || !message) {
      console.error('Struktur payload tidak valid');
      return res.status(400).send('Invalid payload');
    }
    
    // Proses event
    await handleEvent(event, message);
    
    res.status(200).send('OK');
  } catch (error) {
    console.error('Error webhook:', error);
    
    // Tentukan apakah retry diperlukan
    if (isTemporaryError(error)) {
      res.status(500).send('Error sementara');
    } else {
      res.status(200).send('OK');
    }
  }
});
```

### Log Semua

<Note>
  Pertahankan log detail pengiriman webhook untuk debugging dan analitik.
</Note>

```javascript theme={null}
app.post('/webhook', async (req, res) => {
  const startTime = Date.now();
  
  logger.info('Webhook diterima', {
    event: req.body.event,
    timestamp: req.body.timestamp,
    ip: req.ip,
    userAgent: req.headers['user-agent']
  });
  
  try {
    await processWebhook(req.body);
    
    const duration = Date.now() - startTime;
    logger.info('Webhook diproses', {
      duration: duration + 'ms',
      status: 'success'
    });
    
    res.status(200).send('OK');
  } catch (error) {
    const duration = Date.now() - startTime;
    logger.error('Webhook gagal', {
      duration: duration + 'ms',
      error: error.message,
      stack: error.stack
    });
    
    res.status(500).send('Error');
  }
});
```

## 3. Idempotensi

<Callout type="warning">
  <strong>Kritis:</strong> Gunakan message ID untuk deduplikasi event.
</Callout>

### Implementasikan Deduplikasi

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

app.post('/webhook', async (req, res) => {
  const { event, message } = req.body;
  const eventId = `${event}_${message.id}`;
  
  // Periksa apakah sudah diproses
  if (processedEvents.has(eventId)) {
    logger.info('Event duplikat dilewati', { eventId });
    return res.status(200).send('OK');
  }
  
  // Proses event
  await processEvent(event, message);
  
  // Tandai sebagai sudah diproses
  processedEvents.add(eventId);
  
  res.status(200).send('OK');
});
```

### Gunakan 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
    const existing = await db.ProcessedEvent.findOne({ event_id: eventId });
    
    if (existing) {
      return res.status(200).send('OK');
    }
    
    // Proses event
    await processEvent(event, message);
    
    // Simpan event yang diproses
    await db.ProcessedEvent.create({
      event_id: eventId,
      processed_at: new Date(),
      event_type: event
    });
    
    res.status(200).send('OK');
  } catch (error) {
    console.error('Error webhook:', error);
    res.status(500).send('Error');
  }
});
```

## 4. Skalabilitas

### Pemrosesan Async

<Callout>
  Jangan blokir respons webhook pada operasi yang memakan waktu.
</Callout>

```javascript theme={null}
// Contoh sistem queue
const queue = new Queue('webhook-processor', {
  redis: process.env.REDIS_URL
});

app.post('/webhook', async (req, res) => {
  // Tambahkan ke queue
  await queue.add(req.body);
  
  // Respons segera
  res.status(200).send('OK');
});

// Proses queue secara terpisah
queue.process(async (job) => {
  const { event, message } = job.data;
  await processEvent(event, message);
});
```

### Queue Tugas Berat

```javascript theme={null}
// Contoh dengan Bull (Node.js)
const Queue = require('bull');
const webhookQueue = new Queue('webhooks', {
  redis: { port: 6379, host: 'localhost' }
});

app.post('/webhook', async (req, res) => {
  await webhookQueue.add('process', req.body);
  res.status(200).send('OK');
});

webhookQueue.process('process', async (job) => {
  await processWebhook(job.data);
});
```

### Optimasi Database

```javascript theme={null}
// Gunakan index untuk lookup lebih cepat
db.ProcessedEvent.collection.createIndex(
  { event_id: 1 },
  { unique: true }
);

db.ProcessedEvent.collection.createIndex(
  { processed_at: 1 },
  { expireAfterSeconds: 604800 } // TTL 7 hari
);
```

### Load Testing

<Callout type="info">
  Uji endpoint webhook di bawah volume tinggi untuk memastikan dapat menangani traffic puncak.
</Callout>

```bash theme={null}
# Gunakan Artillery untuk load testing
# config.yaml
config:
  target: "https://your-webhook-url.com/webhook"
  phases:
    - duration: 60
      arrivalRate: 100  # 100 request per detik

scenarios:
  - name: "Webhook Test"
    flow:
      - post:
          url: "/"
          json:
            event: "channel.message_in"
            timestamp: 1738056000
```

## 5. Monitoring

### Lacak Status Pengiriman

```javascript theme={null}
const metrics = {
  total: 0,
  success: 0,
  failure: 0,
  duplicate: 0
};

app.post('/webhook', async (req, res) => {
  metrics.total++;
  
  try {
    // Pemeriksaan deduplikasi
    if (isDuplicate(req.body)) {
      metrics.duplicate++;
      return res.status(200).send('OK');
    }
    
    // Proses webhook
    await processWebhook(req.body);
    metrics.success++;
    
    res.status(200).send('OK');
  } catch (error) {
    metrics.failure++;
    res.status(500).send('Error');
  }
  
  // Laporkan metrik setiap 1000 request
  if (metrics.total % 1000 === 0) {
    const successRate = ((metrics.success / metrics.total) * 100).toFixed(2);
    console.log(`Metrik webhook: ${JSON.stringify(metrics)}`);
    console.log(`Tingkat keberhasilan: ${successRate}%`);
  }
});
```

### Alert pada Kegagalan

```javascript theme={null}
// Konfigurasi alert
const ALERT_THRESHOLDS = {
  failureRate: 0.05, // 5%
  consecutiveFailures: 10,
  responseTime: 5000 // 5 detik
};

let consecutiveFailures = 0;

app.post('/webhook', async (req, res) => {
  const startTime = Date.now();
  
  try {
    await processWebhook(req.body);
    consecutiveFailures = 0;
    
    const duration = Date.now() - startTime;
    
    // Periksa waktu respons
    if (duration > ALERT_THRESHOLDS.responseTime) {
      await sendAlert({
        level: 'warning',
        message: `Respons webhook lambat: ${duration}ms`
      });
    }
    
    res.status(200).send('OK');
  } catch (error) {
    consecutiveFailures++;
    
    // Periksa tingkat kegagalan
    const failureRate = metrics.failure / metrics.total;
    if (failureRate > ALERT_THRESHOLDS.failureRate) {
      await sendAlert({
        level: 'critical',
        message: `Tingkat kegagalan webhook tinggi: ${(failureRate * 100).toFixed(2)}%`
      });
    }
    
    // Periksa kegagalan berturut-turut
    if (consecutiveFailures >= ALERT_THRESHOLDS.consecutiveFailures) {
      await sendAlert({
        level: 'critical',
        message: `${consecutiveFailures} kegagalan webhook berturut-turut`
      });
    }
    
    res.status(500).send('Error');
  }
});
```

### Monitor Waktu Respons

```javascript theme={null}
const responseTimes = [];

app.post('/webhook', async (req, res) => {
  const startTime = Date.now();
  
  try {
    await processWebhook(req.body);
    
    const duration = Date.now() - startTime;
    responseTimes.push(duration);
    
    // Simpan 1000 pengukuran terakhir
    if (responseTimes.length > 1000) {
      responseTimes.shift();
    }
    
    // Hitung metrik
    const avg = responseTimes.reduce((a, b) => a + b, 0) / responseTimes.length;
    const p95 = responseTimes.sort((a, b) => a - b)[Math.floor(responseTimes.length * 0.95)];
    
    if (avg > 3000) { // > 3 detik rata-rata
      console.warn(`Waktu respons rata-rata tinggi: ${avg}ms`);
    }
    
    if (p95 > 5000) { // > 5 detik P95
      console.warn(`Waktu respons P95 tinggi: ${p95}ms`);
    }
    
    res.status(200).send('OK');
  } catch (error) {
    res.status(500).send('Error');
  }
});
```

## 6. Integritas Data

### Validasi Payload

```javascript theme={null}
app.post('/webhook', (req, res) => {
  const { event, message } = req.body;
  
  // Validasi field wajib
  if (!event) {
    return res.status(400).send('Field event tidak ada');
  }
  
  if (!message) {
    return res.status(400).send('Field message tidak ada');
  }
  
  // Validasi jenis event
  const validEvents = [
    'channel.message_in',
    'agent.message_out',
    'ai.message_generated',
    'device.connected',
    'device.disconnected'
  ];
  
  if (!validEvents.includes(event)) {
    console.warn(`Jenis event tidak dikenal: ${event}`);
  }
  
  // Validasi message ID
  if (!message.id) {
    return res.status(400).send('Message ID tidak ada');
  }
  
  // Proses webhook...
  res.status(200).send('OK');
});
```

### Tangani Data yang Hilang

```javascript theme={null}
function safeGet(obj, path, defaultValue = null) {
  return path.split('.').reduce((acc, part) => acc && acc[part], obj) || defaultValue;
}

app.post('/webhook', (req, res) => {
  const { message } = req.body;
  
  // Akses field nested dengan aman
  const senderName = safeGet(message, 'sender_name', 'Unknown');
  const channelName = safeGet(message, 'channel.name', 'Unknown');
  const contentType = safeGet(message, 'content.type', 'unknown');
  
  // Proses dengan default yang aman
  console.log(`Pesan dari ${senderName} via ${channelName}`);
  
  res.status(200).send('OK');
});
```

### Kompatibilitas Versi

<Note>
  Bersiaplah untuk penambahan dan perubahan field di masa depan.
</Note>

```javascript theme={null}
// Contoh: Tangani field yang tidak dikenal dengan baik
app.post('/webhook', async (req, res) => {
  const { event, message } = req.body;
  
  // Proses field yang dikenal
  await processMessage(message);
  
  // Simpan payload lengkap untuk referensi masa depan
  await db.RawWebhook.create({
    payload: req.body,
    received_at: new Date(),
    processed: true
  });
  
  res.status(200).send('OK');
});
```

## Checklist

<Callout>
  Gunakan checklist ini untuk memastikan implementasi webhook Anda mengikuti praktik terbaik.
</Callout>

### Keamanan

* [ ] Validasi header `Authorization` pada setiap request
* [ ] Gunakan environment variables untuk secret key
* [ ] Aktifkan HTTPS untuk produksi
* [ ] Implementasikan rate limiting
* [ ] Jangan pernah log informasi sensitif

### Keandalan

* [ ] Respons dalam 5 detik
* [ ] Selalu kembalikan HTTP 200 untuk sukses
* [ ] Tangani error dengan baik
* [ ] Implementasikan logging komprehensif
* [ ] Uji dengan curl atau tool pengujian webhook

### Idempotensi

* [ ] Gunakan `message.id` untuk deduplikasi
* [ ] Simpan ID event yang diproses
* [ ] Tangani retry dengan benar
* [ ] Implementasikan operasi idempoten

### Skalabilitas

* [ ] Gunakan pemrosesan async
* [ ] Queue tugas berat
* [ ] Optimalkan query database
* [ ] Lakukan load testing

### Monitoring

* [ ] Lacak status pengiriman
* [ ] Siapkan alert kegagalan
* [ ] Monitor waktu respons
* [ ] Pertahankan log error

### Integritas Data

* [ ] Validasi struktur payload
* [ ] Tangani data yang hilang dengan baik
* [ ] Kompatibel dengan versi
* [ ] Backup data webhook penting

## Langkah Selanjutnya

<CardGroup cols={2}>
  <Card title="Contoh" icon="code" href="/webhooks/examples">
    Sampel kode implementasi
  </Card>

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