Webhook sering terlihat sederhana: satu layanan mengirim HTTP request, lalu website kita menerima dan memprosesnya. Masalahnya, endpoint webhook pada dasarnya adalah pintu yang bisa dipanggil dari internet. Jika siapa pun dapat mengirim request dengan format yang benar, aplikasi mungkin akan mempercayai data yang sebenarnya dibuat oleh penyerang.
Risikonya bukan hanya data palsu masuk ke database. Webhook yang lemah dapat memicu pengiriman email, perubahan status pesanan, pembuatan akun, pemberian akses, bahkan proses internal lain yang seharusnya hanya boleh dijalankan oleh layanan terpercaya.
Webhook tidak otomatis aman hanya karena URL-nya sulit ditebak
Banyak implementasi awal mengandalkan URL rahasia seperti /webhook/order-7f3a9c. Cara ini memang lebih baik daripada endpoint yang mudah ditebak, tetapi tetap bukan autentikasi. URL dapat bocor melalui log server, riwayat deployment, screenshot, plugin analitik, atau konfigurasi pihak ketiga.
Anggap URL webhook sebagai alamat rumah, bukan kunci pintu. Kita tetap membutuhkan mekanisme untuk memastikan siapa yang datang dan apakah pesan yang dibawa belum diubah.
Lapisan pertama: verifikasi tanda tangan digital
Pola yang umum digunakan adalah HMAC, yaitu kode autentikasi berbasis hash. Layanan pengirim dan penerima memiliki secret yang sama. Pengirim menghitung tanda tangan dari isi request, lalu menyertakannya pada header. Penerima menghitung ulang tanda tangan tersebut dan membandingkan hasilnya.
Contoh konsep header:
X-Webhook-Signature: sha256=...Hal pentingnya: tanda tangan harus dihitung dari raw request body, bukan dari data yang sudah diubah menjadi array atau JSON baru. Perubahan kecil pada spasi, urutan karakter, atau format angka dapat menghasilkan tanda tangan berbeda.
Dalam PHP, perbandingan tanda tangan sebaiknya memakai hash_equals(), bukan operator ==. Fungsi tersebut dirancang untuk mengurangi risiko perbandingan yang dapat ditebak melalui perbedaan waktu proses.
<?php
$rawBody = file_get_contents('php://input');
$received = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';
$secret = $_ENV['WEBHOOK_SECRET'];
$expected = 'sha256=' . hash_hmac('sha256', $rawBody, $secret);
if (!hash_equals($expected, $received)) {
http_response_code(401);
exit('Invalid signature');
}
$data = json_decode($rawBody, true);
Potongan tersebut hanya contoh dasar. Dalam aplikasi nyata, tambahkan validasi JSON, batas ukuran body, pencatatan insiden, dan penanganan error yang tidak membocorkan secret atau detail internal.
Jangan lupa mencegah request lama diputar ulang
Tanda tangan yang valid belum tentu berarti request tersebut masih layak diproses. Jika penyerang berhasil merekam request yang sah, ia dapat mengirim ulang request yang sama beberapa kali. Serangan ini disebut replay attack.
Untuk menguranginya, minta pengirim menyertakan timestamp dan ID unik setiap event. Tanda tangan kemudian dihitung dari gabungan timestamp dan body, misalnya:
timestamp.bodyPenerima dapat menolak request yang terlalu lama, misalnya lebih dari lima menit, dengan toleransi waktu yang wajar untuk perbedaan jam antarserver. ID event juga perlu disimpan sebentar di database atau cache. Jika ID yang sama sudah pernah diproses, request berikutnya dianggap duplikat.
Langkah ini penting terutama untuk event seperti pembayaran, refund, perubahan status langganan, atau pemberian kredit. Satu event seharusnya tidak menghasilkan efek samping berkali-kali.
Validasi isi pesan, bukan hanya pengirimnya
Request dari layanan terpercaya tetap dapat bermasalah karena bug, perubahan format, atau konfigurasi yang keliru. Setelah signature lolos, validasi payload secara ketat.
- Pastikan method HTTP sesuai, biasanya
POST. - Periksa
Content-Typedan ukuran request. - Pastikan field wajib tersedia dan memiliki tipe yang benar.
- Gunakan allowlist untuk nilai seperti status, mata uang, atau jenis event.
- Jangan mempercayai harga, hak akses, atau identitas pengguna hanya dari payload tanpa pengecekan tambahan.
Misalnya, webhook menyatakan sebuah pesanan sudah dibayar. Aplikasi sebaiknya tidak langsung mengaktifkan akses hanya karena field status berisi paid. Cocokkan ID transaksi, nominal, mata uang, dan status dengan data yang tersimpan atau lakukan verifikasi kembali melalui API penyedia pembayaran jika dampaknya besar.
Batasi dampak jika endpoint disalahgunakan
Endpoint webhook sebaiknya memiliki hak akses sesempit mungkin. Jangan menjalankan fungsi admin secara langsung dari request publik. Pisahkan proses penerimaan dan proses bisnis.
Pola yang lebih aman adalah menerima request, memvalidasi, menyimpan event, lalu memprosesnya melalui worker atau antrean. Dengan begitu, request internet tidak langsung memiliki kendali penuh atas proses sensitif. Sistem juga lebih mudah mencoba ulang event yang gagal tanpa meminta pengirim mengirim ulang secara membabi buta.
Jika webhook digunakan untuk memicu pekerjaan yang berat, jangan biarkan proses tersebut berjalan tanpa batas waktu. Gunakan timeout, pembatasan jumlah request, dan rate limit. Rate limit berarti membatasi berapa banyak request yang boleh masuk dalam periode tertentu.
Bagaimana dengan IP allowlist?
Allowlist alamat IP dapat menjadi lapisan tambahan, terutama jika penyedia webhook memiliki daftar IP resmi yang stabil. Namun, jangan menjadikannya satu-satunya perlindungan. Infrastruktur cloud, proxy, dan perubahan jaringan dapat membuat alamat IP berubah. Selain itu, salah konfigurasi proxy bisa membuat aplikasi membaca alamat IP penyerang, bukan alamat pengirim sebenarnya.
Gunakan IP allowlist bersama signature, bukan sebagai pengganti signature. Pastikan pula pemeriksaan dilakukan pada lapisan jaringan atau reverse proxy jika memungkinkan.
Catatan log harus membantu, bukan membuka rahasia
Ketika webhook gagal, log adalah alat utama untuk mencari penyebabnya. Catat waktu, jenis event, ID request, hasil validasi, dan durasi proses. Hindari mencatat secret, token akses, nomor kartu, cookie, atau seluruh payload jika payload mengandung data pribadi.
Gunakan ID korelasi agar satu event dapat dilacak dari request masuk sampai proses selesai. Pisahkan log request ditolak, request duplikat, dan kegagalan pemrosesan. Ketiga kondisi itu memiliki arti yang berbeda dan membutuhkan tindakan berbeda.
Checklist singkat sebelum webhook dipakai di produksi
- Endpoint hanya menerima method dan format yang diperlukan.
- Signature diverifikasi menggunakan raw body dan
hash_equals(). - Request memiliki timestamp dan ID event unik.
- Event lama dan event duplikat dapat ditolak.
- Payload divalidasi dengan allowlist dan tipe data yang jelas.
- Proses sensitif tidak dijalankan langsung tanpa pemeriksaan tambahan.
- Ukuran body, timeout, dan rate limit sudah diatur.
- Secret disimpan di environment atau secret manager, bukan di source code.
- Log cukup informatif tanpa menyimpan data sensitif.
- Ada prosedur untuk mengganti secret jika terjadi kebocoran.
Apa artinya bagi kita?
Webhook yang aman bukan soal menambahkan satu header lalu selesai. Ia membutuhkan beberapa lapisan: autentikasi pesan, perlindungan dari replay, validasi data, pembatasan dampak, dan observabilitas yang baik.
Mulailah dari endpoint yang paling berisiko. Tanyakan: jika request ini dipalsukan, apa kerugian terburuknya? Jika jawabannya menyangkut uang, akses pengguna, atau data penting, jangan memperlakukan webhook sebagai notifikasi biasa. Perlakukan ia sebagai jalur integrasi yang memiliki hak akses terbatas, harus diawasi, dan dapat dihentikan dengan cepat ketika terjadi sesuatu yang tidak wajar.
– Rio Yotto @rioyotto
