Webhooks -- Integrationsleitfaden (Empfänger-Seite)

Webhooks -- Integrationsleitfaden (Empfänger-Seite)

Dieses Dokument beschreibt, wie ein externes System (ERP, n8n, Eigenentwicklung) die Webhooks empfängt und verarbeitet.

HTTP-Request Format

Jeder Webhook wird als HTTP POST gesendet:

wide760

Authentifizierung

Neben der HMAC-Signatur (die immer mitgesendet wird) kann pro Endpunkt eine zusätzliche Authentifizierung konfiguriert werden. Das Zielsystem erhält dann einen entsprechenden Header:

Methode

Header

Beispiel

Keine

Nur X-Shop-Signature

Basic Auth

Authorization: Basic {base64}

Authorization: Basic dXNlcjpwYXNz

Bearer Token

Authorization: Bearer {token}

Authorization: Bearer sk-abc123

Custom Header (API Key)

{Name}: {Wert}

X-Api-Key: mein-geheimer-schluessel

Empfangenen Auth-Header prüfen

phpwide760jswide760

Die Authentifizierung ersetzt nicht die Signatur-Verifikation — beide sollten geprüft werden. Die Signatur stellt sicher, dass der Payload nicht manipuliert wurde, die Authentifizierung schützt den Endpunkt vor unbefugtem Zugriff.

Signatur verifizieren

Jeder Request wird mit HMAC-SHA256 signiert. Die Signatur steht im Header X-Shop-Signature im Format:

wide760,v1=]]>

Verifikations-Algorithmus

  1. Extrahiere timestamp und signature aus dem Header

  2. Baue den signierten String: {timestamp}.{request_body}

  3. Berechne HMAC-SHA256 mit dem Endpunkt-Secret

  4. Vergleiche die berechnete Signatur mit v1 aus dem Header

Beispielcode (PHP)

phpwide760 300) { return false; } $expectedSignature = hash_hmac('sha256', $timestamp . '.' . $payload, $secret); return hash_equals($expectedSignature, $signature); } // Verwendung $payload = file_get_contents('php://input'); $signatureHeader = $_SERVER['HTTP_X_SHOP_SIGNATURE'] ?? ''; $secret = 'dein-endpunkt-secret'; // aus der Webhook-Konfiguration if (!verifyWebhookSignature($payload, $signatureHeader, $secret)) { http_response_code(401); exit('Invalid signature'); } $data = json_decode($payload, true); // Webhook verarbeiten...]]>

Beispielcode (Node.js)

jswide760 { const [key, value] = part.split('=', 2); parts[key] = value; }); const timestamp = parts.t || ''; const signature = parts.v1 || ''; // Replay-Schutz if (Math.abs(Date.now() / 1000 - parseInt(timestamp)) > 300) { return false; } const expected = crypto .createHmac('sha256', secret) .update(timestamp + '.' + payload) .digest('hex'); return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature)); }]]>

Beispielcode (Python)

pywide760 bool: parts = dict(p.split('=', 1) for p in signature_header.split(',')) timestamp = parts.get('t', '') signature = parts.get('v1', '') # Replay-Schutz if abs(time.time() - int(timestamp)) > 300: return False expected = hmac.new( secret.encode(), f"{timestamp}.{payload}".encode(), hashlib.sha256 ).hexdigest() return hmac.compare_digest(expected, signature)]]>

Idempotency

Jeder Webhook-Versand hat einen eindeutigen X-Idempotency-Key Header. Da Webhooks bei Fehlern wiederholt werden, kann derselbe Event mehrfach ankommen.

Empfehlung: Speichere verarbeitete Idempotency-Keys und überspringe Duplikate:

phpwide760exists('processed_webhooks', ['key' => $idempotencyKey])) { http_response_code(200); // Erfolgreich, aber bereits verarbeitet exit; } // Webhook verarbeiten... $db->insert('processed_webhooks', ['key' => $idempotencyKey, 'processed_at' => now()]);]]>

Test-Dispatches erkennen

Manuell ausgelöste Events (über den Test-Bereich im Backend) enthalten das Feld "_test": true im Payload:

jsonwide760

Das Zielsystem kann dieses Feld prüfen um Test-Aufrufe anders zu behandeln (z.B. nicht in Produktionsdaten übernehmen).

Antwort-Verhalten

HTTP-Statuscode

Interpretation

2xx (200, 201, 204)

Erfolgreich zugestellt

3xx

Wird als Fehler gewertet (kein Redirect-Follow)

4xx

Fehler, wird wiederholt

5xx

Fehler, wird wiederholt

Timeout (> 30s)

Fehler, wird wiederholt

Wichtig: Antworte mit HTTP 200 so schnell wie möglich. Verarbeite die Daten asynchron, wenn die Verarbeitung länger dauert.

Retry-Verhalten

Bei einem Fehler wird der Webhook wiederholt:

Versuch

Wartezeit

Zeitpunkt (ab erstem Versuch)

2

1 Minute

nach 1 Min

3

5 Minuten

nach 6 Min

4

15 Minuten

nach 21 Min

5

1 Stunde

nach 1h 21min

Nach 5 fehlgeschlagenen Versuchen wird der Webhook als failed markiert.

Nach 10 aufeinanderfolgenden Fehlern (über mehrere Events hinweg) wird der Endpunkt automatisch deaktiviert (Circuit Breaker). Der Shop-Administrator wird per notification.webhook_disabled-Event an andere Endpunkte benachrichtigt.

Event-Routing

Der Event-Name steht im Header X-Shop-Event und im Payload-Feld event. Verwende diesen um zu entscheiden, wie die Daten verarbeitet werden:

phpwide760

n8n Workflow erstellen

Webhook-Node einrichten

  1. Erstelle einen neuen Workflow in n8n

  2. Füge einen Webhook-Node hinzu

  3. Setze die Methode auf POST

  4. Kopiere die generierte URL und trage sie im Shop-Backend als Endpunkt-URL ein

  5. Setze den Pfad z.B. auf /shop-webhook

Signatur-Verifikation in n8n

Füge nach dem Webhook-Node einen Code-Node hinzu:

jswide760 { const [k, v] = p.split('=', 2); parts[k] = v; }); const expected = crypto .createHmac('sha256', secret) .update(parts.t + '.' + body) .digest('hex'); if (expected !== parts.v1) { throw new Error('Invalid webhook signature'); } return $input.all();]]>

Event-basiertes Routing in n8n

Füge einen Switch-Node hinzu, der auf {{ $json.event }} prüft:

  • order.created -> Bestellung in ERP anlegen

  • order.line_item.created -> Position wurde freigegeben

  • draft_order.created -> Bestellung vormerken (wartet auf Zahlung/Freigabe)

Checkliste für die Integration

  • [ ] HTTPS-Endpunkt eingerichtet

  • [ ] Signatur-Verifikation implementiert

  • [ ] Idempotency-Prüfung eingebaut

  • [ ] Schnelle Antwort (< 5 Sekunden, idealerweise < 1 Sekunde)

  • [ ] Asynchrone Verarbeitung für aufwendige Operationen

  • [ ] Unbekannte Events werden ignoriert (nicht als Fehler gewertet)

  • [ ] _test-Flag wird geprüft um Test-Dispatches zu erkennen

  • [ ] Logging für Debugging eingerichtet

  • [ ] Test-Ping im Backend erfolgreich gesendet