# Keamanan & retry

> Cara memverifikasi request webhook, aturan sukses, jadwal retry, dan checklist saat webhook tidak masuk.


Siapa pun yang tahu URL webhook Anda bisa mengirim request palsu. Karena itu, setiap request dari CSKU membawa Secret Key Anda di header `Authorization`. Tolak semua request yang nilainya tidak cocok.

## Format request

```http
POST /csku/webhook HTTP/1.1
Host: api.toko-anda.com
Content-Type: application/json
Authorization: 3f9a1c7e5b2d4f6a8c0e1b3d5f7a9c2e4b6d8f0a1c3e5b7d9f2a4c6e8b0d1f3a

{"event":"channel.message_in","timestamp":1792041600, ...}
```

- `Authorization` berisi Secret Key Anda **apa adanya**: tanpa awalan `Bearer`, tanpa base64.
- Tidak ada header signature atau timestamp terpisah. Verifikasi cukup dengan mencocokkan `Authorization`.
- Secret Key ada di **Pengaturan → Secret Key**. Lihat [Autentikasi](https://docs.csku.ai/api/autentikasi).

## Verifikasi Authorization

Bandingkan dengan fungsi constant-time, bukan `==`, agar waktu perbandingan tidak membocorkan isi secret.

```js
import crypto from 'node:crypto'

function isFromCsku(header, secret) {
  const a = Buffer.from(header || '')
  const b = Buffer.from(secret)
  return a.length === b.length && crypto.timingSafeEqual(a, b)
}
```

```php
<?php
$ok = hash_equals(getenv('CSKU_SECRET'), $_SERVER['HTTP_AUTHORIZATION'] ?? '');
```

```python
import hmac
ok = hmac.compare_digest(request.headers.get("Authorization", ""), SECRET)
```

```go
ok := subtle.ConstantTimeCompare([]byte(r.Header.Get("Authorization")), secret) == 1
```

Jika tidak cocok, balas `{"success": false}` dan jangan proses payload.

> **Penting: Simpan secret di server**
>
> Secret Key yang sama dipakai untuk memanggil Public API. Simpan hanya di server (environment variable atau secret manager). Jangan taruh di kode frontend, aplikasi mobile, repository publik, atau log.

## Ganti (rotate) secret

Reset Secret Key di **Pengaturan → Secret Key** (butuh OTP ke email owner). Yang berubah:

- Webhook berikutnya, termasuk retry, langsung memakai secret baru di header `Authorization`.
- Panggilan Public API dengan secret lama ditolak.

Urutan aman:

1. Siapkan server agar sementara menerima secret lama **atau** baru.
2. Reset secret di dashboard, lalu simpan secret baru di server.
3. Hapus secret lama dari server.

## Aturan sukses

CSKU menganggap pengiriman **sukses** hanya jika body respons Anda berupa JSON dengan field `success` bernilai benar:

```json
{ "success": true }
```

Nilai `true`, `"true"`, atau `1` diterima. Status HTTP **tidak** dibaca, jadi `200` dengan body kosong tetap dianggap gagal.

Dianggap **gagal**:

- Tidak ada respons dalam 30 detik (timeout).
- Koneksi gagal (DNS, TLS, server mati).
- Body bukan JSON, atau field `success` tidak ada.
- `success` bernilai `false`.

## Jadwal retry

Pengiriman yang gagal dicoba ulang otomatis. Total maksimal 4 percobaan:

| Percobaan | Waktu (perkiraan) |
| --- | --- |
| 1 | Saat event terjadi |
| 2 | Langsung setelah percobaan 1 gagal |
| 3 | Sekitar 30 detik setelah percobaan 2 |
| 4 | Sekitar 30 detik setelah percobaan 3 |

Setelah percobaan ke-4 gagal, event itu dilepas dan tidak dikirim lagi. Jika server Anda mati lebih dari sekitar satu menit, ambil pesan yang terlewat lewat [Ambil pesan](https://docs.csku.ai/api/ambil-pesan).

## Idempotensi dan urutan

- **Deduplikasi dengan `message.id`.** Event yang sama bisa datang lebih dari sekali, misalnya jika server Anda sudah memproses tetapi respons terlambat. Simpan `message.id` yang sudah diproses dan abaikan duplikat. Jangan pakai `timestamp`, karena nilainya berubah di setiap percobaan.
- **Urutan tidak dijamin.** Setiap event dikirim sendiri-sendiri. Retry bisa membuat event lama datang setelah event baru. Jika urutan penting, urutkan di sisi Anda, atau ambil riwayat lewat [Ambil pesan](https://docs.csku.ai/api/ambil-pesan).

## Respons cepat, proses belakangan

Batas waktu 30 detik termasuk semua yang Anda kerjakan sebelum membalas. Pola yang aman:

1. Verifikasi `Authorization`.
2. Simpan payload ke antrean atau database.
3. Balas `{"success": true}`.
4. Proses (panggil sistem lain, kirim notifikasi) di background job.

## Checklist jika webhook tidak masuk

- Paket Anda masih punya fitur **Webhook** dan masih aktif. Lihat [Berlangganan & kredit](https://docs.csku.ai/panduan/berlangganan).
- URL di **Pengaturan → Webhook** benar, memakai `https`, dan bisa diakses dari internet (bukan `localhost` atau IP privat).
- Sertifikat TLS server valid dan tidak kedaluwarsa.
- Firewall atau WAF (misalnya proteksi bot) tidak memblokir request `POST` dari server CSKU.
- Server membaca header `Authorization` dengan benar. Di Apache + PHP, header ini kadang tidak diteruskan.
- Secret di server sama dengan Secret Key terbaru. Jika Anda baru reset, perbarui juga di server.
- Respons Anda berupa JSON `{"success": true}`, bukan teks atau body kosong.
- Respons dikirim dalam waktu kurang dari 30 detik.
- Event yang Anda tunggu memang dikirim. Pesan dari Public API dan Proxy WABA tidak memicu `agent.message_out`. Lihat [Event & payload](https://docs.csku.ai/webhook/event).
