TL;DR: Polling memeriksa pembaruan secara berkala (sederhana namun tidak efisien). Webhooks mengirimkan pembaruan secara real-time (efisien namun kompleks). Gunakan polling untuk pemeriksaan yang tidak sering, webhooks untuk pembaruan real-time. Modern PetstoreAPI mendukung kedua pola ini dengan pengiriman webhook yang andal.
Memahami Perbedaannya
Polling: Klien bertanya "Ada pembaruan?" berulang kali. Webhooks: Server berkata "Ini pembaruannya!" ketika sesuatu terjadi.
Analogi:
- Polling = Memeriksa kotak surat Anda setiap jam
- Webhooks = Tukang pos membunyikan bel pintu ketika surat tiba
Polling: Cara Kerjanya
Klien membuat permintaan berkala untuk memeriksa perubahan.
// 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"
# Mengembalikan 304 Not Modified jika tidak berubah
# Mengembalikan 200 dengan data baru jika berubah
Polling berbasis sejak (since-based):
GET /api/v1/orders/123/events?since=1710331200
# Mengembalikan peristiwa sejak timestamp
Webhooks: Cara Kerjanya
Server mengirim HTTP POST ke endpoint Anda ketika peristiwa 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 ketika peristiwa terjadi
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 dengan 200 OK
Kapan Menggunakan Polling
Baik untuk:
- Pemeriksaan yang tidak sering (sekali per jam)
- Jumlah sumber daya yang sedikit
- Implementasi yang sederhana
- Ketika Anda mengontrol klien
- Pengujian dan debug
Contoh:
- Memeriksa status laporan harian
- Menyinkronkan kontak setiap beberapa menit
- Memantau kesehatan server
- Memeriksa status pembayaran (tidak sering)
Polling baik ketika:
- Pembaruan jarang terjadi
- Penundaan kecil dapat diterima
- Anda menginginkan implementasi yang sederhana
- Sumber daya kecil
Kapan Menggunakan Webhooks
Baik untuk:
- Pembaruan real-time
- Banyak sumber daya untuk dipantau
- Peristiwa yang sensitif waktu
- Integrasi pihak ketiga
- Pembaruan frekuensi tinggi
Contoh:
- Konfirmasi pembayaran
- Pesan obrolan
- Peringatan harga saham
- Perubahan status pesanan
- Notifikasi CI/CD
Webhooks lebih baik ketika:
- Pembaruan harus segera
- Polling akan menjadi tidak efisien
- Banyak klien memantau sumber daya yang sama
- Anda ingin mengurangi beban server
Tabel Perbandingan
| 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 coba lagi) | Menengah (membutuhkan logika coba lagi) |
| Pengaturan | Tidak ada | Pendaftaran endpoint |
| Masalah Firewall | Tidak ada (hanya keluar) | Mungkin perlu whitelisting |
| Biaya | Lebih tinggi (lebih banyak permintaan) | Lebih rendah (lebih sedikit permintaan) |
| Terbaik untuk | Pemeriksaan yang tidak sering | 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();
// Hanya panggil callback jika status berubah
if (order.status !== lastStatus) {
lastStatus = order.status;
callback(order);
}
// Hentikan polling jika dalam status akhir
if (['completed', 'cancelled'].includes(order.status)) {
return;
}
// Lanjutkan polling
setTimeout(poll, 5000);
} catch (error) {
console.error('Polling error:', error);
setTimeout(poll, 30000); // Mundur pada kesalahan
}
};
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();
// Panggil callback jika data berubah
if (JSON.stringify(data) !== JSON.stringify(lastData)) {
lastData = data;
callback(data);
}
// Hentikan jika kondisi terpenuhi
if (stopCondition(data)) {
return;
}
// Atur ulang interval pada permintaan yang berhasil
interval = initialInterval;
} catch (error) {
retries++;
if (retries >= maxRetries) {
throw new Error('Max retries exceeded');
}
}
// Jadwalkan polling berikutnya dengan exponential backoff
setTimeout(poll, interval);
interval = Math.min(interval * 2, maxInterval);
};
poll();
}
// Penggunaan: Poll pesanan 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) {
// Tidak dimodifikasi, lanjutkan polling
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 tanda tangan
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 tanda tangan
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;
}
// Akui penerimaan
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
# Gunakan ngrok untuk mengekspos endpoint lokal
ngrok http 3000
# Daftarkan 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 logika coba lagi.
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);
// Hapus dari antrean setelah berhasil
webhookQueue.splice(webhookQueue.indexOf(item), 1);
} catch (error) {
// Jadwalkan coba lagi dengan exponential backoff
item.attempts++;
item.nextAttempt = now + getBackoff(item.attempts);
if (item.attempts >= 5) {
// Tandai sebagai gagal setelah 5 kali percobaan
await markWebhookFailed(item);
webhookQueue.splice(webhookQueue.indexOf(item), 1);
}
}
}
setTimeout(processQueue, 5000);
}
function getBackoff(attempt) {
// 1 menit, 5 menit, 15 menit, 1 jam, 4 jam
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());
// Lewati 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 (pertahankan 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);
// Kembalikan 5xx untuk memicu coba lagi
res.status(500).json({ error: 'Processing failed' });
}
});
async function processEvent(event) {
// Proses event
switch (event.type) {
case 'order.created':
await handleOrderCreated(event.data);
break;
// ... tangani event lain
}
}
Pendekatan Hibrida
Gunakan polling dan webhooks untuk pembaruan penting.
class OrderMonitor {
constructor(orderId, callback) {
this.orderId = orderId;
this.callback = callback;
this.pollInterval = null;
}
async start() {
// Mulai dengan polling untuk umpan balik instan
this.startPolling();
// Daftarkan webhook untuk pembaruan 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 // Hapus 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 saya harus melakukan polling?Tergantung urgensi. 30 detik untuk mendekati real-time. 5 menit untuk yang tidak mendesak. Seimbangkan kesegaran data dengan beban server.
T: Bagaimana jika endpoint webhook saya mati?Penyedia webhook yang baik akan mencoba lagi dengan exponential backoff. Implementasikan idempotensi untuk menangani pengiriman duplikat.
T: Bagaimana cara mengamankan webhooks?Verifikasi tanda tangan menggunakan shared secrets. Gunakan HTTPS saja. Validasi data event.
T: Bisakah saya menggunakan webhooks untuk data historis?Tidak. Webhooks hanya untuk event baru. Gunakan polling atau API batch untuk data historis.
T: Haruskah saya menggunakan polling atau webhooks untuk aplikasi seluler?Polling lebih sederhana untuk seluler. Webhooks memerlukan notifikasi push sebagai perantara.
T: Bagaimana cara men-debug masalah webhook?Gunakan alat seperti webhook.site untuk pengujian. Catat semua pengiriman webhook. Sediakan riwayat event webhook di API Anda.
Modern PetstoreAPI mendukung polling dan webhooks. Lihat panduan webhooks untuk detail implementasi. Uji integrasi webhook dengan Apidog.
