
Webhook vs Polling: Cara Saya Memilih Pola Integrasi Antar Sistem Tanpa Menyesal
Webhook vs polling — kapan pakai push, kapan pakai pull? Ini kriteria nyata yang saya pakai di produksi, lengkap dengan skrip retry queue dan angka konkritnya.
Minggu lali saya dapat WhatsApp dari client: "Kenapa invoice di sistem kami telat muncul berjam-jam?" Saya cek log, dan penyebabnya klasik — tim kami memakai polling interval 15 menit ke API payment gateway, tapi gateway itu ternyata sedang rate-limit endpoint-nya. Invoice diproses, tapi queue polling kami cuma dapat 40% dari respons per window. Kejadian seperti ini yang bikin saya berhenti memilih pola integrasi secara asal — "terserah yang gampang" — dan mulai bikin kriteria kapan pakai webhook dan kapan pakai polling.
Kedua pola ini sering diangangkan seolah webhook selalu lebih "modern" dan polling selalu kuno. Kenyataannya di lapangan, saya masih menjalankan keduanya bersamaan di sistem yang sama, dan itu keputusan sadar, bukan sisa refactor yang belum beres.
Perbedaan Inti: Siapa yang Mengambil Data
Singkatnya: pada polling, sistem Anda bertanya berkala "ada yang baru?". Pada webhook, sistem lawan yang mendorong notifikasi "ini ada event baru" ke endpoint Anda.
Konsekuensinya jauh lebih dari sekadar arah komunikasi:
- Polling: biaya request terus berjalan meski tidak ada data baru, latensi maksimal sebesar interval, tapi Anda yang pegang kendali penuh.
- Webhook: latensi mendekati realtime dan efisien, tapi Anda harus expose endpoint publik yang andal, aman, dan bisa menerima spike traffic.
Kesalahan yang paling sering saya lihat di code review: tim pakai webhook lalu memproses payload secara synchronous di handler-nya. Satu vendor payment mengirim retry kalau tidak dapat respons dalam 10 detik — handler yang kebetulan nge-hit API internal lambat langsung bikin duplikasi event. Itu bukan bug webhook, itu bug desain handler.
Kapan Saya Pilih Webhook
Saya pakai webhook kalau tiga kondisi ini terpenuhi:
- Event-nya jarang tapi harus cepat diketahui (pembayaran sukses, build selesai, status berubah).
- Sistem saya bisa expose endpoint HTTPS publik yang stabil.
- Payload event bisa didedupe dengan ID unik dari vendor.

Contoh nyata yang saya tangani: sinkronisasi status CI/CD. Untuk repo private di GitHub yang men-trigger Jenkins, pola push ini sudah jadi pendekatan standar — bahkan diskusi lama di Stack Overflow soal trigger Jenkins saat push ke repo private GitHub menunjukkan ini problem yang sudah hadapi bertahun-tahun, dari hook HTTP sampai polling SCM yang bisa dikonfigurasi di sisi Jenkins. Yang menarik dari diskusi itu: bahkan di dunia "push-based CI", polling SCM masih opsi sah kalau repo-nya tidak bisa diakses dari luar.
Handler webhook yang benar: terima, simpan, ack — jangan proses
Pola yang saya pakai sekarang setelah dua kali kena masalah duplicate event:
// Fastify handler — hanya terima & persist
app.post('/webhooks/payment', async (req, reply) => {
const event = req.body as PaymentEvent;
// Verifikasi signature dulu, tolak cepat kalau gagal
if (!verifySignature(req.raw, process.env.WEBHOOK_SECRET!)) {
return reply.code(401).send();
}
// Idempotency: skip kalau event sudah pernah masuk
const inserted = await db.webhookEvent.upsert({
where: { externalId: event.id },
create: { externalId: event.id, payload: event, status: 'PENDING' },
update: {},
});
// Ack segera, proses di background
if (inserted.status === 'PENDING') {
await queue.add('process-payment-event', { id: event.id });
}
return reply.code(200).send();
});
Tiga hal penting di sini: verifikasi signature (tanpa ini endpoint Anda bisa dipalsukan siapa saja), idempotency berbasis ID vendor (vendor yang saya pakai mengirim retry agresif, kadang sampai 5 kali), dan ack cepat lalu proses di queue terpisah. Handler saya target selalu respons < 100ms.
Kapan Polling Masih Pilihan Terbaik
Webhook bukan selalu jawaban. Saya tetap pakai polling kalau:
- Sistem lawan tidak menyediakan webhook (masih banyak, terutama sistem legacy dan API pemerintah/bank).
- Sistem saya berjalan di lingkungan yang tidak bisa menerima inbound connection — misalnya agen di jaringan internal klien.
- Sumber data berubah sangat sering dan saya butuh snapshot penuh, bukan stream event.
Dan jujur, polling punya satu keunggulan yang jarang diakui: kesederhanaan operasionalnya. Tidak ada endpoint publik, tidak ada signature verification, tidak ada masalah vendor kirim ke URL lama setelah kita ganti domain. Kalau polling job mati, scheduler tinggal jalanin lagi — state-nya jelas.
Rumus interval polling yang saya pakai
Jangan asal pasang cron tiap menit. Saya hitung dari tiga angka:
- SLA latensi maksimal yang dijanjikan ke user (misal: data telat maksimal 10 menit boleh).
- Rate limit endpoint lawan (misal: 100 request/menit).
- Jumlah tenant yang harus di-poll di sistem multi-tenant.
Di satu proyek SaaS saya: 40 tenant, rate limit 100 req/menit, SLA 10 menit. Berarti budget 600 request per 10 menit, jadi aman interval per-tenant 1 menit (40 req/menit). Kalau tenant naik ke 150, angka itu jebol dan kita harus turun ke interval 2 menit atau nego rate limit — hitungan seperti ini yang harus dilakukan sebelum deploy, bukan setelah kena 429.
// Polling dengan backoff saat rate-limited
async function pollTenant(tenantId: string) {
const res = await fetch(url, { headers: auth(tenantId) });
if (res.status === 429) {
const retryAfter = Number(res.headers.get('retry-after') ?? 60);
await scheduleNext(tenantId, retryAfter * 1000);
return;
}
const data = await res.json();
if (data.updatedAt > lastCheckpoint[tenantId]) {
await processUpdates(tenantId, data);
}
await scheduleNext(tenantId, POLL_INTERVAL_MS);
}
Perhatikan retry-after — hormati header ini, jangan langsung retry. Dan selalu poll dengan checkpoint (updatedAt >), bukan ambil semua data tiap kali. Saya pernah lihat polling job yang donwload full dump tiap 5 menit; itu 2 GB/hari limbah bandwidth untuk data yang cuma berubah 1%.

