Webhook vs Polling: Cara Menerima Update Status dari Sistem Lain

Gunakan webhook bila sistem lain mendukungnya dan Anda butuh pembaruan cepat dengan beban rendah: sistem pengirim memanggil endpoint Anda setiap kali ada perubahan. Gunakan polling, yaitu menanyakan status secara berkala, bila pengirim tidak menyediakan webhook, bila endpoint Anda tidak bisa diakses dari internet, atau sebagai jaring pengaman. Dalam praktik, sistem yang andal sering memakai keduanya: webhook untuk kecepatan, polling terjadwal untuk menangkap event yang terlewat.

Perbandingan singkat

Aspek Webhook Polling
Kecepatan pembaruan Hampir seketika Tergantung interval, misalnya setiap 5 atau 15 menit
Beban Hanya saat ada perubahan Terus-menerus, walau tidak ada perubahan
Kebutuhan infrastruktur Endpoint HTTPS publik yang selalu siap Cukup klien yang bisa memanggil API keluar
Risiko kehilangan data Ada, bila endpoint mati dan pengirim berhenti mencoba Rendah, karena Anda yang mengambil
Keamanan Wajib memverifikasi pengirim Cukup menjaga kredensial API Anda
Batasan API Jarang terkena rate limit Mudah terkena rate limit bila datanya banyak

Webhook yang aman: verifikasi tanda tangan HMAC

Endpoint webhook terbuka ke internet, sehingga siapa pun bisa mengirim permintaan palsu seperti “pembayaran berhasil” atau “paket diterima”. Cara umum yang dipakai penyedia layanan adalah tanda tangan HMAC: pengirim dan Anda berbagi kunci rahasia, pengirim menghitung HMAC-SHA256 dari body permintaan (sering ditambah timestamp), lalu mengirim hasilnya di header. Anda menghitung ulang dengan kunci yang sama dan membandingkan hasilnya.

Nama header, format (hex atau Base64), dan bagian apa saja yang ditandatangani berbeda di setiap penyedia, jadi ikuti dokumentasi mereka. Contoh berikut mengasumsikan header timestamp berisi detik epoch dan header X-Signature berisi hex dari HMAC-SHA256 atas timestamp + "." + body. Kode ini butuh Java 17 karena memakai HexFormat.

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.MessageDigest;
import java.time.Duration;
import java.time.Instant;
import java.util.HexFormat;

public class VerifikasiWebhook {
    private static final Duration TOLERANSI = Duration.ofMinutes(5);
    private final byte[] kunciRahasia;

    public VerifikasiWebhook(byte[] kunciRahasia) {
        this.kunciRahasia = kunciRahasia.clone();
    }

    public boolean sah(String timestampHeader, String signatureHeader, byte[] bodyMentah) {
        if (timestampHeader == null || signatureHeader == null) {
            return false;
        }
        long detik;
        try {
            detik = Long.parseLong(timestampHeader);
        } catch (NumberFormatException e) {
            return false;
        }
        Duration selisih = Duration.between(Instant.ofEpochSecond(detik), Instant.now()).abs();
        if (selisih.compareTo(TOLERANSI) > 0) {
            return false; // terlalu lama, kemungkinan replay
        }
        try {
            Mac mac = Mac.getInstance("HmacSHA256");
            mac.init(new SecretKeySpec(kunciRahasia, "HmacSHA256"));
            mac.update((timestampHeader + ".").getBytes(StandardCharsets.UTF_8));
            byte[] hitung = mac.doFinal(bodyMentah);
            byte[] diterima = HexFormat.of().parseHex(signatureHeader.trim());
            return MessageDigest.isEqual(hitung, diterima);
        } catch (IllegalArgumentException e) {
            return false; // signature bukan hex yang valid
        } catch (GeneralSecurityException e) {
            throw new IllegalStateException("HMAC tidak tersedia", e);
        }
    }
}

Tiga detail yang sering terlewat:

  • Hitung dari body mentah. Jangan parse JSON lalu menyerialisasikannya kembali; urutan field atau spasi bisa berubah dan tanda tangan tidak akan cocok. Ambil byte persis seperti yang diterima.
  • Bandingkan dengan waktu konstan. MessageDigest.isEqual tidak berhenti di byte pertama yang berbeda, sehingga penyerang tidak bisa menebak tanda tangan dari selisih waktu respons. Hindari String.equals untuk keperluan ini.
  • Tolak timestamp lama. Tanpa pemeriksaan ini, penyerang yang pernah menyadap satu permintaan sah bisa mengirimkannya ulang kapan saja.

