DEV Community

Cover image for Webhooks ve Polling: Hangi API Entegrasyon Yöntemi Daha İyi?
Tobias Hoffmann
Tobias Hoffmann

Posted on • Originally published at apidog.com

Webhooks ve Polling: Hangi API Entegrasyon Yöntemi Daha İyi?

Kısaca: Yoklama (Polling), güncellemeleri belirli aralıklarla kontrol eder (basit ama verimsiz). Web kancaları (Webhooks), olayları gerçek zamanlı iletir (verimli ama daha karmaşık). Seyrek kontroller için yoklama, anlık bildirimler için web kancaları tercih edilmelidir. Modern PetstoreAPI, güvenilir webhook teslimatı ile iki modeli de destekler.

Bugün Apidog'u deneyin

Farkı Anlamak

Yoklama (Polling): İstemci belirli aralıklarla "Güncelleme var mı?" diye sorar. Web Kancaları (Webhooks): Sunucu, bir olay oluştuğunda otomatik olarak bildirir.

  • Yoklama = Posta kutunuzu her saat kontrol etmek
  • Web Kancası = Postacı, posta geldiğinde kapı zilini çalar

Yoklama (Polling): Nasıl Çalışır?

İstemci, değişiklikleri algılamak için periyodik HTTP istekleri gönderir.

// Her 30 saniyede bir yokla
setInterval(async () => {
  const response = await fetch('https://petstoreapi.com/api/v1/orders/123');
  const order = await response.json();

  if (order.status === 'completed') {
    console.log('Sipariş tamamlandı!', order);
    clearInterval(pollInterval);
  }
}, 30000);
Enter fullscreen mode Exit fullscreen mode

Yoklama Yaklaşımları:

Basit Yoklama:

GET /api/v1/orders/123
# Mevcut sipariş durumunu döndürür
Enter fullscreen mode Exit fullscreen mode

Koşullu Yoklama (ETag):

GET /api/v1/orders/123
If-None-Match: "abc123"

# Değişmediyse 304 Not Modified
# Değiştiyse yeni verilerle 200 OK
Enter fullscreen mode Exit fullscreen mode

Zamana Dayalı Yoklama (Since-based polling):

GET /api/v1/orders/123/events?since=1710331200
# Belirtilen zamandan bu yana olayları döndürür
Enter fullscreen mode Exit fullscreen mode

Web Kancaları (Webhooks): Nasıl Çalışır?

Sunucu, olaylar gerçekleştiğinde otomatik olarak belirlediğiniz uç noktaya HTTP POST gönderir.

Kurulum adımları:

// 1. Webhook uç noktasını kaydet
POST /api/v1/webhooks
{
  "url": "https://myapp.com/webhooks/petstore",
  "events": ["order.created", "order.completed"],
  "secret": "whsec_abc123"
}

// 2. Sunucu, olayda webhook gönderir
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. Webhook'u doğrula ve işle
// 200 OK ile yanıt verin
Enter fullscreen mode Exit fullscreen mode

Yoklama (Polling) Ne Zaman Kullanılmalı?

Şu durumlar için uygundur:

  • Seyrek kontrol ihtiyacı (saatte bir gibi)
  • Az sayıda kaynak
  • Basit uygulamalar
  • İstemciyi siz yönetiyorsanız
  • Test ve debug amaçlı

Örnekler:

  • Günlük rapor durumu sorgulama
  • Kişileri birkaç dakikada bir senkronize etme
  • Sunucu sağlık kontrolü
  • Seyrek ödeme durumu kontrolü

Yoklama tercih edilir:

  • Güncellemeler nadir ise
  • Küçük gecikmeler tolere edilebiliyorsa
  • Uygulama sade olacaksa
  • Küçük ölçekli kaynaklarda

Web Kancaları (Webhooks) Ne Zaman Kullanılmalı?

Şu durumlar için uygundur:

  • Gerçek zamanlı güncelleme ihtiyacı
  • Çok sayıda kaynak izleniyorsa
  • Zaman kritik olaylar
  • Üçüncü parti entegrasyonları
  • Yüksek frekanslı güncellemeler

