Untuk memastikan webhook yang dipanggil dari sistem eksternal tetap idempoten di CodeIgniter 3, mulailah dengan menentukan kontrak API yang eksplisit. Kontrak ini harus menjelaskan payload minimal (misalnya request_id, resource_id, versi payload), serta status code yang dikembalikan untuk setiap situasi. Sebagai contoh, balas 200 OK ketika payload valid dan diproses; 202 Accepted untuk proses latar belakang yang masih berlanjut; 4xx untuk kesalahan klien; dan 5xx hanya jika sistem Anda tidak bisa menerima permintaan sama sekali.

Dengan pendekatan seperti ini, sistem pemanggil dapat tahu apakah perlu retry, menyesuaikan backoff, atau menghentikan percobaan. Seluruh artikel ini fokus pada developer legacy yang masih menggunakan CI3 tanpa mengupgrade framework.

Kontrak Payload dan Status Code untuk Idempoten

Kontrak API idempoten paling sederhana terdiri dari tiga bagian:

  • Payload eksplisit: sertakan request_id permanen dari pengirim, resource_id yang berkaitan, tipe event, serta signature atau timestamp jika perlu.
  • Status code deterministik: pastikan CI3 merespons sesuai kategori hasil. Jangan pernah mengandalkan 200 hanya karena callback diterima; gunakan 4xx untuk validasi payload yang gagal.
  • Respons standar: sertakan JSON sederhana seperti {"status":"accepted","request_id":"..."} agar integrasi dapat memverifikasi bahwa callback diakui.

Pastikan semua permintaan webhook wajib menyertakan header X-Request-Id atau field di body. Bila tidak ada, tanggapi dengan 400 Bad Request dan catat log untuk debugging.

Autentikasi Token/Session di CodeIgniter 3

Karena CI3 tidak menyediakan middleware modern, manfaatkan post-controller hook atau base controller yang memeriksa token sebelum logika webhook dijalankan.

Contoh hook sederhana di application/config/hooks.php:

$hook['pre_controller'] = array(
    'class'    => 'WebhookAuthHook',
    'function' => 'validate',
    'filename' => 'WebhookAuthHook.php',
    'filepath' => 'hooks'
);

Isi WebhookAuthHook:

class WebhookAuthHook {
    public function validate() {
        $CI =& get_instance();
        $token = $CI->input->get_request_header('X-Webhook-Token', TRUE);
        if (!$token || $token !== $CI->config->item('webhook_shared_token')) {
            log_message('error', 'Webhook token invalid');
            show_error('Unauthorized', 401);
        }
    }
}

Token disimpan di application/config/config.php atau .env agar bisa diganti tanpa kode ulang. Pendekatan ini berfungsi baik untuk token statis maupun session berbasis DB dengan pengecekan tambahan.

Penjamin Idempoten di Controller Webhook

Buat controller yang responsif terhadap request_id dan memakai model untuk mencatat permintaan. Gunakan tabel sederhana seperti webhook_events(request_id VARCHAR unique, status ENUM, payload JSON, updated_at).

class Webhook extends CI_Controller {
    public function receive() {
        $requestId = $this->input->post('request_id');
        if (!$requestId) {
            show_error('request_id wajib', 400);
            return;
        }

        $this->load->model('Webhook_event_model');
        if ($this->Webhook_event_model->is_processed($requestId)) {
            $this->output->set_status_header(200);
            $this->output->set_output(json_encode(['status' => 'duplicate', 'request_id' => $requestId]));
            return;
        }

        $payload = $this->input->raw_input_stream;
        $this->db->trans_start();
        $this->Webhook_event_model->mark_processing($requestId, $payload);
        // Logika pemrosesan utama di sini
        $this->Webhook_event_model->mark_done($requestId);
        $this->db->trans_complete();

        if ($this->db->trans_status()) {
            $this->output->set_header('Content-Type: application/json');
            $this->output->set_status_header(200);
            $this->output->set_output(json_encode(['status' => 'ok', 'request_id' => $requestId]));
        } else {
            $this->output->set_status_header(500);
            $this->output->set_output(json_encode(['status' => 'failed', 'request_id' => $requestId]));
        }
    }
}

Model Webhook_event_model menggunakan ON DUPLICATE KEY atau pemeriksaan row dengan locking (`SELECT ... FOR UPDATE`) agar status tidak tertutup. Ini mencegah pemrosesan ganda saat banyak request_id sama masuk bersamaan.

Logger dan Kunci Sederhana untuk Menghindari Pemrosesan Ganda

Untuk menjaga idempoten tanpa arsitektur baru, Anda bisa mengkombinasikan tabel webhook_locks(request_id VARCHAR, locked_at DATETIME) dengan log file khusus. Strateginya:

  • Setiap request memeriksa apakah request_id sedang dikunci; jika iya, kembalikan 202 Accepted sebagai indikasi sedang diproses.
  • Gunakan transaction dan INSERT ... ON DUPLICATE KEY pada tabel lock; hapus kunci setelah selesai.
  • Gunakan log_message('info', ...) untuk mencatat request_id, status, dan waktu secara konsisten.

Jika Anda tidak ingin menulis ke tabel, cukup gunakan flock() pada file dengan nama berdasarkan request_id. Kendati pendekatan ini tidak bergaya, ia efektif ketika deployment tidak mendukung shared storage.

Strategi Retry dan Backoff yang Cocok untuk CI3

Karena CI3 tidak memiliki queue built-in, atur retry di sisi pemanggil dengan memperhatikan status code dari webhook Anda. Panduan sederhana:

  • 200/202: sistem penerima berhasil menerima. Tidak perlu retry.
  • 4xx: terjadi kesalahan validasi—perbaiki payload sebelum ulangi.
  • 5xx: retry otomatis dengan exponential backoff, mulai 2 detik dan maksimum 1 menit.

Anda bisa menambahkan header seperti X-Next-Retry: 30 untuk memberi tahu pengirim kapan memulai ulang pengiriman. Karena CI3 tidak punya queue worker, rekomendasikan pengirim menggunakan pola retry berbasis HTTP, bukan menunggu callback yang gagal dikerjakan secara otomatis.

Catatan debugging: ketika webhook gagal, periksa log application/logs untuk request_id, status, dan trace lengkap. Pastikan konfigurasi $config['log_threshold'] cukup tinggi saat sedang menyelesaikan integrasi. Kesalahan umum lainnya adalah tidak mengirim header token atau mengabaikan status response, sehingga pengirim terus retry tanpa batas.

Kesimpulan: Dengan kontrak API yang jelas, validasi token melalui hook, pencatatan request_id di controller, dan mekanisme logging/lock sederhana, Anda bisa menjamin idempoten webhook di CodeIgniter 3 tanpa upgrade framework. Sertakan dokumentasi kontrak bagi tim pengirim webhook agar retry/backoff dan status code dipahami bersama.