DEV Community

Cover image for Webhooks vs. Polling: Welche API-Integrationsmethode ist besser?
Emre Demir
Emre Demir

Posted on • Originally published at apidog.com

Webhooks vs. Polling: Welche API-Integrationsmethode ist besser?

TL;DR: Polling prüft periodisch auf Updates (einfach, aber ineffizient). Webhooks senden Updates in Echtzeit (effizient, aber komplex). Nutzen Sie Polling für seltene Prüfungen, Webhooks für Echtzeit-Updates. Die moderne PetstoreAPI unterstützt beide Muster mit zuverlässiger Webhook-Zustellung.

Teste Apidog noch heute

Den Unterschied verstehen

Polling: Client fragt wiederholt „Gibt es Updates?“ Webhooks: Server sagt „Hier ist ein Update!“, wenn etwas passiert.

Analogie:

  • Polling = Jede Stunde den Briefkasten überprüfen
  • Webhooks = Postbote klingelt an der Tür, wenn Post ankommt

Polling: So funktioniert's

Der Client sendet periodische Anfragen, um auf Änderungen zu prüfen. Ein Minimalbeispiel:

// Alle 30 Sekunden abfragen
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);
Enter fullscreen mode Exit fullscreen mode

Polling-Muster:

Einfaches Polling:

GET /api/v1/orders/123
# Gibt den aktuellen Bestellstatus zurück
Enter fullscreen mode Exit fullscreen mode

Bedingtes Polling (ETag):

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

# Gibt 304 Not Modified zurück, wenn unverändert
# Gibt 200 mit neuen Daten zurück, wenn geändert
Enter fullscreen mode Exit fullscreen mode

Zeitbasiertes Polling (since-based):

GET /api/v1/orders/123/events?since=1710331200
# Gibt Ereignisse seit dem Zeitstempel zurück
Enter fullscreen mode Exit fullscreen mode

Webhooks: So funktionieren sie

Der Server sendet einen HTTP POST an Ihren Endpunkt, sobald ein Ereignis auftritt. So setzen Sie Webhooks um:

// 1. Webhook-Endpunkt registrieren
POST /api/v1/webhooks
{
  "url": "https://myapp.com/webhooks/petstore",
  "events": ["order.created", "order.completed"],
  "secret": "whsec_abc123"
}

// 2. Server sendet Webhook, wenn Ereignis auftritt
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 überprüfen und verarbeiten
// Mit 200 OK antworten
Enter fullscreen mode Exit fullscreen mode

Wann Polling verwenden?

Gut geeignet für:

  • Seltene Prüfungen (einmal pro Stunde)
  • Kleine Anzahl von Ressourcen
  • Einfache Implementierungen
  • Wenn Sie den Client steuern
  • Tests und Debugging

Beispiele:

  • Prüfen des täglichen Berichtsstatus
  • Synchronisieren von Kontakten alle paar Minuten
  • Überwachen der Servergesundheit
  • Prüfen des Zahlungsstatus (selten)

Polling ist in Ordnung, wenn:

  • Updates selten sind
  • Eine leichte Verzögerung akzeptabel ist
  • Sie eine einfache Implementierung wünschen
  • Die Ressource klein ist

Wann Webhooks verwenden?

Gut geeignet für:

  • Echtzeit-Updates
  • Viele zu überwachende Ressourcen
  • Zeitkritische Ereignisse
  • Integrationen von Drittanbietern
  • Häufige Updates

Beispiele:

  • Zahlungsbestätigungen
  • Chat-Nachrichten
  • Aktienkursalarme
  • Änderungen des Bestellstatus
  • CI/CD-Benachrichtigungen

Webhooks sind besser, wenn:

  • Updates sofort sein müssen
  • Polling ineffizient wäre
  • Viele Clients dieselbe Ressource überwachen
  • Sie die Serverlast reduzieren möchten

Vergleichstabelle

<!--kg-card-begin: html-->



















































Faktor Polling Webhooks
Latenz Bis zum Polling-Intervall Echtzeit
Serverlast Hoch (viele leere Anfragen) Niedrig (nur tatsächliche Ereignisse)
Komplexität Einfach Komplex
Zuverlässigkeit Hoch (Client steuert Wiederholung) Mittel (erfordert Wiederholungslogik)
Setup Keines Endpunktregistrierung
Firewall-Probleme Keine (nur ausgehend) Muss ggf. freigegeben werden
Kosten Höher (mehr Anfragen) Niedriger (weniger Anfragen)
Am besten für Seltene Prüfungen Echtzeit-Updates
<!--kg-card-end: html-->

Polling implementieren

Grundlegendes Polling

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 nur, wenn sich der Status geändert hat
      if (order.status !== lastStatus) {
        lastStatus = order.status;
        callback(order);
      }

      // Polling stoppen, wenn Endstatus erreicht
      if (['completed', 'cancelled'].includes(order.status)) {
        return;
      }

      // Polling fortsetzen
      setTimeout(poll, 5000);
    } catch (error) {
      console.error('Polling error:', error);
      setTimeout(poll, 30000); // Bei Fehler zurücktreten
    }
  };

  poll();
}

// Verwendung
pollOrderStatus('order-123', (order) => {
  console.log(`Order status: ${order.status}`);
});
Enter fullscreen mode Exit fullscreen mode