Simpan kunci rahasia di environment variable atau secret manager, jangan di repositori, dan siapkan cara menerima dua kunci sekaligus selama masa rotasi.

Antrean untuk webhook masuk

Kesalahan desain yang paling umum adalah memproses webhook secara langsung di dalam handler HTTP: memperbarui database, mengirim email, memanggil API lain, baru kemudian membalas. Bila proses itu lambat, pengirim menganggap permintaan timeout dan mengirim ulang, sehingga muncul duplikat dan beban berlipat.

Alurnya sebaiknya seperti ini:

  1. Verifikasi tanda tangan. Bila tidak sah, balas 401 dan berhenti.
  2. Simpan body mentah beserta ID event ke tabel, atau langsung kirim ke queue pesan.
  3. Balas 200 atau 202 secepat mungkin.
  4. Worker terpisah mengambil dari queue dan menjalankan logika bisnis, lengkap dengan retry dan dead letter queue.

Dengan cara ini endpoint webhook tetap ringan saat lonjakan, dan kegagalan di logika bisnis tidak membuat pengirim menyerah. Karena pengirim hampir selalu memakai pola at-least-once, worker juga harus menolak event yang sudah pernah diproses berdasarkan ID event. Contoh event yang masuk ke antrean bisa sesederhana ini:

{
  "eventId": "evt_7f3a9c",
  "type": "shipment.status_changed",
  "occurredAt": "2026-03-14T08:21:05Z",
  "data": {
    "trackingNumber": "TRK123456789",
    "status": "IN_TRANSIT"
  }
}

Simpan juga occurredAt. Webhook tidak selalu datang berurutan, jadi status “terkirim” bisa saja tiba lebih dulu daripada “dalam perjalanan”. Worker sebaiknya mengabaikan event yang lebih lama dari status yang sudah tersimpan.

Polling yang hemat

Polling tidak harus boros. Beberapa teknik yang membantu:

  • Ambil perubahan saja. Bila API mendukung parameter seperti updated_since atau cursor, simpan posisi terakhir dan minta data setelah itu saja.
  • Pakai permintaan bersyarat. Header If-None-Match dengan ETag atau If-Modified-Since memungkinkan server membalas 304 Not Modified tanpa body, bila API mendukungnya.
  • Sesuaikan interval dengan status. Paket yang baru dibuat mungkin dicek setiap 30 menit, sedangkan paket yang sudah diterima tidak perlu dicek lagi. Keluarkan data berstatus final dari daftar polling.
  • Sebar jadwal. Jangan memeriksa semua data tepat di menit nol setiap jam. Beri sedikit acak agar tidak menabrak rate limit, dan tangani 429 dengan retry dan exponential backoff.

Gabungan keduanya

Pada fitur seperti pelacakan paket, kombinasi yang sehat adalah webhook sebagai sumber utama dan polling rekonsiliasi sebagai pengaman. Webhook memperbarui status dalam hitungan detik. Polling berjalan jarang, misalnya beberapa kali sehari, khusus untuk data yang statusnya belum final dan lama tidak berubah. Jika polling menemukan perbedaan, itu tanda ada webhook yang hilang, dan jumlahnya layak dijadikan metrik. Penerapan pola ini dibahas di membangun fitur lacak paket.

Pertanyaan yang sering muncul

Bagaimana menguji webhook di komputer lokal?

Endpoint lokal tidak bisa dijangkau pengirim dari internet. Anda bisa memakai fitur kirim ulang atau contoh event di dasbor penyedia bersama terowongan (tunnel) ke mesin lokal, atau cukup menyimpan contoh payload asli lalu mengirimkannya ke endpoint lokal dengan curl dan tanda tangan yang Anda hitung sendiri.

Status apa yang harus dibalas?

Balas 2xx bila event sudah diterima dan disimpan, walau belum diproses. Balas 401 atau 403 untuk tanda tangan tidak sah. Jangan membalas 5xx karena error logika bisnis yang terjadi setelah event tersimpan; itu hanya membuat pengirim mengirim ulang hal yang sama.

Hendra Kurniawan

Oleh Hendra Kurniawan

Pengembang backend Java yang lama menangani integrasi sistem dan message broker. Menulis catatan praktis tentang JMS, API, dan arsitektur aplikasi.