Untuk menjaga integritas dan keandalan webhook, aplikasi Go Fiber harus mendefinisikan kontrak yang mencakup payload tetap, validasi HMAC, idempotensi, dan pola retry yang toleran terhadap kegagalan. Artikel ini menjawab langsung bagaimana merancang kontrak tersebut dengan fokus praktis, termasuk schema payload, headers versioning, dan langkah-langkah implementasi pada penerima webhook.
Desain kontrak webhook Go Fiber yang konsisten
Kontrak webhook yang kuat dimulai dari definisi schema payload yang tidak berubah-ubah: gunakan JSON dengan field wajib, jenis data eksplisit, dan field metadata seperti event_type, timestamp, payload, serta versi schema. Sertakan header tersendiri untuk versi kontrak (misalnya X-Webhook-Version) agar backend bisa memutuskan handling berdasarkan evolution plan.
Selain payload, tetapkan header untuk sekuriti (X-Signature, X-Request-ID) dan informasi metadata. Permintaan harus menyertakan body yang bisa di-hash ulang tanpa modifikasi di transmisi, jadi hindari kompresi atau encoding yang merubah urutan byte sampai pemeriksaan HMAC selesai.
Validasi HMAC dengan rotasi secret
Validasi signature HMAC memastikan webhook berasal dari sumber yang sah dan data tidak dimanipulasi. Dasar pendekatan adalah menerapkan middleware yang membaca X-Signature dan menghitung HMAC terhadap body request gunakan secret yang saat ini aktif serta secret sebelumnya untuk mendukung rotasi.
Rotasi secret bisa dilakukan periodik: menyimpan dua atau tiga secret terakhir dalam konfigurasi, dan middleware mencoba verify terhadap setiap secret hingga match. Selalu gunakan hashing/hmac dan crypto/sha256 untuk menghitung tanda tangan. Jika tidak cocok dengan semua secret, tolak request dengan 401 Unauthorized sebelum middleware lain dijalankan.
Middleware juga ideal untuk mencatat header version di context agar handler utama bisa memeriksa kompatibilitas berdasarkan schema.
func HMACMiddleware(secrets []string) fiber.Handler {
return func(c *fiber.Ctx) error {
signature := c.Get("X-Signature")
if signature == "" {
c.Logger().Warn("missing signature header")
return c.Status(fiber.StatusBadRequest).SendString("missing signature")
}
body := c.Body()
verified := false
for _, secret := range secrets {
mac := hmac.New(sha256.New, []byte(secret))
mac.Write(body)
expect := hex.EncodeToString(mac.Sum(nil))
if hmac.Equal([]byte(expect), []byte(signature)) {
verified = true
break
}
}
if !verified {
c.Logger().Error("invalid webhook signature")
return c.Status(fiber.StatusUnauthorized).SendString("invalid signature")
}
return c.Next()
}
}
Catatan debugging: log seluruh header signature dan ID secara bersyarat (jangan log body jika mengandung data sensitif), sehingga saat terjadi kegagalan validasi bisa cepat ditelusuri dari request-id.
Strategi idempotensi: token dan cache deduplikasi
Jika webhook retries dikirim oleh pengirim, handler harus dapat mengenali duplikasi agar tidak memproses aksi dua kali. Basisnya adalah request idempotency token pada header seperti X-Request-ID atau field payload. Penyimpanannya bisa sederhana dengan Redis TTL atau database yang menampung ID sudah diproses.
Contoh implementasi pendekatan Redis:
func IdempotencyMiddleware(redisClient *redis.Client, ttl time.Duration) fiber.Handler {
return func(c *fiber.Ctx) error {
requestID := c.Get("X-Request-ID")
if requestID == "" {
return c.Status(fiber.StatusBadRequest).SendString("missing request id")
}
key := fmt.Sprintf("webhook:processed:%s", requestID)
set, err := redisClient.SetNX(context.Background(), key, "1", ttl).Result()
if err != nil {
c.Logger().Errorf("redis error: %v", err)
return c.Status(fiber.StatusInternalServerError).SendString("internal error")
}
if !set {
c.Logger().Infof("duplicate webhook id %s", requestID)
return c.Status(fiber.StatusOK).SendString("duplicate")
}
return c.Next()
}
}
TTL harus sedikit lebih panjang dari durasi maksimum retry agar entri tidak dihapus terlalu cepat. Jika handler gagal setelah berhasil memasukkan token, pastikan untuk menghapus entry atau menandainya sebagai failed tergantung kebutuhan bisnis. Catat bahwa Redis bisa menjadi single point so consider Circuit Breaker jika tidak tersedia.
Pola retry dan backoff di sisi penerima
Penerima webhook juga harus tahan terhadap retry ganda dari pengirim. Gunakan mekanisme berikut untuk meningkatkan reliabilitas:
- Timeout terbatas: Tetapkan timeout request Fiber (misalnya 10 detik) agar handler tidak menggantung ketika downstream lambat.
- Mengembalikan status tepat: Untuk kasus berhasil, kembalikan
200 OK; untuk sementara (misalnya pekerjaan masih diproses di background), gunakan202 Accepteddan proses asynchronous. Jika terjadi kesalahan sementara, gunakan503 Service Unavailableagar pengirim mencoba lagi. - Backoff di penerima: Jika melakukan panggilan ulang internal (misalnya chaining ke layanan lain), gunakan retry with exponential backoff dengan batas maksimal dan jitter untuk menghindari thundering herd.
Tailor strategi retry berdasarkan SLA pengirim. Pastikan dokumentasi kontrak menyebut skenario kegagalan yang menyebabkan pengirim mencoba ulang.
Observabilitas dan logging kegagalan
Logging adalah kunci untuk debugging webhook yang ditolak. Catat:
- Header metadata (
X-Request-ID, versi payload, event type). - Status validasi HMAC dan idempotensi.
- Durasi pemrosesan dan error downstream.
Gunakan logger Fiber atau middleware observability untuk mengirimkan metrik seperti jumlah signature invalid dan rata-rata latency. Jika integrasi dengan tracing, sertakan trace id pada logs agar dapat diagregasi.
Dengan struktur kontrak yang konsisten, validasi signature yang tahan rotasi secret, strategi idempotensi berbasis cache dedup, dan pola retry yang didokumentasikan, penerima webhook Go Fiber dapat menjaga keamanan dan keandalan aplikasi backend.
Komentar
0 komentar
Masuk ke akun kamu untuk ikut berkomentar.
Belum ada komentar
Jadilah yang pertama ikut berdiskusi!