# Analisis: Webhook Kadang Tidak Diterima dari Accurate

Dokumen ini menganalisis penyebab webhook dari Accurate IRIS kadang tidak diterima atau tidak terproses oleh layanan Golang (`golang-webhook-service`).

---

## Arsitektur Singkat

1. **Accurate IRIS** mengirim HTTP POST ke URL yang dikonfigurasi (VPS Golang).
2. **Golang service** menerima request → simpan ke SQLite `webhook_queue` → kirim ke worker pool.
3. **Worker** memproses job → relay ke shared hosting (PHP receiver).

Jika salah satu tahap gagal atau lambat, webhook bisa “tidak diterima” dari sudut pandang Accurate (timeout/error) atau dari sudut pandang kita (data tidak sampai/terproses).

---

## Penyebab yang Mungkin

### 1. Format Payload Berbeda (Array vs Single Object)

**Kode saat ini** (`handlers/webhook.go`) hanya menerima **array**:

```go
var payloads []models.WebhookPayload
if err := json.Unmarshal(body, &payloads); err != nil {
    http.Error(w, "Invalid JSON", http.StatusBadRequest)
    return
}
```

Jika Accurate kadang mengirim **satu objek** `{"type":"SALES_INVOICE", "data":[...]}` (bukan `[{...}]`), `json.Unmarshal` gagal → response **400 Bad Request**. Accurate bisa menganggap endpoint error dan tidak retry dengan benar.

**Solusi:** Terima **array dan single object**. Jika body adalah object, bungkus jadi slice satu elemen.

---

### 2. Worker Pool Penuh → Job “Dropped”

**Kode saat ini** (`worker/pool.go`):

```go
func (p *Pool) Submit(queueID int64) {
    select {
    case p.jobs <- queueID:
    default:
        log.Printf("⚠️ Worker pool is full, job %d dropped", queueID)
    }
}
```

- Channel `jobs` berukuran 1000.
- Jika semua worker sibuk dan channel penuh, job **tidak dimasukkan** ke channel (hanya log “dropped”).
- Job **sudah tersimpan** di DB dengan status `pending`, tapi **tidak ada yang memproses** sampai service **restart** (startup akan mengambil lagi `pending`/`failed`).

Efek: request ke Accurate **200 OK**, tapi webhook “kadang tidak jalan” karena tidak pernah di-pick worker sampai restart.

**Solusi:** Jangan drop. Saat channel penuh, **tunggu dengan timeout** (mis. 30 detik) agar ada slot; kalau tetap penuh, job tetap di DB dan akan diambil saat restart. Atau perbesar channel / jumlah worker.

---

### 3. Timeout di Sisi Accurate / Reverse Proxy

Banyak penyedia webhook (dan reverse proxy) menganggap request gagal jika **tidak dapat response dalam 5–15 detik**.

**Kode saat ini** (`main.go`): server HTTP **tanpa** `ReadTimeout` / `WriteTimeout` / `ReadHeaderTimeout`.

- Jika DB SQLite sibuk (lock) atau banyak request bersamaan, handler bisa lama baru bisa baca body / tulis response.
- Connection menggantung lama → Accurate atau proxy bisa **timeout** dan putus. Dari sisi Accurate: “webhook tidak diterima” (timeout).

**Solusi:** Set timeout pada server, mis.:

- `ReadHeaderTimeout`: 5–10 detik (batas waktu baca header).
- `ReadTimeout`: 10–15 detik (batas waktu baca body).
- `WriteTimeout`: 10–15 detik (batas waktu kirim response).

Dengan begitu server tidak menggantung tanpa batas dan lebih kecil kemungkinan Accurate mengalami timeout.

---

### 4. Service Tidak Reachable (URL / Firewall / Binding)

- **URL di Accurate** harus mengarah ke **VPS yang menjalankan service** (bukan localhost).
- **Firewall** (ufw/iptables/cloud security group) harus mengizinkan akses ke port yang dipakai (mis. 8080).
- Server harus **listen di `0.0.0.0`** (bukan hanya `127.0.0.1`) agar bisa diterima dari internet.

Jika salah satu salah, Accurate “tidak bisa mengirim” → webhook kadang tidak sampai.

