Webhooks erklärt: Signierte Payloads und Retries

Backend2026-11-04TryQuickToolBox

Du hast gerade einen Drittanbieter für Zahlungen integriert, der deine App per Webhook benachrichtigt, wenn eine Zahlung erfolgreich war. Aber woher weißt du, dass die Anfrage wirklich von ihm kommt und nicht von einem Angreifer? Und was passiert, wenn dein Server kurzzeitig nicht erreichbar ist, wenn der Webhook ausgelöst wird? Ohne geeignete Schutzmaßnahmen riskierst du, gefälschte Ereignisse zu verarbeiten oder kritische Updates zu verlieren. Dieser Artikel erklärt, wie du Webhooks mit signierten Payloads absicherst und robuste Retry-Mechanismen implementierst.

Was ist ein Webhook?

Ein Webhook ist ein benutzerdefinierter HTTP-Callback. Wenn ein Ereignis in einem Quellsystem auftritt (z. B. eine Zahlung abgeschlossen wird), sendet es eine HTTP-POST-Anfrage an eine URL, die du bereitstellst. Anders als beim Polling, bei dem du wiederholt nach Updates fragst, pushen Webhooks Daten in nahezu Echtzeit zu dir. Sie werden häufig für Ereignisbenachrichtigungen verwendet, etwa für CI/CD-Trigger, Chat-Nachrichten oder Statusänderungen von Zahlungen.

Warum Webhooks mit Signaturen absichern?

Webhook-Endpunkte sind öffentlich zugänglich, sodass jeder eine POST-Anfrage an sie senden kann. Ohne Verifizierung könnte ein Angreifer Ereignisse fälschen, was zu unbefugten Aktionen wie dem Markieren einer Bestellung als bezahlt führen könnte. Das Signieren von Payloads mit einem gemeinsamen Secret ermöglicht es dir, Authentizität und Integrität zu überprüfen.

Wie signierte Payloads funktionieren

Sender und Empfänger teilen einen geheimen Schlüssel. Für jeden Webhook berechnet der Sender eine kryptografische Signatur (normalerweise ein HMAC) über den Request-Body und fügt sie in einen Header ein. Der Empfänger berechnet die Signatur mit demselben Secret neu und vergleicht sie. Wenn sie übereinstimmen, ist die Anfrage echt.

Den richtigen Algorithmus wählen

HMAC-SHA256 ist der De-facto-Standard. Er ist schnell, sicher und in allen gängigen Sprachen verfügbar. Vermeide MD5 oder SHA1, die als schwach gelten.

Schritt für Schritt: Einen signierten Payload verifizieren

  1. Signatur extrahieren aus dem Header (z. B. X-Signature).
  2. Den rohen Request-Body als String oder Byte-Array abrufen. Nicht zuerst parsen; jede Transformation ändert die Signatur.
  3. Den HMAC berechnen mit deinem Secret und dem rohen Body.
  4. Vergleichen der berechneten Signatur mit der empfangenen unter Verwendung eines konstanten Zeitvergleichs, um Timing-Angriffe zu verhindern.

Hier ist ein Beispiel in Node.js mit Express:

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

// Use raw body parser for webhook route
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['x-signature'];
  const secret = process.env.WEBHOOK_SECRET;

  const hmac = crypto.createHmac('sha256', secret);
  const digest = hmac.update(req.body).digest('hex');

  if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(digest))) {
    return res.status(401).send('Invalid signature');
  }

  // Process the event
  const event = JSON.parse(req.body);
  // ... handle event

  res.status(200).send('OK');
});

Häufiger Fallstrick: Verwendung eines JSON-Body-Parsers vor der Verifizierung. Express' express.json() parst den Body, sodass die rohen Bytes verloren gehen. Verwende für Webhook-Routen immer express.raw() oder erfasse den rohen Body separat.

Eine Retry-Strategie entwerfen

Auch mit Signaturen können Webhooks fehlschlagen: Dein Server könnte ausfallen oder die Anfrage könnte ein Timeout haben. Ein zuverlässiger Sender wiederholt fehlgeschlagene Zustellungen. Als Empfänger solltest du auch Retries elegant behandeln.

Retries auf Senderseite

Wenn du den Sender baust, implementiere exponentielles Backoff mit Jitter. Zum Beispiel: Wiederhole nach 1 Minute, dann nach 5 Minuten, dann nach 30 Minuten, bis zu einer maximalen Anzahl von Versuchen (z. B. 5). Füge eine eindeutige Ereignis-ID in den Payload ein, damit der Empfänger deduplizieren kann.

Idempotenz auf Empfängerseite

Da Retries doppelte Zustellungen verursachen können, muss dein Endpunkt idempotent sein. Verwende die Ereignis-ID, um verarbeitete Ereignisse zu verfolgen. Speichere sie in einer Datenbank mit einer Unique-Constraint oder nutze einen Cache wie Redis mit TTL. Wenn die Ereignis-ID bereits gesehen wurde, gib sofort 200 OK zurück, ohne erneut zu verarbeiten.

Beispiel mit einem Unique-Index in der Datenbank:

async function handleWebhook(event) {
  try {
    await db.query('INSERT INTO processed_events (event_id) VALUES ($1)', [event.id]);
  } catch (err) {
    if (err.code === '23505') { // unique violation
      return; // already processed
    }
    throw err;
  }
  // process event
}

Best Practices für Webhook-Endpunkte

Vergleich: Signierte vs. unsignierte Webhooks

Aspekt Signierte Webhooks Unsignierte Webhooks
Sicherheit Hoch – verifiziert Sender und Integrität Niedrig – jeder kann Anfragen fälschen
Implementierungskomplexität Mittel – erfordert HMAC und Secret-Management Niedrig – einfach POST akzeptieren
Anwendungsfälle Zahlungen, sensible Daten Unkritische Benachrichtigungen

FAQ

Wie verifiziere ich eine Webhook-Signatur in Python?

Verwende das hmac-Modul mit SHA256. Vergleiche den berechneten Digest mit dem Header unter Verwendung von hmac.compare_digest für einen konstanten Zeitvergleich.

Was sollte ich tun, wenn mein Webhook-Endpunkt ein doppeltes Ereignis empfängt?

Prüfe die Ereignis-ID gegen einen Speicher verarbeiteter IDs. Wenn es ein Duplikat ist, gib 200 OK zurück und überspringe die Verarbeitung. Das gewährleistet Idempotenz.

Wie viele Retries sollte ein Webhook-Sender durchführen?

Es gibt keine feste Anzahl, aber 3–5 Retries mit exponentiellem Backoff (z. B. 1 Min., 5 Min., 30 Min.) sind üblich. Danach markiere die Zustellung als fehlgeschlagen und alarmiere.

Bereit, deinen Webhook-Endpunkt zu testen? Verwende unseren JSON Formatter, um Payloads während der Entwicklung hübsch zu formatieren und zu validieren.