Smart Polling (Exponentieller 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, wenn sich Daten geändert haben
      if (JSON.stringify(data) !== JSON.stringify(lastData)) {
        lastData = data;
        callback(data);
      }

      // Stoppen, wenn Bedingung erfüllt
      if (stopCondition(data)) {
        return;
      }

      // Intervall bei erfolgreicher Anfrage zurücksetzen
      interval = initialInterval;

    } catch (error) {
      retries++;
      if (retries >= maxRetries) {
        throw new Error('Max retries exceeded');
      }
    }

    // Nächstes Polling mit exponentiellem Backoff planen
    setTimeout(poll, interval);
    interval = Math.min(interval * 2, maxInterval);
  };

  poll();
}

// Verwendung: Bestellung abfragen, bis abgeschlossen
smartPoll('https://petstoreapi.com/api/v1/orders/123',
  (order) => console.log('Order:', order),
  {
    stopCondition: (order) => ['completed', 'cancelled'].includes(order.status),
    initialInterval: 2000,
    maxInterval: 30000
  }
);
Enter fullscreen mode Exit fullscreen mode

Polling mit 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) {
      // Nicht geändert, Polling fortsetzen
      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

Webhooks implementieren

Webhooks registrieren

// Webhook-Endpunkt registrieren
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

Webhooks empfangen

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

// Raw Body Parser für Signaturprüfung
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;

  // Signatur prüfen
  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());

  // Ereignis verarbeiten
  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;
  }

  // Empfang bestätigen
  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

Webhooks lokal testen

# ngrok verwenden, um den lokalen Endpunkt freizugeben
ngrok http 3000

# ngrok URL als Webhook-Endpunkt registrieren
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

Zuverlässige Webhook-Zustellung

Webhooks können fehlschlagen. Implementieren Sie eine Wiederholungslogik.

Senderseite (Server)

// Webhooks zur Zustellung in die Warteschlange stellen
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);
      // Bei Erfolg aus der Warteschlange entfernen
      webhookQueue.splice(webhookQueue.indexOf(item), 1);
    } catch (error) {
      // Wiederholung mit exponentiellem Backoff planen
      item.attempts++;
      item.nextAttempt = now + getBackoff(item.attempts);

      if (item.attempts >= 5) {
        // Nach 5 Versuchen als fehlgeschlagen markieren
        await markWebhookFailed(item);
        webhookQueue.splice(webhookQueue.indexOf(item), 1);
      }
    }
  }

  setTimeout(processQueue, 5000);
}

function getBackoff(attempt) {
  // 1min, 5min, 15min, 1Std, 4Std
  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

Empfängerseite (Client)

// Idempotente Webhook-Behandlung
const processedEvents = new Set();

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

  // Überspringen, wenn bereits verarbeitet (Idempotenz)
  if (processedEvents.has(event.id)) {
    return res.status(200).json({ received: true });
  }

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

    // Alte Event-IDs bereinigen (die letzten 1000 behalten)
    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);
    // 5xx zurückgeben, um Wiederholung auszulösen
    res.status(500).json({ error: 'Processing failed' });
  }
});

async function processEvent(event) {
  // Das Ereignis verarbeiten
  switch (event.type) {
    case 'order.created':
      await handleOrderCreated(event.data);
      break;
    // ... andere Ereignisse behandeln
  }
}
Enter fullscreen mode Exit fullscreen mode

Hybridansatz

Verwenden Sie sowohl Polling als auch Webhooks für kritische Updates und maximale Zuverlässigkeit:

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

  async start() {
    // Mit Polling für sofortiges Feedback beginnen
    this.startPolling();

    // Webhook für Echtzeit-Update registrieren
    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 // Automatische Löschung nach der ersten Zustellung
      })
    });

    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

FAQ

F: Wie oft sollte ich pollen? Hängt von der Dringlichkeit ab. 30 Sekunden für nahezu Echtzeit. 5 Minuten für nicht dringende Fälle. Balancieren Sie Aktualität mit Serverlast.

F: Was, wenn mein Webhook-Endpunkt nicht erreichbar ist? Gute Webhook-Anbieter versuchen es mit exponentiellem Backoff erneut. Implementieren Sie Idempotenz, um doppelte Zustellungen zu handhaben.

F: Wie sichere ich Webhooks? Überprüfen Sie Signaturen mithilfe von Shared Secrets. Verwenden Sie nur HTTPS. Validieren Sie Ereignisdaten.

F: Kann ich Webhooks für historische Daten verwenden? Nein. Webhooks sind nur für neue Ereignisse. Verwenden Sie Polling oder Batch-APIs für historische Daten.

F: Sollte ich Polling oder Webhooks für mobile Apps verwenden? Polling ist einfacher für Mobilgeräte. Webhooks erfordern Push-Benachrichtigungen als Vermittler.

F: Wie debugge ich Webhook-Probleme? Verwenden Sie Tools wie webhook.site zum Testen. Protokollieren Sie alle Webhook-Zustellungen. Stellen Sie eine Webhook-Ereignishistorie in Ihrer API bereit.

Die moderne PetstoreAPI unterstützt sowohl Polling als auch Webhooks. Details zur Implementierung finden Sie im Webhook-Leitfaden. Testen Sie Webhook-Integrationen mit Apidog.

Top comments (0)