**Cek:** Dari luar (browser/Postman/curl dari jaringan lain), akses `http(s)://IP-VPS:port/health` dan `POST .../webhook/accurate` dengan payload valid.

---

### 5. Subscription Webhook Accurate Kedaluwarsa

Di `worker/pool.go`, `checkWebhookActivePeriod()` memeriksa `accurate_webhook_active_period` dan memanggil **renew** ke Accurate jika mendekati expiry.

- Jika **renew gagal** (URL salah, auth salah, jaringan error) atau **tidak dikonfigurasi** (`ACCURATE_RENEW_URL` kosong), subscription bisa **habis**.
- Saat habis, Accurate bisa **berhenti mengirim** webhook → “kadang tidak menerima” (sebenarnya tidak dikirim).

**Solusi:** Pastikan `ACCURATE_RENEW_URL`, `ACCURATE_AUTH_TOKEN`, dan `ACCURATE_SIGNATURE_SECRET` benar; cek log “[Renewal]” dan error-nya.

---

### 6. SQLite Lock / DB Error

Di bawah **concurrent request tinggi**, SQLite bisa mengembalikan **database locked** atau **busy**.

- Sudah ada `PRAGMA busy_timeout = 5000` di `database.go`, yang membantu.
- Jika `INSERT` ke `webhook_queue` gagal karena lock, handler mengembalikan **500** → Accurate bisa retry, tapi dari log terlihat “kadang gagal”.

**Solusi:** Pertahankan `busy_timeout`; bisa tambah **retry** 1–2 kali pada `INSERT` jika error adalah “busy”/“locked”.

---

### 7. Kurang Logging

Tanpa log yang jelas, sulit membedakan:

- Request **tidak sampai** ke server (network/firewall/URL).
- Request **sampai** tapi kita return 4xx/5xx (payload, DB, dll).
- Request **200** tapi job **tidak pernah diproses** (pool penuh, worker error).

**Solusi:** Log setiap request masuk (method, path, content-length, dan jika gagal parse: ringkasan error). Log di handler dan di worker (mulai/selesai/gagal).

---

## Ringkasan Rekomendasi

| No | Masalah | Tindakan |
|----|--------|----------|
| 1 | Hanya terima array | Terima array **dan** single object di handler |
| 2 | Job dropped saat pool penuh | Jangan drop: tunggu dengan timeout atau pastikan job di DB tetap di-pick (restart / scanner) |
| 3 | Tidak ada timeout HTTP | Set `ReadHeaderTimeout`, `ReadTimeout`, `WriteTimeout` pada server |
| 4 | URL/firewall/binding | Pastikan URL Accurate benar, firewall buka port, listen `0.0.0.0` |
| 5 | Renewal subscription | Cek env dan log renewal Accurate |
| 6 | SQLite lock | Pertahankan busy_timeout; optional: retry INSERT pada busy |
| 7 | Sulit debug | Tambah logging request masuk dan status pemrosesan |

---

## Verifikasi Cepat

1. **Health:**  
   `curl http(s)://VPS:port/health` → harus `OK`.

2. **Test webhook (array):**  
   `curl -X POST .../webhook/accurate -H "Content-Type: application/json" -d '[{"type":"SALES_INVOICE","databaseId":1,"data":[{"salesInvoiceNo":"TEST-001"}]}]'`  
   Harus 200 dan ada record di `webhook_queue`.

3. **Test webhook (single object):**  
   `curl -X POST .../webhook/accurate -H "Content-Type: application/json" -d '{"type":"SALES_INVOICE","databaseId":1,"data":[{"salesInvoiceNo":"TEST-002"}]}'`  
   Setelah perbaikan, juga harus 200 (bukan 400).

4. **Cek log:**  
   Lihat apakah ada “Worker pool is full”, “Invalid JSON”, “Queue Error”, atau “[Renewal] Failed”.

Dengan perbaikan di kode (format payload, Submit saat pool penuh, timeout server) dan pengecekan infrastruktur (URL, firewall, renewal), masalah “webhook kadang tidak menerima dari Accurate” bisa dilacak dan dikurangi.
