Kontrak webhook Laravel API harus menyediakan jaminan bahwa setiap event diproses satu kali, sambil tetap menoleransi pengiriman ulang dari producer. Di artikel ini Anda akan menemukan langkah-langkah konkret untuk menegakkan idempotensi, memvalidasi signature, menyimpan status event, serta mengatur perilaku retry yang aman baik dari sisi producer maupun consumer.
Solusinya mencakup struktur database minimal, middleware yang memastikan signature sesuai dan idempotency key tersimpan, dan job handler yang menjaga status pemrosesan. Penjelasan menggarisbawahi mengapa pendekatan tersebut bekerja dan bagaimana menguji kontrak secara end-to-end.
Menetapkan Kontrak Webhook Laravel API dengan Idempotensi
Idempotensi di sini didasarkan pada kombinasi event identifier yang unik dan header X-Idempotency-Key yang konsisten. Ketika producer mengirim event berikut signature, Laravel API harus menyimpan record yang menunjukkan apakah event sudah selesai diproses sehingga permintaan berikutnya bisa diabaikan atau dijawab ulang tanpa perubahan negara.
Skema tabel minimal yang diperlukan bisa berupa:
Schema::create('webhook_events', function (Blueprint $table) {
$table->id();
$table->string('event_id')->unique();
$table->string('idempotency_key')->index();
$table->string('signature');
$table->enum('status', ['pending', 'processing', 'completed', 'failed']);
$table->json('payload');
$table->unsignedSmallInteger('attempts')->default(0);
$table->timestamp('last_attempt_at')->nullable();
$table->timestamps();
});
Tabel ini mendukung pencarian cepat berdasarkan event_id atau idempotency_key saat middleware menerima permintaan baru. Status memberikan konteks retry: jika status sudah completed, permintaan ulang bisa langsung dijawab 200 tanpa mengeksekusi logika domain.
Implementasi Middleware Signature dan Penyimpanan Status
Middleware memegang dua peran: memverifikasi signature dan menjamin hanya satu proses aktif per idempotency key/event. Berikut kerangka dasar:
class VerifyWebhookSignature
{
public function handle(Request $request, Closure $next)
{
$payload = $request->getContent();
$signature = $request->header('X-Signature');
$expected = hash_hmac('sha256', $payload, config('services.webhook.secret'));
if (!hash_equals($expected, $signature)) {
return response()->json(['error' => 'Invalid signature'], 400);
}
$eventId = $request->header('X-Event-Id');
$idempotencyKey = $request->header('X-Idempotency-Key');
$record = WebhookEvent::firstOrCreate(
['event_id' => $eventId],
['idempotency_key' => $idempotencyKey, 'signature' => $signature, 'payload' => $payload]
);
if ($record->status === 'completed') {
return response()->json(['status' => 'already processed'], 200);
}
if ($record->status === 'processing' && $record->attempts >= 3) {
return response()->json(['status' => 'duplicate'], 409);
}
$request->attributes->set('webhook_event', $record);
return $next($request);
}
}
Middleware tersebut menyimpan objek event untuk dipakai oleh controller atau job handler. Jika status sudah selesai, middleware cukup mengembalikan tanggapan sukses untuk menghindari duplicate processing.
Job Handler dan Penyimpanan Status
Setelah middleware, logika domain sebaiknya dijalankan lewat job queue agar bisa di-retry secara terkontrol. Job handler harus memperbarui status dan attempts:
class ProcessWebhookEvent implements ShouldQueue
{
public function handle(WebhookEvent $event)
{
$event->update(['status' => 'processing', 'attempts' => $event->attempts + 1, 'last_attempt_at' => now()]);
try {
// logika domain
$event->update(['status' => 'completed']);
} catch (Throwable $e) {
$event->update(['status' => 'failed']);
throw $e; // queue akan retry jika dikonfigurasi
}
}
}
Penanganan exception penting karena job queue akan otomatis menangani retry berdasarkan konfigurasi driver (database, Redis). Pastikan queue worker memanfaatkan backoff dan retry_after yang wajar agar tidak membanjiri sistem sisi producer.
Menangani Retry: Producer dan Consumer
Dari sisi producer, kontrak harus jelas: webhook harus dianggap berhasil hanya jika consumer mengembalikan respon 2xx. Jika menerima 4xx (signature invalid misalnya) atau 5xx, producer wajib melakukan retry dengan logika exponential backoff dan cap waktu tertentu.
Consumer (Laravel API) di sisi lain harus menangani retry secara defensif:
- Polling status: ketika producer melakukan polling untuk melihat apakah event selesai, endpoint dapat membaca tabel
webhook_eventsuntuk memberi tahu status terbaru. - Backoff internal: job queue Laravel mendukung
backoffpada job untuk memberi jeda antar retry. Gunakan properti dan pastikan informasi terakhir (attempts, timestamp) disimpan untuk audit dan debugging. - Response konsisten: saat middleware mendeteksi event sedang diproses, kembalikan 202 atau 409 agar producer tahu event diterima tapi belum selesai.
Strategi ini menghentikan duplicate state perubahan karena setiap event hanya ditandai processing satu kali per idempotency key.
Pengujian Integrasi untuk Memastikan Kontrak
Pengujian integrasi memastikan semua lapisan (middleware, database, job, queue) benar:
- Simulasikan request webhook dengan header signature dan idempotency key, periksa bahwa entry dibuat di database dan job dipush ke queue.
- Kirim ulang request yang sama; pastikan middleware mengembalikan 200 tanpa memicu job baru.
- Gunakan mock job failure lalu pastikan status berubah menjadi failed dan job di-retry sesuai konfigurasi queue. Verifikasi pula nilai
attemptsbertambah. - Test respon 5xx untuk memastikan producer retry backoff diterima dan tidak memunculkan duplicate entry.
- Jika ada mekanisme polling status, uji endpoint status untuk memberikan info terbaru berdasarkan kolom
statusdanlast_attempt_at.
Gunakan fitur Laravel seperti withoutMiddleware atau fake queue (Contoh: Queue::fake()) hanya untuk isolasi unit. Untuk pengujian end-to-end, jalankan worker queue sesungguhnya dan gunakan HTTP client (misalnya Laravel\\Http\\Client atau Http::fake()) agar kontrak bisa divalidasi dalam aliran nyata.
Dengan pendekatan ini kontrak webhook Laravel API tetap kokoh: middleware menjaga signature dan idempotency, job handler mengelola status dan retry, serta strategi pengujian memberi bukti bahwa sistem bertahan terhadap duplicate dan kegagalan sementara.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!