Menetapkan Kontrak Webhook yang Konsisten

Bayangkan Anda menerima notifikasi dari layanan eksternal. Solusi lebih aman dimulai dengan kontrak webhook yang dipahami bersama: payload dipastikan memiliki field wajib, tipe data tetap, dan versi schema yang tercatat. Gunakan struktur JSON yang sederhana seperti event_type, data, dan timestamp, lengkap dengan penjelasan tipe dan batasan panjang.

Kontrak harus mencakup header otentikasi: X-Webhook-Signature untuk HMAC, X-Webhook-Id (ID unik untuk idempotensi), serta X-Webhook-Version untuk pengelolaan evolusi schema. Catat format timestamp (misalnya ISO 8601 UTC) dan batas waktu penerimaan agar penerima bisa menolak payload kedaluwarsa.

Tuliskan kontrak ini dalam dokumentasi bersama partner dan definisikan tiket bug jika payload menyimpang. Pendekatan ini memastikan semua tim memahami pola data dan mekanisme signature sebelum implementasi.

Validasi Signature HMAC yang Tepat

Dengan kontrak yang jelas, kini validasi HMAC menjadi kunci integritas. Layanan pengirim menggunakan shared secret untuk menghasilkan signature:
signature = HMAC(secret, rawBody)

Penerima mengambil payload mentah dari $this->request->getBody() dan memvalidasi signature menggunakan hashing yang diperbandingkan secara aman (hash_equals) agar terhindar timing attack. Jangan pernah mengandalkan JSON yang sudah di-parse karena perubahan whitespace bisa merusak validasi.

Gunakan header signature yang konsisten, misalnya X-Webhook-Signature: sha256=.... Pisahkan mekanisme parsing sehingga Anda bisa segera menolak request jika signature tidak cocok sebelum melanjutkan ke logika bisnis.

Mengimplementasikan Middleware untuk Validasi Signature

CodeIgniter 4 memungkinkan penggunaan middleware (filter) untuk memisahkan logika keamanan. Berikut contoh filter untuk memvalidasi signature sebelum controller dipanggil:

namespace Appilters;
use CodeIgniteriltersilterInterface;
use CodeIgniterilters
equestInterface;
use CodeIgniterilters
esponseInterface;

class WebhookSignatureFilter implements filterInterface
{
    protected string $secret;

    public function __construct()
    {
        $this->secret = env('WEBHOOK_SECRET');
    }

    public function before(requestInterface $request, $arguments = null)
    {
        $rawBody = $request->getBody();
        $signatureHeader = $request->getHeaderLine('X-Webhook-Signature');

        if (empty($signatureHeader)) {
            return Services::response()->setStatusCode(400)->setBody('Missing signature');
        }

        [$algo, $signature] = explode('=', $signatureHeader, 2);
        if (empty($signature) || !in_array($algo, hash_algos())) {
            return Services::response()->setStatusCode(400)->setBody('Invalid signature format');
        }

        $expected = hash_hmac($algo, $rawBody, $this->secret);
        if (!hash_equals($expected, $signature)) {
            return Services::response()->setStatusCode(401)->setBody('Signature mismatch');
        }
    }

    public function after(requestInterface $request, responseInterface $response, $arguments = null)
    {
        // Tidak diperlukan
    }
}

Pastikan filter ini diterapkan hanya pada route webhook. Dengan memblokir request yang tidak valid lebih awal, Anda menjaga controller tetap terfokus pada pemrosesan payload.

Menangani Retry dan Idempotensi Tanpa Duplikasi

Layanan pengirim biasanya melakukan retry. Untuk mencegah pemrosesan ganda, simpan token idempotent yang unik per webhook. Header X-Webhook-Id digunakan sebagai kunci. Simpan token ini dalam tabel basis data atau cache persisten (misalnya Redis) dengan masa berlaku lebih panjang daripada komit logica Anda.

Strategi sederhana: sebelum memproses payload, cek apakah token sudah ada. Jika ada dan statusnya pending atau processed, kembalikan 200 tanpa duplikasi kerja. Jika tidak ada, buat entri baru dengan timestamp dan status processing, lalu lanjutkan. Saat selesai, update status menjadi done. Gunakan transaksi jika logika pemrosesan melibatkan beberapa tabel agar status konsisten.

Waspadai token yang tidak pernah selesai (misalnya karena proses crash). Terapkan job cleanup periodik yang menghapus token dengan status processing dan lebih lama dari batas timeout.

Logging dan Observabilitas untuk Webhook Gagal

Webhook sering gagal karena signature, format payload, atau masalah downstream. Untuk debugging, log setiap event yang ditolak beserta alasan berikut:

  • Signature mismatch atau header hilang.
  • Payload schema tidak sesuai (misalnya field wajib kosong).
  • Duplikat idempotent.
  • Timeout atau pengecualian di proses utama.

Gabungkan context seperti request->getHeaderLine('X-Webhook-Id') dan raw body sebagian (hindari log data sensitif) untuk memudahkan tracing. Gunakan logger yang mendukung binding (misalnya log_message('error', 'Webhook gagal', ['id' => $id])) agar mudah mencari di observability tool.

Jika tersedia, kirim metrik seperti jumlah webhook diterima, persentase signature mismatch, dan rata-rata latency ke sistem monitoring (contoh: Prometheus) agar pola kesalahan cepat terdeteksi.

Penutup

Desain kontrak webhook yang kembali ke definisi schema, header, signature, serta idempotensi membuat integrasi menjadi lebih dapat diprediksi dan aman di CodeIgniter 4. Middleware validasi signature, strategi penyimpanan token, dan logging observabilitas memungkinkan Anda menangani retry dan kegagalan tanpa kehilangan jejak. Dengan pendekatan ini, tim Anda bisa berkembang dari sekadar menerima webhook menjadi mengelola event eksternal dengan keandalan tinggi.