Kontrak Webhook Laravel harus menjawab dua hal utama: siapa yang mengirim payload dan bagaimana mencegah pemrosesan ulang. Artikel ini langsung menjelaskan cara memverifikasi autentikasi webhook, membatasi replay, dan menjaga idempotensi melalui middleware serta strategi retry dan reject di Laravel.
Memahami kontrak webhook Laravel
Kontrak yang tepat memastikan pengirim menyediakan informasi yang konsisten seperti timestamp, signature HMAC, dan payload yang dapat diidentifikasi. Laravel menerima webhook melalui route dan harus memisahkan validasi autentikasi dari logika bisnis. Penegakan kontrak berarti:
- Menyamakan header dan payload dengan rumus yang disepakati.
- Memastikan setiap request hanya diproses sekali, walau pengirim mengulang.
- Menyediakan respons tegas (200/400/409) agar pengirim tahu statusnya.
Dengan pendekatan ini, Anda menghindari penerimaan payload tak sah sekaligus menjaga sistem internal tetap konsisten.
Validasi autentikasi dan signature
Validasi webhook berpusat pada signature yang dihitung dari payload dan shared secret. Kontrak harus menetapkan format header (misalnya X-Signature), algoritma, serta rentang waktu valid.
Contoh middleware untuk signature
Gunakan middleware agar validasi terjadi sebelum controller. Middleware ini memeriksa header signature dan timestamp, lalu memastikan nilai HMAC sesuai.
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Symfony\Component\HttpFoundation\Response;
class VerifyWebhookSignature
{
public function handle(Request $request, Closure $next)
{
$signature = $request->header('X-Signature');
$timestamp = $request->header('X-Timestamp');
if (! $signature || ! $timestamp) {
return response('Signature missing', Response::HTTP_BAD_REQUEST);
}
$payload = $request->getContent();
$secret = config('services.webhook.secret');
$expected = hash_hmac('sha256', $timestamp . '.' . $payload, $secret);
if (! hash_equals($expected, $signature)) {
return response('Invalid signature', Response::HTTP_UNAUTHORIZED);
}
return $next($request);
}
}
Rangkai middleware ini ke route webhook sehingga setiap payload yang masuk sudah tervalidasi sebelum diproses.
Proteksi replay dan idempotensi payload
Replay attack terjadi jika payload berhasil ditiru ulang. Kontrak harus mendefinisikan header seperti X-Request-Id atau X-Nonce dan X-Timestamp. Saat menerima request, bandingkan timestamp dengan waktu server untuk membatasi jarak waktu (misalnya ±5 menit).
Menyimpan ID unik dan membatasi replay
Gunakan cache (Redis) untuk menyimpan request_id selama window validasi. Jika ID sudah ada, segera reject dengan HTTP 409 untuk menghindari duplikasi.
$cacheKey = 'webhook:request:' . $requestId;
if (Cache::has($cacheKey)) {
return response('Replay detected', 409);
}
Cache::put($cacheKey, true, now()->addMinutes(5));
Setelah ID tersimpan, lanjutkan pemrosesan. Jika handler gagal, pastikan mekanisme retry tidak menganggapnya sukses tanpa pengecekan ulang.
Idempotensi dan pengelolaan retry
Idempotensi umumnya dijaga oleh handler bisnis:
- Simpan status akhir per
request_iddi database dan hanya jalankan operasi jika status belum diproses. - Gunakan transaksi untuk memastikan data tidak berubah jika operasi dipanggil dua kali.
- Setiap percobaan ulang harus membaca status untuk menentukan apakah harus menolak or memperbarui.
Jika kontrak tidak mengizinkan retry (misalnya payload sensitif terkait pembayaran), konfigurasikan handler untuk mengembalikan HTTP 422 atau 409 setelah validasi bahwa payload sudah diproses.
Strategi retry, logging, dan debugging
Dokumentasikan batas retry (jumlah, interval) dan bagikan mekanisme error response ke pengirim. Sistem internal bisa menggunakan queue Laravel untuk memproses webhook secara asinkron dan mencatat status:
- Gunakan queue worker yang mengecek apakah ID sudah diproses sebelum menjalankan job.
- Simpan log signature, timestamp, dan respon sehingga mudah dilakukan audit saat terjadi kegagalan.
- Berikan response detail yang memungkinkan klien mengenali status tanpa menebak apakah harus retry.
Jika ada deny, indikasi seperti HTTP 409 plus pesan jelas membantu pengirim berhenti mencoba, sementara HTTP 500 menandakan mereka boleh coba ulang setelah memperbaiki sistem.
Kesimpulan
Kontrak webhook Laravel yang baik menggabungkan validasi signature, proteksi replay, dan idempotensi. Dengan middleware terpisah untuk autentikasi, cache untuk menyimpan request ID, serta handling status yang konsisten, aplikasi tetap aman tanpa menyetop aliran data. Selalu dokumentasikan masing-masing header, response code, dan batas retry agar integrasi dengan pihak ketiga berjalan mulus.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!