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.
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);
Polling-Muster:
Einfaches Polling:
GET /api/v1/orders/123
# Gibt den aktuellen Bestellstatus zurück
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
Zeitbasiertes Polling (since-based):
GET /api/v1/orders/123/events?since=1710331200
# Gibt Ereignisse seit dem Zeitstempel zurück
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
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 |
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}`);
});
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
}
);
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();
}
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');
}
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)
);
}
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"]
}'
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}`);
}
}
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
}
}
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'
});
}
}
}
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)