Pola Hybrid: Yang Saya Pakai di Produksi Sekarang
Di sistem saya sekarang, keduanya jalan bareng:
- Webhook sebagai jalur utama untuk event penting (pembayaran, perubahan status) — latensi di bawah 5 detik.
- Polling rekonsiliasi tiap 1 jam sebagai jaring pengaman, membandingkan state lokal vs state vendor untuk menangkap event yang hilang (webhook gagal kirim, endpoint sempat down, dsb).
Polling rekonsiliasi ini pernah menyelamatkan saya: ada maintenance vendor 6 jam yang bikin semua webhook drop diam-diam. Tanpa polling rekonsiliasi, mismatch data baru ketahuan minggu kemudian dari laporan finance.
Untuk kasus yang butuh audit trail dari integrasi ini, pencatatan event-nya juga saya gabungkan ke sistem audit log yang saya bangun setelah kena komplain tenant — setiap webhook masuk dan hasil polling rekonsiliasi dicatat sebagai event, jadi saat ada selisih data saya bisa trace kapan dan kenapa.
Yang juga perlu dipikirkan: ini bagian dari prinsip 12-factor app yang saya pakai — worker polling dan handler webhook sebaiknya proses terpisah yang bisa diskalakan dan di-restart independen, bukan satu monolith yang kalau worker-nya crash, endpoint webhook ikut mati.
Checklist Keputusan Praktis
Sebelum pilih, jawab lima pertanyaan ini:
- Apakah vendor menyediakan webhook yang andal (retry, signature, dokumentasi event)?
- Bisakah saya expose endpoint publik yang aman?
- Seberapa telat data boleh? Kalau 1 jam masih oke, polling jauh lebih murah di-maintain.
- Apakah ada ID unik per event untuk dedupe?
- Siapa yang handle saat webhook gagal berhari-hari?
Kalau jawaban 1-2 "tidak", polling. Kalau semua "ya", webhook plus polling rekonsiliasi periodik. Jangan pernah webhook tanpa jaring pengaman — itu pesan terpenting dari artikel ini.
Terakhir, satu kebiasaan kecil yang saya paksa ke tim: apa pun polanya, semua integrasi harus punya dashboard sederhana yang menunjukkan "event terakhir diterima kapan". Insiden invoice telat kemarin ketahuan dalam 2 menit karena dashboard itu menyala merah — sebelum client sempat WhatsApp lagi. Kalau Anda baru akan membangun integrasi baru minggu ini, mulai dari lima pertanyaan di atas, tulis jawabannya di design doc, dan baru tulis kode. Sepuluh menit ekstra di awal menghemat malam debugging di kemudian hari.
Sumber
- How to trigger a Jenkins build when a push is made to a private github repository — diskusi Stack Overflow yang menunjukkan push-based trigger dan polling SCM tetap berdampingan di dunia CI/CD, termasuk keterbatasan repo private.
Tomi Hartanto
Senior Software Engineer
Backend developer dengan 8+ tahun pengalaman membangun sistem scalable. Menulis tentang arsitektur backend, database, dan DevOps.
Artikel Terkait

Audit Log di Aplikasi SaaS: Daftar Event yang Wajib Saya Catat Setelah Kena Komplain Tenant

Onboarding UX untuk SaaS B2B: Cara Saya Turunkan Churn Sejak Hari Pertama Pakai

Feature Flag: Cara Saya Rilis Fitur Bertahap Tanpa Takut Merusak Produksi
Tomi Hartanto
Senior Software EngineerBackend developer dengan 8+ tahun pengalaman membangun sistem scalable. Menulis tentang arsitektur backend, database, dan DevOps.