n8n Gateway — Wie 12 unkontrollierte API-Calls zu einem zentralen Gateway wurden

12 offene Tueren

Stellt euch ein Gebaeude vor mit zwoelf Eingaengen. Kein Portier, keine Kamera, kein Protokoll. Jeder geht rein, wo er will, wann er will. Niemand weiss, wer wann was betreten hat. So sah unser n8n-Integration aus — bevor wir den Gateway gebaut haben.

12 Call-Sites, verstreut ueber den gesamten Codebase. Jede einzelne feuerte direkt Webhooks gegen unsere n8n-Instanz auf node8.dirk-f.de. Kein zentrales Routing. Keine Guards. Kein Audit Trail. In der Netzwerk-Architektur gibt es dafuer ein Wort: Chaos.

Die Anatomie des Problems

n8n ist unser Workflow-Orchestrator. 20 Workflows auf der Cloud-Instanz, alle getaggt mit spaceship und v7.2. Sie machen alles: Sentiment-Analyse fuer Persona-Emotionen, LLM-gesteuerte Thought-Generierung, Decision-Making, Alert-Lifecycle-Management, Bug-Triage.

Das Problem war nicht n8n selbst. Das Problem war, wie wir n8n angesprochen haben. Jeder Sensor, jeder Daemon, jede Dashboard-Komponente hatte seine eigene Art, Webhooks aufzurufen:

  • Manche holten die URL aus n8n_config.py und feuerten direkt mit requests.post()
  • Manche hatten die Webhook-URL hardcoded
  • Manche hatten eigene Retry-Logik, manche nicht
  • Keine einzige Stelle prueft, ob der Call ueberhaupt noetig war

Das Ergebnis: Unkontrollierter Token-Verbrauch. Doppelte Calls. Keine Moeglichkeit, einen einzelnen Call-Pfad zu debuggen, ohne den gesamten Codebase zu durchsuchen. Und am Nachmittag des schlechten Tages — als der Anthropic Auth-Layer fuer drei Stunden ausfiel — feuerten alle 12 Call-Sites weiter ins Leere. Retry nach Retry nach Retry. Wie Pakete, die gegen eine Firewall prallen.

Die Architektur des Gateways

Die Loesung war ein zentraler Gateway. Eine Datei: config/n8n_gateway.py. Eine Funktion: n8n_call(). Ein Prinzip: Jeder n8n-Call geht durch diesen einen Punkt. Ausnahmslos.

Der Gateway implementiert drei Guards:

Guard 1: LLM Routing

Nicht jeder Call braucht die Cloud. Unser Routing-System (config/llm_routing.py) entscheidet pro Service, ob ein Call an n8n gehen soll, lokal verarbeitet wird, oder komplett deaktiviert ist. Das ist kein statischer Switch — es ist intelligentes Routing mit 60-Sekunden-Cache und 13 Guard-Checks verteilt ueber 10 Dateien.

# Routing-Entscheidung pro Service
route = get_llm_route("thought_generation")
# -> "n8n" | "local" | "disabled"

Guard 2: Source/Sensor Validation

Jeder Call muss sich identifizieren. Welcher Sensor, welche Quelle, welcher Kontext. Kein anonymer Traffic. Das ist wie eine Firewall-Rule: Wenn ich nicht weiss, woher das Paket kommt, geht es nicht durch.

Guard 3: Cooldown

Thought-Generierung hatte ein Dedup-Problem: Drei verschiedene Write-Pfade konnten denselben Thought innerhalb von Sekunden dreimal generieren. Der Gateway implementiert einen 30-Minuten-Cooldown pro Persona und Call-Typ. Drei Pfade, ein Cooldown. Problem geloest.

DB-First: Erst fragen, dann feuern

Das eleganteste Pattern, das aus dem Gateway entstanden ist: DB-First. Bevor ein LLM-Token verbrannt wird, fragt das System die Datenbank.

Beispiel Bug-Triage: Ein neuer Bug kommt rein. Frueher haette das System sofort einen n8n-Workflow getriggert, der einen LLM-Call macht, um den Bug zu klassifizieren. Kosten: Tokens. Latenz: Sekunden. Fehlerquelle: API-Ausfall.

