Webhook-Idempotenzschlüssel für sichere Wiederholungen

Nutze dies, wenn dieselbe Webhook-Wiederholung den Auftrag zweimal ausführt und du einen stabilen Schlüssel brauchst.

Füge einen Webhook-Körper ein, benenne die Ereignis-ID und erhalte den Idempotenzschlüssel (die Ereignis-ID oder ein SHA-256 des Körpers, wenn keine ID vorhanden ist), das Claim-SQL und ein Aufräumfenster. Der Schlüssel wird in diesem Browser berechnet. Diese Seite lädt nicht hoch, was du einfügst.

Idempotenzschlüssel
Wird berechnet
Schlüssel halten
72 Stunden

SQL-Schema


        

Redis-Anspruch


        

n8n-Code-Node


      

Warum derselbe Webhook zweimal laufen kann

Anbieter wiederholen Zustellungen, daher kann dein Empfänger dasselbe Ereignis mehr als einmal sehen. Stripe schreibt das in der Dokumentation und empfiehlt, die bereits verarbeiteten Ereignis-IDs zu protokollieren (Stripe: doppelte Ereignisse). Wenn dein Endpoint zu lange braucht oder einen Fehler zurückgibt, sendet der Anbieter das Ereignis erneut, und Stripe wiederholt eine Zustellung im Live-Modus bis zu drei Tage lang mit exponentiellem Backoff (Stripe: automatische Wiederholungen). Wenn sich nichts merkt, dass das Ereignis schon verarbeitet wurde, läuft der Auftrag dahinter noch einmal: eine zweite Abbuchung, eine zweite Bestellung, eine zweite CRM-Zeile. In einem Shop kann ein Webhook, der zweimal läuft, für eine Bestellung zwei Sendungen oder zwei Gutscheine auslösen.

Ein stabiler Schlüssel, nur einmal beansprucht

Die Lösung ist ein Schlüssel, der über alle Wiederholungen gleich bleibt und nur einmal beansprucht wird. Jede Stripe-Wiederholung trägt eine neue Signatur und einen neuen Zeitstempel, daher kann der Header stripe-signature nicht der Schlüssel sein. Die Ereignis-ID schon: Die Vorlage nutzt evt: plus die ID aus dem Körper, und wenn der Körper keine ID hat, sha256: plus den SHA-256 des rohen Körpers. Der Claim ist eine einzige Anweisung auf einem Primärschlüssel, INSERT ... ON CONFLICT (key) DO NOTHING RETURNING key, daher bekommt nur eine Zustellung die Zeile zurück, auch wenn zwei gleichzeitig ankommen. Kommt eine Zeile zurück, läuft der Geschäftsschritt. Kommt keine zurück, wurde das Ereignis schon gesehen: Der Empfänger antwortet trotzdem mit 200, und Abbuchung, Bestellung oder CRM-Zeile entstehen kein zweites Mal. Die Reihenfolge der Prüfungen ist auch für die Sicherheit wichtig. Prüfe zuerst die Signatur des Anbieters (Stripe: Ereignisse prüfen) und beanspruche erst dann den Schlüssel, damit eine gefälschte Anfrage keine echte Ereignis-ID beanspruchen kann. Die Vorlage prüft keine Signaturen, diese Prüfung gehört also davor.

Webhook-Deduplizierung: Schlüssel in Postgres beanspruchen, dann nach der zurückgegebenen Zeile verzweigen Ein Webhook kommt an. Der Deduplizierungsschlüssel ist evt: plus Ereignis-ID, oder sha256: plus SHA-256 des Rohkörpers, wenn es keine ID gibt. Postgres führt INSERT ... ON CONFLICT (key) DO NOTHING RETURNING key aus. Kommt eine Zeile zurück, ist das Ereignis neu: verarbeiten. Kommt keine Zeile, ist es ein Duplikat: überspringen und mit 200 antworten. Anbieter Deduplizierungsschlüssel ohne ID: Zeile zurück keine Zeile Webhook kommt an neu: verarbeiten Duplikat: überspringen mit 200 antworten Postgres evt:<id> sha256:<hex> hex = sha256(raw_body) INSERT INTO processed_events (key, first_seen) VALUES ($1, now()) ON CONFLICT (key) DO NOTHING RETURNING key;
Ablauf der n8n-Vorlage auf dieser Seite: Schlüssel bilden, in Postgres beanspruchen, dann danach verzweigen, ob eine Zeile zurückkommt.

Was passiert ist, als wir es ausgeführt haben

Wir haben die n8n-Vorlage unten auf unserer eigenen Maschine gegen Postgres ausgeführt, mit n8n 2.42.6 und Postgres 16. Wir haben Ereignis A gesendet, dann Ereignis A noch einmal mit einem anderen Signatur-Header, dann Ereignis B. n8n hat 3 Ausführungen aufgezeichnet und die Geschäftstabelle hat 2 Zeilen bekommen. Die Wiederholung von A bekam eine 200-Antwort und hat den Geschäftsschritt nicht ausgeführt, die Wiederholung wurde also beantwortet und nichts wurde doppelt geschrieben. Danach haben wir zweimal einen Körper ohne ID gesendet: Der Schlüssel war der SHA-256 des rohen Körpers, und der zweite Aufruf nahm den Duplikat-Zweig. In einer separaten Prüfung haben wir mit dem SQL, das diese Seite erzeugt, 8 gleichzeitige Claims für denselben Schlüssel an Postgres geschickt: Einer bekam die Zeile zurück, und der Geschäftsschritt lief einmal.

