Webhooks vs Polling: Pola Integrasi API Mana yang Lebih Baik?

Ashley Innocent

Ashley Innocent

20 March 2026

Webhooks vs Polling: Pola Integrasi API Mana yang Lebih Baik?

Apidog untuk Perusahaan

Penerapan On-Premises

SSO & RBAC

Sesuai SOC 2

Jelajahi Apidog Enterprise

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: 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:

Contoh:

Polling baik ketika:

Kapan Menggunakan Webhooks

Baik untuk:

Contoh:

Webhooks lebih baik ketika:

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.

Mengembangkan API dengan Apidog

Apidog adalah alat pengembangan API yang membantu Anda mengembangkan API dengan lebih mudah dan efisien.