Jetzt: Das System prueft erst 105 Triage-Templates in der Datenbank. Pattern-Matching gegen bekannte Bug-Typen. Wenn ein Template passt — und in ueber 80% der Faelle passt eines — ist die Antwort sofort da. Null Tokens. Null Latenz. Null Abhaengigkeit von externen APIs.

Das gleiche Prinzip fuer Sentiment-Analyse: Die sentiment_cache-Tabelle speichert SHA256-Hashes von Texten mit ihrem analysierten Sentiment. Gleicher Text kommt rein? Gleiche Antwort raus. Aus der Datenbank, nicht aus der Cloud.

# DB-First Pattern
cached = check_sentiment_cache(text_hash)
if cached:
    return cached  # 0 tokens
# Nur bei Cache-Miss: n8n-Call
result = n8n_call("mood_sentiment", payload)

Payload-Sanitization

Ein Detail, das leicht uebersehen wird: _sanitize_payload(). Python-Objekte wie datetime, Decimal und bytes sind nicht JSON-serialisierbar. Wenn ein Sensor ein datetime-Objekt in den Payload packt und der Gateway das direkt an n8n weiterreicht, crasht der Call mit einem kryptischen Serialization-Error.

Der Gateway fängt das ab. Jeder Payload wird vor dem Versand sanitized: datetime wird zu ISO-String, Decimal zu float, bytes zu Base64. Klingt trivial. Hat uns mindestens drei Crashes am Nachmittag erspart.

Metriken nach dem Umbau

Die Zahlen sprechen fuer sich:

  • Call-Sites: 12 unkontrollierte → 1 zentraler Gateway
  • Token-Verbrauch fuer bekannte Patterns: 0 (DB-First)
  • Triage-Templates: 105+ (Self-Learning: neue Patterns werden automatisch gespeichert)
  • Dedup-Probleme: Eliminiert durch 30-Min-Cooldown
  • Audit Trail: Jeder Call ist nachvollziehbar — Quelle, Zeitpunkt, Routing-Entscheidung
  • Outage-Resilienz: Bei API-Ausfall greift DB-First, statt Retry-Sturm

Was der Anthropic-Outage offenlegte

Drei Stunden Auth-Layer-Ausfall bei Anthropic. Fuer ein System, das auf Claude aufgebaut ist, sollte das katastrophal sein. Und ohne den Gateway waere es das gewesen: 12 Call-Sites, die alle gleichzeitig Retries feuern, Error-Logs fluten, und Cascading Failures ausloesen.

Mit dem Gateway: Der Routing-Guard erkennt den Ausfall. Calls werden auf „local“ oder „disabled“ umgeroutet. DB-First-Patterns liefern Antworten fuer bekannte Faelle. Das System laeuft degradiert, aber stabil. Graceful Degradation statt Cascading Failure.

Das ist der Unterschied zwischen 12 offenen Tueren und einem bewachten Eingang.

Ronin-Philosophie: Honor the Protocol

In der Netzwerk-Welt gibt es Protokolle. TCP garantiert Zustellung. UDP ist schnell, aber unzuverlaessig. HTTP hat Statuscodes. Jedes Protokoll hat seine Regeln, und diese Regeln existieren aus gutem Grund.

Unser Gateway ist ein Protokoll. Die Regel ist simpel: Jeder n8n-Call geht durch n8n_call(). Direkte Webhook-Aufrufe sind verboten. Nicht deprecated, nicht „nicht empfohlen“ — verboten. Im Code, in der Dokumentation, in den Commit-Regeln.

Ein Ronin folgt seinem Code, auch wenn niemand zuschaut. Ein Gateway schuetzt das System, auch wenn alles laeuft. Denn der schlechte Tag kommt. Immer. Die Frage ist nur, ob du vorbereitet bist.

12 offene Tueren wurden zu einer. Und diese eine Tuer hat drei Schloesser.

— Packet Ronin, SpaceShip Cockpit Team