n8n-Ausführungsansicht der Vorlage: Die wiederholte Zustellung von Ereignis A läuft durch den Claim-Schritt, bekommt keine Zeile zurück und nimmt den Zweig Respond 200 duplicate. Der Geschäftsschritt lief nicht.
Screenshot aus unserem Lauf, n8n 2.42.6 mit Postgres 16: Die zweite Zustellung von Ereignis A nahm den Duplikat-Zweig.

So kommt es in n8n

Wer n8n betreibt, kann den Workflow importieren, den wir nach diesem Lauf exportiert haben, ohne die Credential-ID aus dem Labor, ihn mit Postgres verbinden und den Geschäftsschritt durch eigene Nodes ersetzen. Der Webhook-Node nutzt den Antwortmodus "Using Respond to Webhook node", daher antwortet der Zweig, der gelaufen ist. Der Code-Node baut den Schlüssel, ein Postgres-Node führt den Claim aus, und "Row Returned?" schickt ein neues Ereignis zum Geschäftsschritt und eine Wiederholung zu "Respond 200 duplicate".

n8n-Workflow-Vorlage (Webhook, Code, Postgres, IF, Respond to Webhook)

Der exportierte Workflow als .json-Datei, bereit zum Import.

Direkter Link zur JSON-Datei
  1. Tabellen anlegen

    CREATE TABLE IF NOT EXISTS processed_events (
      key text PRIMARY KEY,
      first_seen timestamptz NOT NULL DEFAULT now()
    );
    -- Beispiel-Geschäftstabelle der Vorlage. Ersetze sie durch deinen eigenen Schritt.
    CREATE TABLE IF NOT EXISTS business_log (
      id bigserial PRIMARY KEY,
      event_key text NOT NULL,
      event_type text,
      created_at timestamptz NOT NULL DEFAULT now()
    );
  2. Umgebung und Zugangsdaten setzen

    Selbst gehostetes n8n blockiert eingebaute Module im Code-Node standardmäßig. Setze NODE_FUNCTION_ALLOW_BUILTIN=crypto im n8n-Container, damit require('crypto') funktioniert. Öffne nach dem Import beide Postgres-Nodes und wähle deine Postgres-Credential (Host, Port, Datenbank, Benutzer, Passwort). n8n kann je nach deinen Ausführungseinstellungen auch Ausführungsdaten speichern, einschließlich des Webhook-Körpers. Wenn der Körper personenbezogene Daten enthält, prüfe diese Einstellungen und ihre Aufbewahrungsdauer.

  3. Importieren, eigenen Schritt anschließen, veröffentlichen

    Importiere in n8n die .json-Datei. Ersetze "Business Step (insert business_log)" durch deine eigenen Nodes, lass sie auf dem true-Zweig von "Row Returned?" und veröffentliche den Workflow. Setze deine Signaturprüfung vor den Code-Node, wenn dein Anbieter Anfragen signiert.

  4. Mit einer wiederholten Zustellung testen

    Sende dasselbe Ereignis zweimal und ein anderes Ereignis einmal. Unter Executions sollten der erste und der dritte Lauf durch den Geschäftsschritt gehen und der zweite "Respond 200 duplicate" nehmen.

  5. Alte Schlüssel aufräumen

    Das Aufräumen ist ein eigener Schritt außerhalb des Webhook-Workflows. Halte Schlüssel länger, als der Anbieter erneut senden kann. Stripe erlaubt ein manuelles erneutes Senden aus dem Dashboard bis zu 15 Tage lang und über die CLI bis zu 30 Tage lang (Stripe: manuelle Wiederholungen). Ein geplanter n8n-Workflow oder ein Cron-Job kann Folgendes ausführen:

    DELETE FROM processed_events WHERE first_seen < now() - interval '30 days';

Grenze: Der Schlüssel wird beansprucht, bevor der Geschäftsschritt läuft. Schlägt der Geschäftsschritt nach dem Claim fehl, wird eine Wiederholung dieses Ereignisses als Duplikat behandelt. Füge einen Fehlerpfad hinzu, der DELETE FROM processed_events WHERE key = $1 für diesen Schlüssel ausführt, oder behebe den Fehler und spiele das Ereignis von Hand erneut ein.

Was gespeichert wird und was in deinem Browser bleibt

Der Schlüssel auf dieser Seite wird mit crypto.subtle.digest in deinem Browser berechnet, und das Seitenskript stellt mit dem, was du einfügst, keine Netzwerkanfrage. In Postgres speichert processed_events den Schlüssel und einen Zeitstempel. Der Schlüssel ist die Ereignis-ID des Anbieters oder ein SHA-256 des Körpers, wenn keine ID vorhanden ist. Die Beispieltabelle business_log speichert den Schlüssel und den Ereignistyp, und dein eigener Geschäftsschritt entscheidet, was sonst noch gespeichert wird. Der Planer oben erzeugt denselben Claim wie die n8n-Vorlage: INSERT ... ON CONFLICT (key) DO NOTHING RETURNING key, einen Zweig nach der zurückgegebenen Zeile und ein separates Aufräum-DELETE. Diese Hinweise beschreiben den Code auf dieser Seite und in der Vorlage. Sie sind keine Zertifizierung.

Doppelte Abbuchungen oder doppelte CRM-Zeilen?

Schick den Ablauf. Wir antworten innerhalb von 2-3 Werktagen mit einer kurzen schriftlichen Prüfung.

Kostenlose Problemprüfung anfordern