Örnekler:

  • Ödeme onayları
  • Sohbet mesajları
  • Borsa fiyat uyarıları
  • Sipariş durumu değişiklikleri
  • CI/CD bildirimleri

Web kancaları daha iyi olur:

  • Anında güncelleme gerekiyorsa
  • Yoklama verimsizse
  • Aynı kaynağı birçok istemci izliyorsa
  • Sunucu yükünü azaltmak istiyorsanız

Karşılaştırma Tablosu

Faktör Yoklama (Polling) Web Kancaları (Webhooks)
Gecikme Yoklama aralığına kadar Gerçek zamanlı
Sunucu yükü Yüksek (çok sayıda boş istek) Düşük (sadece gerçek olaylar)
Karmaşıklık Basit Karmaşık
Güvenilirlik Yüksek (istemci yeniden denemeyi kontrol eder) Orta (yeniden deneme mantığı gerekir)
Kurulum Yok Uç nokta kaydı
Güvenlik duvarı sorunları Yok (yalnızca giden) Beyaz listeye alma gerekebilir
Maliyet Daha yüksek (daha fazla istek) Daha düşük (daha az istek)
En iyi kullanım Sık olmayan kontroller Gerçek zamanlı güncellemeler

Yoklama (Polling) Uygulaması

Temel Yoklama

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();

      // Sadece durum değişirse callback çalıştır
      if (order.status !== lastStatus) {
        lastStatus = order.status;
        callback(order);
      }

      // Terminal duruma geldiyse yoklamayı bitir
      if (['completed', 'cancelled'].includes(order.status)) {
        return;
      }

      // Devam et
      setTimeout(poll, 5000);
    } catch (error) {
      console.error('Yoklama hatası:', error);
      setTimeout(poll, 30000); // Hata alırsa beklemeyi artır
    }
  };

  poll();
}

// Kullanım
pollOrderStatus('order-123', (order) => {
  console.log(`Sipariş durumu: ${order.status}`);
});
Enter fullscreen mode Exit fullscreen mode

Akıllı Yoklama (Üstel Geri Çekilme)

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();

      // Değişiklik varsa callback çalıştır
      if (JSON.stringify(data) !== JSON.stringify(lastData)) {
        lastData = data;
        callback(data);
      }

      // Durdurma koşulu varsa bitir
      if (stopCondition(data)) {
        return;
      }

      // Başarılı istekte interval sıfırlanır
      interval = initialInterval;

    } catch (error) {
      retries++;
      if (retries >= maxRetries) {
        throw new Error('Maksimum yeniden deneme aşıldı');
      }
    }

    // Üstel geri çekilme ile devam et
    setTimeout(poll, interval);
    interval = Math.min(interval * 2, maxInterval);
  };

  poll();
}

// Kullanım: Sipariş tamamlanana kadar yokla
smartPoll('https://petstoreapi.com/api/v1/orders/123',
  (order) => console.log('Sipariş:', order),
  {
    stopCondition: (order) => ['completed', 'cancelled'].includes(order.status),
    initialInterval: 2000,
    maxInterval: 30000
  }
);
Enter fullscreen mode Exit fullscreen mode

ETag ile Yoklama

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) {
      // Değişiklik yok, devam et
      setTimeout(poll, 30000);
      return;
    }

    const data = await response.json();
    etag = response.headers.get('etag');

    callback(data);
    setTimeout(poll, 30000);
  };

  poll();
}
Enter fullscreen mode Exit fullscreen mode

Web Kancaları (Webhooks) Uygulaması

Web Kancalarını Kaydetme

// Webhook uç noktasını kaydet
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');
}
Enter fullscreen mode Exit fullscreen mode

Web Kancalarını Alma

const express = require('express');
const crypto = require('crypto');
const app = express();

// İmza doğrulaması için ham body kullanın
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;

  // İmzayı doğrula
  const isValid = verifySignature(body, signature, process.env.WEBHOOK_SECRET);

  if (!isValid) {
    return res.status(401).json({ error: 'Geçersiz imza' });
  }

  const event = JSON.parse(body.toString());

  // Olayı işle
  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;
  }

  // Başarılı yanıt
  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)
  );
}
Enter fullscreen mode Exit fullscreen mode

