TL;DR: Polling memeriksa pembaruan secara berkala (mudah tapi tidak efisien). Webhooks mengirim pembaruan secara real-time (efisien tapi lebih kompleks). Pakai polling untuk pengecekan jarang, webhooks untuk update real-time. Modern PetstoreAPI mendukung keduanya dengan pengiriman webhook yang andal.
Memahami Perbedaannya
Polling: Klien bertanya "Ada pembaruan?" secara berkala.
Webhooks: Server memberitahu "Ini update-nya!" saat ada perubahan.
- Polling = Cek kotak surat tiap jam
- Webhooks = Tukang pos membunyikan bel saat surat datang
Polling: Cara Kerjanya
Klien melakukan permintaan berkala untuk cek perubahan status.
// Poll setiap 30 detik
setInterval(async () => {
const response = await fetch('https://petstoreapi.com/api/v1/orders/123');
const order = await response.json();
if (order.status === 'completed') {
console.log('Order completed!', order);
clearInterval(pollInterval);
}
}, 30000);
Pola polling:
Polling sederhana:
GET /api/v1/orders/123
# Mengembalikan status pesanan saat ini
Polling kondisional (ETag):
GET /api/v1/orders/123
If-None-Match: "abc123"
# 304 jika tidak berubah, 200 jika berubah
Polling berbasis sejak (since-based):
GET /api/v1/orders/123/events?since=1710331200
# Mengembalikan event sejak timestamp
Webhooks: Cara Kerjanya
Server mengirim HTTP POST ke endpoint Anda saat event terjadi.
Alur pengaturan:
// 1. Daftarkan endpoint webhook
POST /api/v1/webhooks
{
"url": "https://myapp.com/webhooks/petstore",
"events": ["order.created", "order.completed"],
"secret": "whsec_abc123"
}
// 2. Server mengirim webhook saat event
POST https://myapp.com/webhooks/petstore
{
"id": "evt_123",
"type": "order.completed",
"created": 1710331200,
"data": {
"orderId": "123",
"status": "completed",
"completedAt": "2024-01-01T12:00:00Z"
}
}
// 3. Verifikasi dan proses webhook, respon 200 OK
Kapan Menggunakan Polling
Gunakan polling untuk:
- Pemeriksaan tidak sering (misal 1 jam sekali)
- Resource sedikit
- Implementasi sederhana
- Kontrol penuh pada sisi klien
- Testing & debugging
Contoh:
- Cek status laporan harian
- Sync kontak tiap beberapa menit
- Monitor health server
- Cek status pembayaran jarang-jarang
Polling tepat saat:
- Update jarang
- Delay bisa diterima
- Butuh kode sederhana
- Resource terbatas
Kapan Menggunakan Webhooks
Gunakan webhooks untuk:
- Update real-time
- Memantau banyak resource
- Event sensitif waktu
- Integrasi pihak ketiga
- Update frekuensi tinggi
Contoh:
- Konfirmasi pembayaran
- Pesan chat
- Alert harga saham
- Perubahan status order
- Notifikasi CI/CD
Webhooks optimal saat:
- Update harus cepat
- Polling tidak efisien
- Banyak klien memantau resource sama
- Ingin kurangi beban server
Tabel Perbandingan
<!--kg-card-begin: html-->
| Faktor | Polling | Webhooks |
|---|---|---|
| Latensi | Hingga interval polling | Real-time |
| Beban server | Tinggi (banyak permintaan kosong) | Rendah (hanya peristiwa nyata) |
| Kompleksitas | Sederhana | Kompleks |
| Keandalan | Tinggi (klien mengontrol retry) | Menengah (butuh retry logic) |
| Pengaturan | Tidak ada | Pendaftaran endpoint |
| Masalah Firewall | Tidak ada (hanya keluar) | Mungkin perlu whitelist |
| Biaya | Lebih tinggi (lebih banyak request) | Lebih rendah (sedikit request) |
| Terbaik untuk | Pengecekan jarang | Pembaruan real-time |
Mengimplementasikan Polling
Polling Dasar
async function pollOrderStatus(orderId, callback) {
let lastStatus = null;
const poll = async () => {
try {
const response = await fetch(`https://petstoreapi.com/api/v1/orders/${orderId}`);
const order = await response.json();
// Callback hanya jika status berubah
if (order.status !== lastStatus) {
lastStatus = order.status;
callback(order);
}
// Stop polling jika status akhir
if (['completed', 'cancelled'].includes(order.status)) {
return;
}
setTimeout(poll, 5000);
} catch (error) {
console.error('Polling error:', error);
setTimeout(poll, 30000); // Retry dengan delay saat error
}
};
poll();
}
// Penggunaan
pollOrderStatus('order-123', (order) => {
console.log(`Order status: ${order.status}`);
});
Polling Cerdas (Exponential Backoff)
async function smartPoll(url, callback, options = {}) {
const {
maxRetries = 10,
initialInterval = 1000,
maxInterval = 60000,
stopCondition = () => false
} = options;
let retries = 0;
let interval = initialInterval;
let lastData = null;
const poll = async () => {
try {
const response = await fetch(url);
const data = await response.json();
// Callback jika data berubah
if (JSON.stringify(data) !== JSON.stringify(lastData)) {
lastData = data;
callback(data);
}
// Stop jika kondisi terpenuhi
if (stopCondition(data)) {
return;
}
// Reset interval jika sukses
interval = initialInterval;
} catch (error) {
retries++;
if (retries >= maxRetries) {
throw new Error('Max retries exceeded');
}
}
setTimeout(poll, interval);
interval = Math.min(interval * 2, maxInterval);
};
poll();
}
// Contoh penggunaan: Poll status order hingga selesai
smartPoll('https://petstoreapi.com/api/v1/orders/123',
(order) => console.log('Order:', order),
{
stopCondition: (order) => ['completed', 'cancelled'].includes(order.status),
initialInterval: 2000,
maxInterval: 30000
}
);
Polling dengan ETag
async function pollWithEtag(url, callback) {
let etag = null;
const poll = async () => {
const headers = {};
if (etag) {
headers['If-None-Match'] = etag;
}
const response = await fetch(url, { headers });
if (response.status === 304) {
setTimeout(poll, 30000);
return;
}
const data = await response.json();
etag = response.headers.get('etag');
callback(data);
setTimeout(poll, 30000);
};
poll();
}
Mengimplementasikan Webhooks
Mendaftarkan Webhooks
// Daftarkan endpoint webhook
async function registerWebhook(url, events) {
const response = await fetch('https://petstoreapi.com/api/v1/webhooks', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${API_KEY}`
},
body: JSON.stringify({
url,
events,
secret: generateSecret()
})
});
return response.json();
}
function generateSecret() {
return 'whsec_' + crypto.randomBytes(32).toString('hex');
}
Menerima Webhooks
const express = require('express');
const crypto = require('crypto');
const app = express();
// Parser body mentah untuk verifikasi signature
app.use('/webhooks', express.raw({ type: 'application/json' }));
app.post('/webhooks/petstore', async (req, res) => {
const signature = req.headers['x-petstore-signature'];
const body = req.body;
// Verifikasi signature
const isValid = verifySignature(body, signature, process.env.WEBHOOK_SECRET);
if (!isValid) {
return res.status(401).json({ error: 'Invalid signature' });
}
const event = JSON.parse(body.toString());
// Proses event
switch (event.type) {
case 'order.created':
await handleOrderCreated(event.data);
break;
case 'order.completed':
await handleOrderCompleted(event.data);
break;
case 'order.cancelled':
await handleOrderCancelled(event.data);
break;
}
res.status(200).json({ received: true });
});
function verifySignature(payload, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}
Menguji Webhooks Secara Lokal
# Ekspos endpoint lokal dengan ngrok
ngrok http 3000
# Daftar URL ngrok sebagai endpoint webhook
curl -X POST https://petstoreapi.com/api/v1/webhooks \
-H "Authorization: Bearer $TOKEN" \
-d '{
"url": "https://abc123.ngrok.io/webhooks/petstore",
"events": ["order.created", "order.completed"]
}'
Pengiriman Webhook yang Andal
Webhooks bisa gagal, implementasikan retry logic.
Sisi Pengirim (Server)
// Antrean webhook untuk pengiriman
const webhookQueue = [];
async function sendWebhook(event) {
const webhooks = await db.webhooks.findMany({
where: { events: { contains: event.type } }
});
for (const webhook of webhooks) {
webhookQueue.push({
webhook,
event,
attempts: 0,
nextAttempt: Date.now()
});
}
processQueue();
}
async function processQueue() {
const now = Date.now();
for (const item of webhookQueue) {
if (item.nextAttempt > now) continue;
try {
await deliverWebhook(item);
webhookQueue.splice(webhookQueue.indexOf(item), 1);
} catch (error) {
// Exponential backoff
item.attempts++;
item.nextAttempt = now + getBackoff(item.attempts);
if (item.attempts >= 5) {
// Tandai gagal setelah 5x
await markWebhookFailed(item);
webhookQueue.splice(webhookQueue.indexOf(item), 1);
}
}
}
setTimeout(processQueue, 5000);
}
function getBackoff(attempt) {
// 1m, 5m, 15m, 1h, 4h
const delays = [60000, 300000, 900000, 3600000, 14400000];
return delays[attempt - 1] || delays[delays.length - 1];
}
async function deliverWebhook({ webhook, event }) {
const signature = generateSignature(event, webhook.secret);
const response = await fetch(webhook.url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Petstore-Signature': signature,
'X-Petstore-Event': event.type
},
body: JSON.stringify(event),
timeout: 10000
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
}
}
Sisi Penerima (Klien)
// Penanganan webhook idempotent
const processedEvents = new Set();
app.post('/webhooks/petstore', async (req, res) => {
const event = JSON.parse(req.body.toString());
// Skip jika sudah diproses (idempotensi)
if (processedEvents.has(event.id)) {
return res.status(200).json({ received: true });
}
try {
await processEvent(event);
processedEvents.add(event.id);
// Bersihkan ID event lama (1000 terakhir)
if (processedEvents.size > 1000) {
const arr = Array.from(processedEvents);
arr.slice(0, arr.length - 1000).forEach(id => processedEvents.delete(id));
}
res.status(200).json({ received: true });
} catch (error) {
console.error('Webhook processing error:', error);
// Kembali 5xx untuk trigger retry
res.status(500).json({ error: 'Processing failed' });
}
});
async function processEvent(event) {
switch (event.type) {
case 'order.created':
await handleOrderCreated(event.data);
break;
// ... handle event lain
}
}
Pendekatan Hibrida
Gabungkan polling dan webhooks untuk update penting.
class OrderMonitor {
constructor(orderId, callback) {
this.orderId = orderId;
this.callback = callback;
this.pollInterval = null;
}
async start() {
// Start polling untuk feedback instan
this.startPolling();
// Daftarkan webhook untuk update real-time
await this.registerWebhook();
}
startPolling() {
this.pollInterval = setInterval(async () => {
const order = await this.fetchOrder();
this.callback(order);
if (['completed', 'cancelled'].includes(order.status)) {
this.stop();
}
}, 10000);
}
async registerWebhook() {
const response = await fetch('https://petstoreapi.com/api/v1/webhooks', {
method: 'POST',
headers: { 'Authorization': `Bearer ${TOKEN}` },
body: JSON.stringify({
url: 'https://myapp.com/webhooks/petstore',
events: [`order.${this.orderId}`],
oneTime: true // Dihapus otomatis setelah pengiriman pertama
})
});
this.webhookId = (await response.json()).id;
}
stop() {
if (this.pollInterval) {
clearInterval(this.pollInterval);
}
if (this.webhookId) {
fetch(`https://petstoreapi.com/api/v1/webhooks/${this.webhookId}`, {
method: 'DELETE'
});
}
}
}
FAQ
T: Seberapa sering polling? Tergantung kebutuhan. Untuk real-time, 30 detik. Tidak mendesak, 5 menit. Sesuaikan balance antara freshness dan beban server.
T: Bagaimana jika endpoint webhook down? Provider webhook yang baik akan retry dengan exponential backoff. Terapkan idempotensi (cek event.id) untuk handle duplikat.
T: Cara mengamankan webhooks? Verifikasi signature dengan shared secret, selalu gunakan HTTPS, dan validasi payload event.
T: Bisakah webhooks untuk data historis? Tidak. Webhooks hanya untuk event baru. Ambil data historis dengan polling atau batch API.
T: Polling atau webhooks untuk mobile app? Polling lebih simpel di mobile. Webhooks perlu push notification sebagai perantara.
T: Cara debug masalah webhook? Pakai tools seperti webhook.site untuk testing. Log semua pengiriman webhook dan sediakan riwayat event di API Anda.
Modern PetstoreAPI mendukung polling & webhooks. Lihat panduan webhooks untuk detail implementasi. Uji integrasi webhook dengan Apidog.
Top comments (0)