Yerel Olarak Web Kancalarını Test Etme

# Yerel endpoint'i açığa çıkarmak için ngrok kullan
ngrok http 3000

# ngrok URL'sini webhook endpoint olarak kaydet
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"]
  }'
Enter fullscreen mode Exit fullscreen mode

Güvenilir Web Kancası Teslimatı

Web kancalarının iletimi başarısız olabilir. Yeniden deneme (retry) mantığını uygulamak şarttır.

Gönderici Tarafı (Sunucu)

// Web kancalarını teslim için sıraya al
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);
      // Başarılı olduysa sıradan çıkar
      webhookQueue.splice(webhookQueue.indexOf(item), 1);
    } catch (error) {
      // Retry için üstel bekleme
      item.attempts++;
      item.nextAttempt = now + getBackoff(item.attempts);

      if (item.attempts >= 5) {
        // 5 deneme sonra başarısız olarak işaretle
        await markWebhookFailed(item);
        webhookQueue.splice(webhookQueue.indexOf(item), 1);
      }
    }
  }

  setTimeout(processQueue, 5000);
}

function getBackoff(attempt) {
  // 1dk, 5dk, 15dk, 1sa, 4sa
  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}`);
  }
}
Enter fullscreen mode Exit fullscreen mode

Alıcı Tarafı (İstemci)

// Idempotent webhook işleme
const processedEvents = new Set();

app.post('/webhooks/petstore', async (req, res) => {
  const event = JSON.parse(req.body.toString());

  // Zaten işlendiyse atla (idempotency)
  if (processedEvents.has(event.id)) {
    return res.status(200).json({ received: true });
  }

  try {
    await processEvent(event);
    processedEvents.add(event.id);

    // Eski event id'lerini temizle (son 1000'i tut)
    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 işleme hatası:', error);
    // Retry için 5xx döndür
    res.status(500).json({ error: 'İşleme başarısız oldu' });
  }
});

async function processEvent(event) {
  // Event'i işle
  switch (event.type) {
    case 'order.created':
      await handleOrderCreated(event.data);
      break;
    // ... diğer eventler
  }
}
Enter fullscreen mode Exit fullscreen mode

Hibrit Yaklaşım

Kritik güncellemeler için hem yoklama hem de web kancalarını birlikte kullanabilirsiniz.

class OrderMonitor {
  constructor(orderId, callback) {
    this.orderId = orderId;
    this.callback = callback;
    this.pollInterval = null;
  }

  async start() {
    // Hızlı yanıt için polling başlat
    this.startPolling();

    // Gerçek zamanlı güncelleme için webhook kaydet
    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 // İlk tetiklemeden sonra otomatik sil
      })
    });

    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'
      });
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Sıkça Sorulan Sorular

S: Ne sıklıkla yoklama yapmalıyım? Aciliyetine göre değişir. Gerçek zamanlıya yakın için 30 saniye, acil değilse 5 dakika uygundur. Uygulamanın güncelliği ile sunucu yükünü dengeleyin.

S: Web kancası endpoint'im kapalıysa ne olur? Kaliteli webhook sağlayıcıları, üstel geri çekilme ile yeniden dener. Yinelenen teslimatları idempotency ile yönetin.

S: Web kancalarını nasıl güvenli hale getiririm? İmzaları paylaşılan bir gizli anahtarla doğrulayın. Sadece HTTPS kullanın. Olay verilerini doğrulayın.

S: Web kancaları geçmiş veriler için kullanılabilir mi? Hayır. Web kancaları sadece yeni olaylar içindir. Geçmiş için polling veya toplu API kullanın.

S: Mobil uygulamalarda polling mi, webhook mu kullanmalıyım? Polling mobilde daha basittir. Webhook için push bildirimi altyapısı gereklidir.

S: Web kancası sorunlarını nasıl ayıklarım? Test için webhook.site gibi araçları kullanın. Tüm webhook teslimatlarını loglayın. API'nizde webhook olay geçmişi sağlayın.

Modern PetstoreAPI, polling ve web kancalarını destekler. Detaylı uygulama için web kancası kılavuzuna göz atın. Web kancası entegrasyonlarınızı Apidog ile test edebilirsiniz.

Top comments (0)