Automatischer Zahlungsabgleich heißt: Ein kleiner Job holt deine Kontoumsätze über deinen eigenen Zugang zu einem Kontoinformationsdienst ab, erkennt die Rechnungsnummer im Verwendungszweck und meldet nur diesen Treffer signiert an Fortlauf. Fortlauf ordnet ihn der Rechnung zu; bezahlt gesetzt wird sie nach deiner Bestätigung oder, wenn du es ausdrücklich einschaltest, bei exakt passendem Betrag automatisch. Eingerichtet wird einmalig in der Oberfläche, danach läuft alles per Schlüssel und Signatur.

Ein Kunde überweist, das Geld ist auf deinem Konto, und die Rechnung steht trotzdem weiter auf „Ausgestellt“, bis jemand den Kontoauszug durchsieht. Diese Anleitung zeigt dir, wie du diesen Abgleich automatisierst: vom API-Schlüssel bei deinem Kontoinformationsdienst bis zur Zahlungsquelle in Fortlauf und zur ersten zugeordneten Zahlung. Als Beispiel nutzen wir open-banking.io; die Schritte in Fortlauf sind für jeden Dienst gleich.

Hinweis: allgemeine technische Einordnung, keine Rechts- oder Steuerberatung. Wie ein Zahlungseingang in deinen Büchern zu behandeln ist, klärst du mit deiner Steuerberatung.

Was ist automatischer Zahlungsabgleich?

Zahlungsabgleich ist die Zuordnung eines Zahlungseingangs auf deinem Bankkonto zu der Rechnung, die er bezahlt. Automatisch heißt: Diese Zuordnung findet ein Programm, nicht du. Ob sie dann auch gebucht wird, legst du fest.

Das Problem: Eine Überweisung kennt keine Rechnung

Bei Kartenzahlung oder Lastschrift weiß ein System, welcher Vorgang bezahlt wurde. Bei einer SEPA-Überweisung nicht. Die Bank meldet einen Betrag, ein Datum und einen Freitext, den Verwendungszweck. Was darin steht, entscheidet dein Zahler.

Die gute Nachricht: Zahler übernehmen die Rechnungsnummer in der Regel. In unserem eigenen Geschäftskonto trugen alle bezahlten Rechnungen einer Stichprobe ihre vollständige Nummer im Verwendungszweck, verpackt in die unterschiedlichsten Formulierungen. Die Nummer überlebt, der Text drumherum nicht. Genau darauf baut der Abgleich auf. Fortlauf schreibt die Rechnungsnummer als Zahlungsreferenz in jede E-Rechnung mit Bankverbindung; mehr musst du dafür nicht tun.

So funktioniert es: abrufen, filtern, zuordnen, melden, buchen

Ein kleiner Job auf deiner Infrastruktur erledigt ein paar Mal am Tag fünf Schritte:

  1. Abrufen. Er bittet deinen Kontoinformationsdienst, die Umsätze bei der Bank zu aktualisieren, prüft, ob deine Zustimmung noch gilt und wie aktuell die Daten sind, und liest dann die Umsätze. Ein Kontoinformationsdienst ist ein Dienst nach der EU-Zahlungsdiensterichtlinie (PSD2), dem du lesenden Zugriff auf dein Konto erlaubt hast. Ob ein Anbieter selbst zugelassen ist oder den Bankzugang über einen zugelassenen Partner herstellt, steht in seinem Impressum.
  2. Filtern. Er behält nur eingegangene, gebuchte SEPA-Überweisungen. Abbuchungen, vorgemerkte Umsätze und Kartenzahlungen fallen weg.
  3. Zuordnen. Er sucht im Verwendungszweck nach der Nummer einer deiner ausgestellten Rechnungen. Nur bei genau einem Treffer geht es weiter.
  4. Melden. Er schickt den Treffer signiert an deine Zahlungsquelle in Fortlauf: Betrag, Wertstellung, Rechnungsnummer.
  5. Buchen. Fortlauf prüft die Signatur, prüft Rechnung und offenen Betrag gegen den eigenen Stand und entscheidet: automatisch verbuchen oder dir zur Bestätigung vorlegen.

Wem was gehört

Bevor es technisch wird, die Grenze, die den ganzen Aufbau trägt:

TeilWem er gehört
Der BankzugangDir. Du schließt den Vertrag mit dem Kontoinformationsdienst, du erteilst dort die Zustimmung, du hältst die Schlüssel.
Der Abgleich-JobDir. Er läuft auf deiner Infrastruktur, mit deinen Zugangsdaten.
Fortlauferhält je Treffer eine Meldung: Zu dieser Rechnung ist dieser Betrag eingegangen.

Deine Kontoumsätze bleiben bei dir. Fortlauf erfährt, dass zu einer Rechnung ein Betrag eingegangen ist, nicht, was sonst auf deinem Konto passiert. Wir betreiben diesen Aufbau für unsere eigene GmbH gegen unser eigenes Geschäftskonto; an die Rechnungs-API von Fortlauf ging dabei nie eine andere Kontobewegung als der jeweilige Treffer.

Dein Vertrag besteht mit dem Kontoinformationsdienst, nicht mit uns. Wir empfehlen keinen bestimmten Anbieter und erhalten für die Nennung von open-banking.io keine Vergütung.

Wann Fortlauf bucht und wann du bestätigst

Für jede Meldung gibt es drei mögliche Ausgänge:

Was der Verwendungszweck und der Betrag hergebenErgebnis in Fortlauf
genau eine Rechnungsnummer, Betrag auf den Cent gleich dem offenen Betrag, und du hast die automatische Verbuchung eingeschaltetVerbucht: Die Rechnung ist bezahlt, ohne dass du etwas tust.
genau eine passende Rechnung, aber der Betrag weicht ab (Teilzahlung, Überzahlung), oder die automatische Verbuchung ist aus, oder keine erkennbare Rechnungsnummer, aber genau eine offene Rechnung mit genau diesem oder fast diesem BetragBestätigung nötig: Du prüfst und ordnest mit einem Klick zu.
weder Rechnungsnummer noch Betrag passen eindeutig, oder mehrere Rechnungen passen gleich gutKein Beleg gefunden: Fortlauf rät nicht. Du ordnest die Zahlung selbst zu.

Der dritte Fall der mittleren Zeile ist wichtig: Nennt die gemeldete Nummer keine offene Rechnung (etwa weil sie inzwischen bezahlt ist oder sich der Zahler vertippt hat), schaut Fortlauf auf den Betrag und kann dir dann eine andere Rechnung vorschlagen. Prüfe deshalb bei jedem Vorschlag die Rechnungsnummer.

Auf Wunsch verbucht Fortlauf eindeutige Zahlungen selbst – nur nach deiner Zustimmung und nur für Quellen, die du einschaltest. Ohne beides legt Fortlauf dir jede Zahlung zur Bestätigung vor. Das ist Absicht. Eine fälschlich als bezahlt markierte Rechnung ist leise und teuer: Du hörst auf, einer Forderung nachzugehen, die noch offen ist. Ein Vorschlag, den du prüfst, kostet dich eine Sekunde. Und du öffnest trotzdem kein Online-Banking mehr, liest keinen Kontoauszug und tippst keinen Betrag ab.

Wie schnell ist das?

In Stunden, nicht in Sekunden. Banken liefern Umsätze über PSD2 nur auf Anfrage, und Abrufe ohne dich als Nutzer sind begrenzt. Der Abruf läuft nach einem Zeitplan, den du setzt. Echtzeit gibt es bei SEPA-Überweisungen nicht; wer sie verspricht, fragt ebenfalls in Intervallen nach. Für offene Posten ist das schnell genug, denn eine Überweisung braucht ohnehin ein bis zwei Bankarbeitstage.

Was du brauchst

  • Ein Fortlauf-Konto mit Administrator- oder Inhaber-Rolle. API-Schlüssel, Zahlungsquellen und die automatische Verbuchung verwaltest du im Kontobereich (admin.fortlauf.de); das dürfen nur diese beiden Rollen. Zahlungen prüfst und bestätigst du im Rechnungsbereich (app.fortlauf.de).
  • Einen bezahlten Tarif für den Live-Betrieb (Kompakt oder Produktiv). Echte Rechnungen und Live-API-Schlüssel gibt es erst dort (Preise). Im kostenlosen Tarif Basis spielst du denselben Aufbau in der Sandbox mit Test-Schlüsseln und Testrechnungen durch.
  • Ein Konto bei einem Kontoinformationsdienst, in dieser Anleitung open-banking.io, und dein Geschäftskonto als verbundenes Konto.
  • Einen Ort, an dem der Job läuft: ein cron-Eintrag auf einem Server, ein kleiner Container in deinem Cluster oder eine Workflow-Engine wie n8n. Er braucht ein paar Mal am Tag ein paar Sekunden.
  • Node.js ab Version 20 oder Go ab Version 1.24 für die offiziellen SDKs des Dienstes. Die Beispiele unten sind in TypeScript, die Signatur zusätzlich in Go und als Shell-Befehl.

Der Code in dieser Anleitung ist ein Bauplan für deine eigene Umgebung: Ausschnitte, die du zu einem Job zusammensetzt. Zwei Dinge baust du dabei selbst ein: einen Schalter für den Trockenlauf (Schritt 4) und die Entscheidung, was du als Verwendungszweck meldest (Schritt 3).

Schritt 1: open-banking.io einrichten

App entsperren

open-banking.io verschlüsselt deine Kontodaten mit einem Schlüssel, den nur du hältst. Beim ersten Öffnen legst du deshalb eine Passphrase fest; laut Anbieter verlässt sie dein Gerät nicht. Ohne diese Passphrase kommst du nicht an deine Daten, auch der Anbieter nicht. Bewahre sie in deinem Passwortmanager auf.

open-banking.io: Eingabe der Passphrase zum Entsperren der App

Bankkonto verbinden und Zugriff erlauben

Im nächsten Schritt wählst du deine Bank und meldest dich dort an. Deine Bank fragt dich mit einer starken Kundenauthentifizierung, zum Beispiel per App-Freigabe, ob der Dienst lesend auf dein Konto zugreifen darf. Das ist die PSD2-Zustimmung: Sie erlaubt nur das Lesen, keine Überweisung.

open-banking.io: Seite „Connect a bank“ mit Land Deutschland, Kontoart Business und der Auswahl der verfügbaren Banken

Diese Zustimmung ist begrenzt gültig, je nach Bank 90 bis 180 Tage. Wann sie bei dir abläuft, liefert der Dienst als Datum mit; dein Job liest es bei jedem Lauf und warnt dich rechtzeitig (siehe Schritt 3). Ist sie abgelaufen, verbindest du das Konto in der App neu.

open-banking.io: verbundenes Geschäftskonto mit Status „Active“, letztem Abruf, verbleibender Gültigkeit der Zustimmung und dem Knopf „Reconnect“ (Bankname unkenntlich gemacht)

API-Schlüssel erzeugen

Auf der Entwicklerseite des Dienstes führen dich vier Schritte zur ersten Abfrage: Schlüssel erzeugen, Zugangsdatei exportieren, SDK installieren, erster Aufruf.

open-banking.io: Entwicklerseite mit den vier Schritten Schlüssel erzeugen, Zugangsdatei exportieren, SDK installieren, erster Aufruf

Unter „Create an API key“ vergibst du nur einen Namen, zum Beispiel „Zahlungsabgleich“. Der Dienst zeigt den Schlüssel ein einziges Mal an. Kopiere ihn sofort in den Secret-Speicher, aus dem dein Job liest.

Laut Anbieter gewährt der Schlüssel denselben Zugriff, den du selbst auf deine Daten hast. Lege für den Abgleich einen eigenen Schlüssel an, den du unabhängig von anderen Anwendungen widerrufen kannst.

open-banking.io: Dialog zum Erzeugen eines API-Schlüssels mit dem Feld Name

Zugangsdatei exportieren

Der Schlüssel allein reicht nicht. Deine Umsätze liegen beim Dienst verschlüsselt, und dein Job entschlüsselt sie selbst. Dafür exportierst du unter „Export credentials.json“ eine Datei, die den API-Schlüssel mit deinem privaten Entschlüsselungsschlüssel verbindet. Beim Download heißt sie open-banking.io-credentials-JJJJ-MM-TT.json; benenne sie in credentials.json um oder gib dem SDK den Pfad an.

Diese Datei ist der wertvollste Schlüssel im ganzen Aufbau. Der private Schlüssel darin entschlüsselt jede Buchung deines Kontos: Miete, Gehälter, Lieferanten, Zahler, die nie von Fortlauf gehört haben. Deshalb:

  • Die Datei gehört nur auf den Server, auf dem der Job läuft, nie in einen Browser, eine mobile App oder ein Repository, auch kein privates.
  • In Kubernetes legst du sie als Secret an und bindest sie schreibgeschützt ein; so empfiehlt es auch der Anbieter.
  • Lass den Job in einer eigenen Umgebung laufen, getrennt von anderen Anwendungen.
  • Lösche die heruntergeladene Datei aus deinem Download-Ordner, sobald sie im Secret-Speicher liegt.

open-banking.io: Export der Zugangsdatei credentials.json

SDK installieren und Konto-Kennung finden

open-banking.io bietet offizielle SDKs für mehrere Sprachen an, darunter TypeScript (npm install @open-banking-io/client) und Go (go get github.com/open-banking-io/clients/go). Beide kommen ohne weitere Abhängigkeiten aus und lesen die Zugangsdatei direkt ein.

open-banking.io: Installationsbefehl für das Node.js-SDK

Mit getAccounts() liest du die Kennung des Kontos aus, das du abgleichen willst. Der Job fragt nur dieses eine Konto ab.

Warum eine eigene Zahlungsquelle und nicht einfach der API-Schlüssel?

In Fortlauf legst du gleich zwei Dinge an: einen API-Schlüssel und eine Zahlungsquelle. Die naheliegende Frage: Fortlauf hat doch eine API, mit der man Zahlungen erfassen kann (POST /invoices/{id}/payments). Warum bucht der Job nicht einfach damit?

Kurz gesagt: Eine Zahlung aus deinem Bankkonto ist etwas anderes als eine Zahlung, die ein Mensch eintippt, und ein Job, der ohne Aufsicht läuft, sollte so wenig dürfen wie möglich. Vier Gründe:

1. Die Herkunft bleibt erhalten. Das Zahlungsjournal einer Rechnung wird nur erweitert: Einträge werden angehängt, nie geändert. Eine Zahlung, die über den API-Schlüssel erfasst wird, steht dort mit der Herkunft „Manuell erfasst“, als Angabe eines Menschen. Kommt sie in Wahrheit von deiner Bank, lässt sich das später nicht mehr unterscheiden. Eine Meldung über die Zahlungsquelle steht dort mit der Herkunft „Aus Zahlungseingang“ und verweist auf die Meldung, aus der sie stammt.

2. Jede Meldung hinterlässt eine Spur. Fortlauf protokolliert jede Meldung an eine Zahlungsquelle, auch die abgewiesenen: wann sie kam, ob die Signatur stimmte, einen Fingerabdruck des Inhalts und was daraus wurde, zum Beispiel „Verbucht“, „Bestätigung nötig“, „Duplikat“ oder „Kein Beleg gefunden”. Den vollständigen Inhalt speichert Fortlauf nicht. Von einer verarbeiteten Meldung speichert Fortlauf den gemeldeten Verwendungszweck und die zugeordnete Rechnung; von einer Meldung, die wegen Signatur, Zeitstempel oder unlesbarem Inhalt abgewiesen wurde, speichert Fortlauf keinen Verwendungszweck. Was einmal protokolliert ist, lässt sich nachträglich nicht ändern, und bei einer automatischen Verbuchung hält Fortlauf fest, auf Grund welcher Zustimmung gebucht wurde.

3. Doppelt gemeldet heißt nicht doppelt gebucht, auch nach Wochen nicht. Dein Job prüft bei jedem Lauf die letzten zwei Wochen und sieht dieselbe Überweisung deshalb viele Male. Darauf sind die beiden Wege sehr unterschiedlich vorbereitet:

  • Zahlungsquelle: Jede Meldung trägt eine eventId, die du aus der Buchungskennung der Bank ableitest. Fortlauf verarbeitet je Zahlungsquelle jede angenommene eventId nur einmal, ohne Ablaufdatum. Kommt dieselbe Buchung am nächsten Tag oder in drei Wochen erneut, antwortet Fortlauf „Duplikat“.
  • API-Schlüssel: Für das direkte Erfassen gibt es keinen solchen natürlichen Schlüssel. Dort sicherst du jede Wiederholung selbst über einen Idempotency-Key ab, und Fortlauf merkt sich diesen Schlüssel nur 24 Stunden. Ein Job, der jeden Tag die letzten zwei Wochen prüft, würde dieselbe Überweisung am nächsten Tag also erneut erfassen, wenn er nicht selbst Buch darüber führt, was er schon gemeldet hat.

4. Der Job bekommt nur, was er braucht. Mit der Zahlungsquelle hält der Job einen API-Schlüssel, der Rechnungen und Zahlungen nur lesen kann, und einen Signaturschlüssel, der nur eines erlaubt: Meldungen an genau diese eine Quelle zu schicken, für ein Unternehmen und eine Umgebung. Damit lässt sich nichts lesen, kein Beleg erstellen und, solange du die automatische Verbuchung nicht einschaltest, auch nichts buchen. Für das direkte Erfassen bräuchte der Job dagegen „Zahlungen erfassen und stornieren”, also das Recht, jede offene Rechnung als bezahlt zu markieren, ohne Zuordnung und ohne Menschen dazwischen.

API-Schlüssel + POST /invoices/{id}/paymentsZahlungsquelle
Herkunft im Zahlungsjournal„Manuell erfasst“„Aus Zahlungseingang“
Protokoll jeder Meldungnur die erfasste Zahlungjede Meldung, auch abgewiesene, unveränderlich
Schutz vor DoppelbuchungIdempotency-Key, selbst zu setzen, gilt 24 StundeneventId aus der Bankbuchung, je Quelle ohne Ablauf
Wer entscheidet die Zuordnung?dein Job alleinFortlauf prüft gegen den eigenen Stand
Rechte des JobsZahlungen erfassen und stornierenLesen + Meldungen an eine Quelle
Ohne Einschalten der automatischen Verbuchungwird sofort gebuchtwird dir zur Bestätigung vorgelegt

Den direkten Weg gibt es trotzdem, und er hat seinen Platz, wenn eine Zahlung nicht aus einem Bankabruf stammt. Für einen automatischen Abgleich deines Bankkontos ist die Zahlungsquelle der richtige Weg.

Schritt 2: Fortlauf vorbereiten

API-Schlüssel anlegen

Damit der Job weiß, welche Rechnungen offen sind, liest er sie über die Fortlauf-API. Dafür bekommt er einen eigenen API-Schlüssel mit nur zwei Rechten.

  1. Öffne den Kontobereich und gehe zu API-Schlüssel.
  2. Wähle API-Schlüssel erstellen und gib einen Namen, etwa „Zahlungsabgleich open-banking.io“.
  3. Modus: die Umgebung, in der deine echten Rechnungen liegen, also Live. Zahlungsquelle, Schlüssel und Rechnungen müssen in derselben Umgebung sein.
  4. Unternehmen: Wähle das Unternehmen, für das du die Zahlungsquelle anlegst. Hast du mehrere Unternehmen angelegt, wähle hier das richtige. Ein Schlüssel für das ganze Konto liest nur die Rechnungen deines zuerst angelegten Unternehmens.
  5. Berechtigungen: Das Formular hat alle Berechtigungen vorausgewählt. Entferne alle Haken bis auf „Dokumente lesen“ (documents:read) und „Zahlungen lesen“ (payments:read). Mehr braucht der Job nicht: Er liest offene Rechnungen und ihre offenen Beträge. Gemeldet wird über die Zahlungsquelle, nicht über diesen Schlüssel.
  6. Der Schlüssel wird nur einmal angezeigt. Kopiere ihn sofort in den Secret-Speicher.

Kontobereich: neuer API-Schlüssel im Modus Live mit nur „Dokumente lesen“ und „Zahlungen lesen“; Beispiel eines Kontos mit nur einem Unternehmen

Kontobereich: der neue API-Schlüssel wird einmalig angezeigt (hier unkenntlich gemacht)

Zahlungsquelle anlegen

Jetzt bekommt Fortlauf einen Absender, der Zahlungen melden darf. Ohne angelegte Quelle weist Fortlauf jede Meldung ab. Das Anlegen geht nur in der Oberfläche: Eine Quelle erzeugt einen Schlüssel, der Zahlungsmeldungen erlaubt, und dafür steht eine angemeldete Person mit Administrator-Rolle ein, kein API-Schlüssel.

  1. Gehe im Kontobereich zu Zahlungsquellen und wähle Quelle einrichten.
  2. Art der Quelle: „Bankkonto-Anbindung“. Die Art dient der Zuordnung im Protokoll; die Verarbeitung ist für alle Arten gleich.
  3. Prüfverfahren: „Signatur (HMAC-SHA256)“. Damit unterschreibt dein Job jede Meldung, und Fortlauf verwirft alles, was nicht von dir stammt.
  4. Ereignistypen: payment_received bleibt angehakt. Das ist der Wert, den dein Job im Feld type sendet.
  5. Umgebung: Die Quelle wird in der Umgebung angelegt, in der du gerade arbeitest, und lässt sich danach nicht mehr umstellen. Prüfe, dass es dieselbe ist wie beim API-Schlüssel. Die Quelle gehört zu dem Unternehmen, das oben links ausgewählt ist.
  6. Bestätige mit Quelle anlegen.

Kontobereich: Formular „Quelle einrichten“ mit Art Bankkonto-Anbindung, Prüfverfahren Signatur (HMAC-SHA256), Ereignistyp payment_received und Umgebung Live

Empfangsadresse und Signaturschlüssel sichern

Nach dem Anlegen zeigt dir Fortlauf zwei Werte:

  • Empfangsadresse: die vollständige Adresse https://api.fortlauf.de/v1/inbound/…, an die dein Job meldet, mit einem Knopf zum Kopieren. Der Teil nach /inbound/ ist die Kennung deiner Quelle: genau 48 Zeichen aus Ziffern und Kleinbuchstaben a–f, ohne Schrägstrich am Ende. Sie ändert sich nicht. Übernimm die Adresse am besten per Kopierknopf; schon ein fehlendes /v1 beantwortet Fortlauf mit HTTP 404.
  • Signaturschlüssel: eine Zeichenfolge, die mit insec_ beginnt. Mit ihr signiert dein Job jede Meldung. Du kannst ihn später in der Quellen-Übersicht unter Anzeigen erneut abrufen; jeder Abruf wird protokolliert.

Dein Job meldet per POST an genau diese Adresse. Lege beide Werte in denselben Secret-Speicher wie die Zugangsdatei.

Kontobereich: „Quelle angelegt“ mit Empfangsadresse und unkenntlich gemachtem Signaturschlüssel

Kontobereich: Detailansicht der Zahlungsquelle mit vollständiger Empfangsadresse, Prüfverfahren, Schlüssel-Kennung, „Noch nichts empfangen“ und den Abschnitten „Schlüssel ersetzen“ und „Quelle deaktivieren“

Wenn der Signaturschlüssel in falsche Hände gerät

Wer ihn hat, kann Meldungen an diese eine Quelle schicken, aber nichts lesen und, solange die automatische Verbuchung für die Quelle aus ist, nichts buchen. Du reagierst selbst, im Kontobereich unter Zahlungsquellen:

Kontobereich: Karte einer Zahlungsquelle mit den Abschnitten Signaturschlüssel, „Schlüssel ersetzen“, Automatische Verbuchung mit dem Knopf zum Abschalten und „Quelle deaktivieren“

  1. Automatische Verbuchung für diese Quelle abschalten, falls sie an ist. Das geht jederzeit und sofort; ab dann landet jede Meldung als „Bestätigung nötig“.

  2. Schlüssel ersetzen: Öffne die Quelle und wähle im Abschnitt Schlüssel ersetzen den Knopf Ersetzen. Fortlauf fragt auf derselben Seite nach einer Bestätigung; in der Live-Umgebung tippst du dafür ERSETZEN ein. Der bisherige Schlüssel ist sofort ungültig, ohne Übergangszeit. Halte deinen Job vorher an (oder lass ihn im Trockenlauf laufen), hinterlege den neuen Schlüssel, schicke die Probemeldung aus Schritt 4 und schalte den Job erst dann wieder scharf. Sonst weist Fortlauf die Meldungen des laufenden Jobs mit HTTP 401 ab.

    Kontobereich: Rückfrage „Signaturschlüssel jetzt ersetzen?“ mit dem Eingabefeld für das Wort ERSETZEN

  3. Oder die Quelle ganz stilllegen: Wähle im Abschnitt Quelle deaktivieren den Knopf Deaktivieren und bestätige auf derselben Seite. Danach nimmt Fortlauf an ihre Empfangsadresse keine Meldungen mehr an (HTTP 404). Eine deaktivierte Quelle lässt sich dort nicht wieder einschalten: Willst du weitermachen, legst du eine neue Quelle an; für die automatische Verbuchung bestätigst du die Einstellung dann erneut (Schritt 6).

    Kontobereich: Rückfrage „Diese Quelle deaktivieren?“ mit den drei Folgen: HTTP 404 für den Absender, Protokoll bleibt erhalten, keine Reaktivierung

Prüfe jeden Vorschlag unter „Bestätigung nötig“, der zwischen dem Verdacht und dem Ersetzen eingegangen ist, besonders sorgfältig.

Schritt 3: Den Abgleich-Job bauen

Der Job hat vier Aufgaben. Wir zeigen jede einzeln, damit du sie in deine Umgebung übertragen kannst. Die Beispiele lesen ihre Werte aus Umgebungsvariablen: OBIO_ACCOUNT_ID, FORTLAUF_API_KEY, FORTLAUF_INBOUND_URL, FORTLAUF_INBOUND_SECRET.

Umsätze aktualisieren, Zustimmung prüfen, Gutschriften lesen

open-banking.io holt Umsätze nicht von selbst nach Zeitplan bei der Bank. Ein Job, der nur liest, liest dann ewig denselben Stand: Bei uns lag der letzte Abruf einmal fünf Tage zurück, während jede Statusprüfung „Verbindung in Ordnung“ meldete. Deshalb in dieser Reihenfolge:

  1. Aktualisieren mit sync(). Scheitert das mit einem Grund, der deine Mitwirkung braucht (zum Beispiel „neu verbinden“), bricht der Job laut ab.
  2. Zustimmung prüfen: Ist die Verbindung nicht mehr aktiv, bricht der Job ab. Läuft sie bald ab (das Datum steht in validUntil), soll er dich warnen.
  3. Aktualität prüfen: Liegt der letzte Abruf bei der Bank (lastSyncedAt) länger als ein Laufintervall plus Puffer zurück, zum Beispiel 26 Stunden, oder fehlt die Angabe, gleicht der Job ab, meldet aber nichts. Ein Ergebnis auf veralteten Daten ist kein Beleg dafür, dass nichts eingegangen ist.
  4. Gutschriften lesen, und zwar alle Seiten. Die Antwort ist seitenweise aufgeteilt; wer nur die erste Seite liest, übersieht den Rest, ohne eine Fehlermeldung zu bekommen.
TypeScript
// Snippet 1 — refresh the account, check the consent, read incoming credits.
import { OpenBankingClient, SyncError } from '@open-banking-io/client';

const bank = OpenBankingClient.fromCredentials('credentials.json'); // API key + decryption key
const accountId = process.env.OBIO_ACCOUNT_ID!;

try {
  await bank.sync(accountId); // open-banking.io does not refresh on its own schedule
} catch (e) {
  if (e instanceof SyncError) throw new Error(`Bankzugang erneuern? (${e.reason})`);
  throw e;
}

const conn = (await bank.getConnections()).find((c) => c.accountIds.includes(accountId));
if (!conn?.isLive) throw new Error('Bank-Einwilligung abgelaufen — neu verbinden');

const from = new Date(Date.now() - 14 * 86_400_000).toISOString().slice(0, 10);
const credits = [];
for (let offset = 0; ; ) {
  const page = await bank.getTransactions(accountId, { from, limit: 100, offset });
  credits.push(
    ...page.items.filter(
      (t) =>
        t.creditDebitIndicator === 'CRDT' && // money in
        t.status === 'BOOK' && // booked, not pending
        /ESCT|ICDT|RCDT|RRCT/.test(t.bankTransactionCode ?? '') // SEPA credit transfer
    )
  );
  offset += page.items.length;
  if (page.items.length === 0 || offset >= page.total) break;
}

Der Ausschnitt zeigt die Zustimmungsprüfung über isLive. Die Warnung vor dem Ablauf (validUntil) und die Aktualitätsprüfung (lastSyncedAt) gehören in deinen Job dazu.

Zur Datensparsamkeit: Das SDK entschlüsselt die Umsätze einer Seite lokal auf deinem Server, bevor du filterst; der Filter begrenzt also nicht, was entschlüsselt wird, sondern was weiterverarbeitet und gemeldet wird. Umso wichtiger: Schreib keine entschlüsselten Felder ins Log, speichere keine Umsätze und gib nichts weiter als den Treffer.

Ausgestellte Rechnungen und offene Beträge lesen

Der Job liest alle ausgestellten Rechnungen über die Fortlauf-API, wieder alle Seiten. Den offenen Betrag einer Rechnung nimmt er aus ihrem Zahlungsjournal, nie aus eigener Rechnung: Fortlauf berücksichtigt dort bereits Teilzahlungen und Gutschriften.

TypeScript
// Snippet 2 — every open invoice, all pages (API key: documents:read).
const API = 'https://api.fortlauf.de/v1';
const auth = { Authorization: `Bearer ${process.env.FORTLAUF_API_KEY}` };

const invoices: { invoiceId: string; invoiceNumber: string }[] = [];
for (let page = 1; ; page++) {
  const res = await fetch(`${API}/invoices?status=issued&page=${page}&pageSize=100`, { headers: auth });
  const body = await res.json();
  for (const d of body.data) invoices.push({ invoiceId: d.id, invoiceNumber: d.invoiceNumber });
  if (page >= body.pagination.totalPages) break; // default page size is 25 — never read just one page
}

// The open amount comes from the server (API key: payments:read), never from your own arithmetic:
const ledger = await (await fetch(`${API}/invoices/${invoices[0]!.invoiceId}/payments`, { headers: auth })).json();
console.log(ledger.summary.openAmountMinor);

Die Rechnungsnummer im Verwendungszweck finden

Jetzt vergleicht der Job jeden Verwendungszweck mit den Nummern deiner Rechnungen. Zwei Fallen solltest du kennen, bevor du ein einfaches „kommt die Nummer im Text vor?“ schreibst:

  • Der Verwendungszweck wird in Blöcken zu 35 Zeichen übertragen und ohne Rücksicht auf Wortgrenzen zusammengesetzt. Wir haben „Betrag“ als „B etrag“ ankommen sehen. Eine Rechnungsnummer, die über eine Blockgrenze fällt, kommt zerteilt an, und eine einfache Textsuche übersieht diese Zahlung, ohne Fehler und ohne Log. Vergleiche deshalb nur Buchstaben und Ziffern.
  • R1 steckt in R10. Sobald du Leerzeichen ignorierst, findet eine naive Suche kurze Nummern in längeren. Vor und nach der Nummer darf deshalb keine weitere Ziffer stehen, die längste passende Nummer gewinnt, und bei einem Gleichstand gibt es keinen Treffer.

Der folgende Ausschnitt folgt derselben Regel, mit der Fortlauf selbst zuordnet:

TypeScript
// Snippet 3 — does the Verwendungszweck name an invoice? Same rule as Fortlauf's own matcher.
const canonical = (s: string) => s.toUpperCase().replace(/[^0-9A-Z]/g, '');
const digit = (c?: string) => c !== undefined && c >= '0' && c <= '9';
const alnum = (c?: string) => c !== undefined && /[0-9A-Za-z]/.test(c);

function* positions(hay: string, needle: string) {
  for (let i = hay.indexOf(needle); i >= 0; i = hay.indexOf(needle, i + 1)) yield i;
}

export function names(remittance: string, invoiceNumber: string): boolean {
  const text = remittance.toUpperCase(), token = invoiceNumber.toUpperCase();
  for (const i of positions(text, token)) // 1. the number as a whole word: "RE-2026-0042"
    if (!alnum(text[i - 1]) && !alnum(text[i + token.length])) return true;
  const hay = canonical(remittance), num = canonical(invoiceNumber);
  if (num.length < 3) return false; // short numbers only as whole words
  for (const i of positions(hay, num)) { // 2. split by a 35-char chunk seam: "RE-2026-00 42"
    const lead = !(digit(hay[i - 1]) && digit(num[0])); // no digit glued on either side,
    const trail = !(digit(hay[i + num.length]) && digit(num.at(-1))); // so R1 is not in R10
    if (lead && trail) return true;
  }
  return false;
}

export function findInvoice<T extends { invoiceNumber: string }>(remittance: string, open: T[]) {
  const hits = open.filter((inv) => names(remittance, inv.invoiceNumber));
  const c = (inv: T) => canonical(inv.invoiceNumber);
  // the longest number wins: RE2026001 beats 2026001
  const kept = hits.filter((a) => !hits.some((b) => c(b).length > c(a).length && c(b).includes(c(a))));
  return kept.length === 1 ? kept[0] : null; // none, or a tie -> a human decides, never guess
}

Der Job meldet nur einen eindeutigen Treffer. Gutschriften ohne erkennbare Rechnungsnummer meldet er nicht. Das ist bewusst so: Fortlauf erfährt nur von Zahlungen, die sich einer deiner Rechnungen zuordnen lassen.

Den Treffer signiert an Fortlauf melden

Für jeden Treffer schickt der Job eine Meldung an die Empfangsadresse deiner Quelle:

FeldInhalt
typepayment_received
eventIdeine stabile Kennung je Bankbuchung, zum Beispiel obio- plus Buchungskennung der Bank. Daran erkennt Fortlauf Duplikate.
amountMinorBetrag in Cent als ganze Zahl, also 13209 für 132,09 €. Nie als Kommazahl, nie als Feld amount.
currencyEUR
valueDateWertstellung, JJJJ-MM-TT
remittanceInfooptional; wird bei Fortlauf unveränderlich gespeichert, eine Löschfunktion gibt es derzeit nicht. Wir empfehlen: nur die Rechnungsnummer (so im Ausschnitt unten). Den vollständigen Verwendungszweck sendest du nur, wenn du das ausdrücklich willst.
invoiceNumberdie gefundene Rechnungsnummer
occurredAtdas Buchungsdatum der Bank, nicht der Zeitpunkt der Meldung

Nicht in die Meldung gehören die IBAN des Zahlers und dein Kontostand. Beim Verwendungszweck gilt: Fortlauf speichert, was du in remittanceInfo sendest, unveränderlich; eine Funktion, diesen Text wieder zu löschen, gibt es derzeit nicht. Der vollständige Verwendungszweck enthält oft den Namen des Zahlers. Bau deinen Job deshalb so, dass er dort standardmäßig nur die Rechnungsnummer sendet, wie im Ausschnitt unten; zuordnen kann Fortlauf damit genauso. Den vollständigen Text schickst du nur, wenn du ihn im Rechnungsbereich beim Prüfen sehen willst, und dann als bewusste Einstellung deines Jobs, nicht als Standard. Und füge kein zusätzliches Feld id hinzu: Es hätte Vorrang vor eventId bei der Duplikaterkennung.

Die Meldung trägt drei Kopfzeilen, die beweisen, dass sie von dir stammt:

  • Webhook-Id: die Kennung der Meldung; am einfachsten dieselbe wie eventId.
  • Webhook-Timestamp: der Zeitpunkt in Unix-Sekunden. Er ist Teil der Signatur. Fortlauf akzeptiert höchstens fünf Minuten Abweichung; signiere direkt vor dem Senden und halte die Uhr deines Servers synchron.
  • Webhook-Signature: v1, gefolgt von der Base64-kodierten HMAC-SHA256-Signatur über {Webhook-Id}.{Webhook-Timestamp}.{Inhalt}. Als Schlüssel dient der Signaturschlüssel genau so, wie Fortlauf ihn anzeigt, samt insec_, nicht Base64-dekodiert. Signiere genau die Bytes, die du sendest.
TypeScript
// Snippet 4 — sign and send one matched payment to your inbound source.
import { createHmac } from 'node:crypto';

const secret = process.env.FORTLAUF_INBOUND_SECRET!; // "insec_…", used as-is (not base64-decoded)
const event = {
  type: 'payment_received',
  eventId: 'obio-' + 'TX-ID-FROM-THE-BANK', // stable per bank transaction = dedup key
  amountMinor: 13209, // integer cents, never "132.09"
  currency: 'EUR',
  valueDate: '2026-10-01',
  remittanceInfo: 'RE-2026-0042', // only the invoice number: Fortlauf stores this field
  invoiceNumber: 'RE-2026-0042',
  occurredAt: '2026-10-01' // the bank's booking date, not "now"
};

const body = JSON.stringify(event); // sign exactly these bytes, send exactly these bytes
const ts = String(Math.floor(Date.now() / 1000));
const sig = createHmac('sha256', secret).update(`${event.eventId}.${ts}.${body}`).digest('base64');

const res = await fetch(process.env.FORTLAUF_INBOUND_URL!, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Webhook-Id': event.eventId,
    'Webhook-Timestamp': ts,
    'Webhook-Signature': `v1,${sig}`
  },
  body
});
const { outcome } = await res.json(); // read the verdict from the body — 200 is not "booked"
console.log(res.status, outcome); // e.g. 200 processed | 200 pending_confirmation | 401 rejected_signature — on 401/stale/unparseable: stop the run (see text)
if (res.status === 401 || outcome === 'rejected_stale' || outcome === 'rejected_unparseable') process.exit(1);

Dieselbe Signatur in Go, nur mit der Standardbibliothek:

Go
// Snippet 5 — the same signature in Go (standard library only).
package main

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/base64"
)

// sign returns the Webhook-Signature header value.
// secret: the inbound source's signing secret, used as raw bytes ("insec_…").
// body:   the exact bytes you POST.
func sign(secret, id, timestamp string, body []byte) string {
	mac := hmac.New(sha256.New, []byte(secret))
	mac.Write([]byte(id + "." + timestamp + "."))
	mac.Write(body)
	return "v1," + base64.StdEncoding.EncodeToString(mac.Sum(nil))
}

Die Antwort auswerten

HTTP 200 heißt nicht „gebucht“. Fortlauf antwortet auf jede korrekt signierte Meldung mit 200; das Ergebnis steht im Feld outcome der Antwort. Im Rechnungsbereich siehst du dieselben Ergebnisse unter den deutschen Bezeichnungen:

outcomeIm RechnungsbereichBedeutungWas du tust
processedVerbuchtautomatisch verbuchtnichts
pending_confirmationBestätigung nötiggenau eine passende Rechnung, darf sich aber nicht selbst buchenprüfen und zuordnen (Schritt 5)
ignored_duplicateDuplikatdiese eventId kam schon einmalnichts, wenn ein früherer Lauf sie erfolgreich gemeldet hat
rejected_no_matchKein Beleg gefundenweder Nummer noch Betrag passen eindeutigselbst zuordnen; Rechnungsnummer und Umgebung prüfen
rejected_staleVeraltetZeitstempel mehr als fünf Minuten danebenLauf abbrechen, Serveruhr prüfen
rejected_unparseableNicht lesbarInhalt kein JSON-Objekt oder Ereignistyp unbekanntLauf abbrechen, Feld type und Inhalt prüfen

Außerdem:

  • HTTP 401 (rejected_signature, „Signatur ungültig“): Die Signatur stimmt nicht. Das ist kein Netzwerkproblem, sondern fast immer ein falscher Signaturschlüssel, eine fehlende Kopfzeile (Webhook-Id, Webhook-Timestamp) oder ein Fehler beim Zusammensetzen der signierten Zeichenfolge.
  • HTTP 404: Kennung falsch, Quelle deaktiviert oder Adresse unvollständig (prüfe das /v1).
  • HTTP 429: zu viele Meldungen in kurzer Zeit (mehr als 120 pro Minute); später erneut senden.
  • Die eventId in der Antwort ist die Kennung des Eintrags bei Fortlauf, nicht die, die du gesendet hast.
  • Ein Ergebnis, das in dieser Tabelle nicht vorkommt, behandelt dein Job als Fehler, nicht als Erfolg.

Abgewiesen heißt: nichts passiert, du kannst korrigiert neu senden. Eine abgewiesene Meldung (HTTP 401, rejected_stale, rejected_unparseable) belegt ihre eventId nicht. Behebst du den Fehler und schickst dieselbe Zahlung mit derselben eventId erneut, verarbeitet Fortlauf sie ganz normal. Von einer abgewiesenen Meldung speichert Fortlauf auch keinen Freitext wie den Verwendungszweck. Ein falscher Schlüssel oder eine falsch gehende Uhr trifft aber jede weitere Meldung des Laufs genauso. Deshalb:

  • Der Job bricht beim ersten abgewiesenen oder unbekannten Ergebnis ab und meldet in diesem Lauf nichts mehr. Nach der Korrektur kann der nächste Lauf dieselben Zahlungen mit denselben Kennungen erneut melden.
  • Vor dem Scharfschalten schickst du eine Probemeldung mit einer Wegwerf-Kennung (z. B. probe-<Zeitstempel>, jedes Mal neu) und "type": "connection_test", signiert wie jede andere Meldung (Ausschnitt in Schritt 4). Antwortet Fortlauf 200 rejected_unparseable, stimmen Signaturschlüssel und Uhr. Bei 401 oder rejected_stale korrigierst du zuerst.

Ein Durchlauf ist gesund, wenn jeder Treffer angenommen wurde (processed, pending_confirmation, oder ignored_duplicate für eine Zahlung, die ein früherer Lauf schon erfolgreich gemeldet hat) und keine Meldung abgewiesen. Ein abgewiesener Lauf endet mit einem Fehlercode, damit dein Zeitplan dich benachrichtigt.

Bekommst du 401 und siehst den Fehler nicht, berechne die Signatur von Hand und vergleiche:

Bash
#!/usr/bin/env bash
# Snippet 6 — compute a signature by hand to debug a 401 (bash + openssl).
set -euo pipefail
SECRET="insec_…"                       # from the source's detail page („Anzeigen")
ID="obio-TX-ID"                        # = Webhook-Id = eventId in the body
TS="$(date +%s)"                       # = Webhook-Timestamp; must be within 5 minutes
BODY='{"type":"payment_received","eventId":"obio-TX-ID","amountMinor":13209,"currency":"EUR","invoiceNumber":"RE-2026-0042","remittanceInfo":"RE-2026-0042","occurredAt":"2026-10-01T08:00:00Z"}'

SIG="v1,$(printf '%s.%s.%s' "$ID" "$TS" "$BODY" | openssl dgst -sha256 -hmac "$SECRET" -binary | base64)"
echo "Webhook-Signature: $SIG"

Schritt 4: Probemeldung, Trockenlauf, scharf schalten

Eine verbuchte Zahlung ist ein Eintrag im Zahlungsjournal, der nicht gelöscht, sondern nur durch eine Gegenbuchung korrigiert wird. Deshalb schaltest du in Stufen scharf:

  1. Trockenlauf. Der Job tut alles, außer zu melden: aktualisieren, lesen, filtern, zuordnen, und dann nur ins Log schreiben, welche Rechnung er mit welchem Betrag gemeldet hätte, ohne weitere Umsatzdaten. Bau dafür einen Schalter in deinen Job ein, etwa eine Umgebungsvariable, die das Melden standardmäßig ausschaltet und nur auf deinen ausdrücklichen Wert hin einschaltet. Die Ausschnitte oben enthalten diesen Schalter nicht: Snippet 4 sendet, sobald es läuft, also rufe es erst hinter dem Schalter auf. Lass den Job so ein paar Tage laufen und prüfe jeden Treffer von Hand.
  2. Probemeldung. Der Trockenlauf prüft weder Signatur noch Adresse. Schicke deshalb einmal eine Probemeldung (siehe auch „Die Antwort auswerten“). Sie hat einen Ereignistyp, für den Fortlauf nichts tut, und bucht deshalb nichts. Antwortet Fortlauf 200 rejected_unparseable, stimmen Adresse, Signaturschlüssel und Uhr. Nimm jedes Mal eine neue Kennung; der Ausschnitt erzeugt sie aus der Uhrzeit.
TypeScript
// Snippet 7 — the probe: one signed, money-free test message before you arm the job.
// Fortlauf has no handler for this type, so it books nothing and answers
// 200 rejected_unparseable — which proves address, signing secret and clock are right.
import { createHmac } from 'node:crypto';

const id = `probe-${Date.now()}`; // throw-away id, never a real transaction's
const body = JSON.stringify({ type: 'connection_test', eventId: id });
const ts = String(Math.floor(Date.now() / 1000));
const sig = createHmac('sha256', process.env.FORTLAUF_INBOUND_SECRET!).update(`${id}.${ts}.${body}`).digest('base64');

const res = await fetch(process.env.FORTLAUF_INBOUND_URL!, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json', 'Webhook-Id': id, 'Webhook-Timestamp': ts, 'Webhook-Signature': `v1,${sig}` },
  body
});
const { outcome } = await res.json().catch(() => ({}));
if (res.status === 200 && outcome === 'rejected_unparseable') console.log('Probe OK');
else {
  console.error(`Probe failed: HTTP ${res.status} ${outcome ?? ''}`); // 401: secret/headers · rejected_stale: clock · 404: address
  process.exit(1);
}
  1. Melden einschalten, automatische Verbuchung noch aus. Solange du die automatische Verbuchung nicht eingeschaltet hast, landet jede angenommene Meldung als „Bestätigung nötig“ im Rechnungsbereich. Gebucht wird nichts ohne dich. So siehst du die ersten echten Zahlungen, ohne dass der Job eine Rechnung selbst bezahlt setzt.
  2. Zeitplan. Starte den ersten Lauf von Hand, lies sein Ergebnis, und übergib dann an den Zeitplan, zum Beispiel zwei- bis viermal am Tag.
  3. Automatische Verbuchung, wenn du willst. Erst wenn dich die Vorschläge überzeugt haben, schaltest du sie ein (Schritt 6).

Schritt 5: Zahlungseingänge prüfen und bestätigen

Jede angenommene Meldung erscheint im Rechnungsbereich unter Zahlungseingänge, mit Quelle, Rechnungsnummer, Verwendungszweck, Signaturprüfung und Ergebnis.

Rechnungsbereich: Liste der Zahlungseingänge mit einem verbuchten Eingang und einer Teilzahlung mit „Bestätigung nötig“

Für einen Eingang mit Bestätigung nötig:

  1. Öffne ihn. Du siehst nebeneinander den gemeldeten Betrag und den offenen Betrag der Rechnung, die Rechnungsnummer, die Wertstellung und den Verwendungszweck, den dein Job gemeldet hat (mit den Beispielen nur die Rechnungsnummer; sendest du den vollständigen Text, stammen Brüche mitten im Wort von der Bank).
  2. Prüfe, ob die vorgeschlagene Rechnung die ist, die der Zahler meint. Passen Betrag und Rechnung, wähle Zahlung zuordnen und bestätige.
  3. Die Rechnung ist bezahlt, oder teilweise bezahlt, wenn der Betrag kleiner war.

Rechnungsbereich: „Vorschlag prüfen“ für eine Teilzahlung von 297,50 € auf eine Rechnung über 595,00 € mit dem Knopf „Zahlung zuordnen“

Bestätigen dürfen Mitglieder, Administratoren und Inhaber; die Rolle „Nur Lesen“ sieht die Eingänge, kann sie aber nicht zuordnen.

Auf der Rechnung selbst steht die Zahlung danach im Zahlungsverlauf, mit Wertstellung und der Herkunft „Aus Zahlungseingang“. Eine falsche Zuordnung nimmst du dort mit Zahlung zurücknehmen zurück; das ist eine Gegenbuchung, beide Einträge bleiben sichtbar.

Rechnung im Rechnungsbereich: Zahlungsverlauf mit 1.820,70 € bezahlt, 0,00 € offen und Herkunft „Aus Zahlungseingang“

In der Rechnungsliste wechselt der Status von Ausgestellt zu Bezahlt.

Rechnungsbereich: Rechnungsliste, eine Rechnung „Bezahlt“, zwei weiterhin „Ausgestellt“

Schritt 6 (optional): Automatische Verbuchung einschalten

Wenn du eindeutige Fälle nicht mehr einzeln bestätigen willst, schaltest du die automatische Verbuchung ein. Sie greift nur, wenn alles zusammenkommt: Der Verwendungszweck nennt genau eine offene Rechnung, der Betrag entspricht auf den Cent ihrem offenen Betrag, du hast die Einstellung für dein Unternehmen bestätigt, und du hast sie für diese Zahlungsquelle eingeschaltet. Alles andere bleibt „Bestätigung nötig“.

Die Reihenfolge ist wichtig: zuerst die Zahlungsquelle anlegen (Schritt 2), dann die Einstellung für das Unternehmen bestätigen, dann die Quelle einschalten. Eine Bestätigung gilt nur für Quellen, die es zu diesem Zeitpunkt schon gab. Legst du später eine weitere Quelle an, bestätigst du die Einstellung erneut, bevor du die neue Quelle einschalten kannst.

  1. Im Kontobereich unter Zahlungsquellen auf Automatische Verbuchung einrichten gehen.
  2. Die Angaben unter „Was Sie mit dem Einschalten bestätigen“ lesen und Automatische Verbuchung einschalten wählen.
  3. Zurück in den Zahlungsquellen die Quelle öffnen und Automatische Verbuchung für diese Quelle einschalten wählen. Der Status wechselt von „Nur mit Bestätigung“ zu „Verbucht automatisch“.

Kontobereich: Seite „Automatische Verbuchung“ mit den Angaben, die du bestätigst, und dem Knopf zum Einschalten

Kontobereich: Automatische Verbuchung eingeschaltet, mit Datum, bestätigender Person und abgedeckter Quelle

Kontobereich: Detailansicht der Zahlungsquelle mit dem Status „Verbucht automatisch“

Beides kannst du jederzeit wieder abschalten; das Abschalten wird nie verweigert. Danach landet jede Meldung wieder als „Bestätigung nötig“.

Was per API geht – und was nicht

Ein Aufbau ganz ohne Oberfläche ist nicht möglich. Die Einrichtung machst du einmal im Kontobereich; danach läuft alles ohne Oberfläche, mit API-Schlüssel und Signatur.

Nur im Kontobereich

AufgabeWarum
Zahlungsquelle anlegenEine Quelle erzeugt einen Schlüssel, der Meldungen erlaubt. Dafür steht eine angemeldete Person mit Administrator-Rolle ein, kein API-Schlüssel.
Signaturschlüssel anzeigenJeder Abruf wird der Person zugeordnet und protokolliert.
Signaturschlüssel ersetzen, Quelle deaktivierenBeides ändert, wer Zahlungen melden darf; das entscheidet ein Mensch.
Automatische Verbuchung bestätigen und je Quelle einschaltenEine Zustimmung gibt ein Mensch, kein Programm.

Per API und Signatur

AufgabeWegBerechtigung
ausgestellte Rechnungen lesenGET /invoices?status=issued, alle Seitendocuments:read
offenen Betrag einer Rechnung lesenGET /invoices/{id}/paymentspayments:read
Zahlungseingang meldenPOST an die Empfangsadresse der Quelle, signiertSignaturschlüssel der Quelle, kein API-Schlüssel
gemeldete Zahlungen auflistenGET /inbound-eventspayments:read
einen Eingang mit „Bestätigung nötig“ bestätigenPOST /inbound-events/{id}/confirmpayments:write

Wenn du Eingänge per API bestätigst, etwa aus einem eigenen Freigabe-Workflow, dann mit einem anderen Schlüssel als dem, mit dem der Job liest, und nie aus dem Job selbst. Ein Job, der seine eigenen Meldungen bestätigt, ist eine automatische Verbuchung durch die Hintertür, ohne die Zustimmung, die dafür vorgesehen ist.

Der direkte Weg über POST /invoices/{id}/payments mit payments:write steht dir für Zahlungen offen, die nicht aus einem Bankabruf stammen; warum er für den Bankabgleich nicht der richtige ist, steht im Abschnitt „Warum eine eigene Zahlungsquelle und nicht einfach der API-Schlüssel?“.

Die vollständigen Felder und Antworten stehen in der API-Referenz.

Sicherheit und Betrieb

Die wichtigsten Punkte an einer Stelle:

  • Die Zugangsdatei ist das Kronjuwel. Ihr privater Schlüssel entschlüsselt dein ganzes Konto. Eigener Secret-Speicher, schreibgeschützt eingebunden, eigene Umgebung, nie in ein Repository.
  • Nichts Unnötiges loggen oder weitergeben. Das SDK entschlüsselt lokal; was dein Job davon speichert, protokolliert und meldet, entscheidest du. Gemeldet wird nur der Treffer.
  • Der Fortlauf-Schlüssel des Jobs liest nur. „Dokumente lesen“ und „Zahlungen lesen“, für das Unternehmen der Zahlungsquelle, sonst nichts.
  • Schlüssel ersetzen. Den Fortlauf-API-Schlüssel widerrufst und ersetzt du selbst unter API-Schlüssel; für den Signaturschlüssel siehe „Wenn der Signaturschlüssel in falsche Hände gerät“.
  • Erst Trockenlauf, dann Probemeldung. Baue den Schalter zum Melden so, dass er standardmäßig aus ist.
  • Abbruch bei Abweisung. Der Job hört beim ersten abgewiesenen Ergebnis auf und endet mit einem Fehlercode.
  • Zustimmung im Blick behalten. Der Job warnt vor dem Ablauf (validUntil) und scheitert laut, wenn die Verbindung neu hergestellt werden muss. Richte eine Benachrichtigung für fehlgeschlagene Läufe ein.
  • Aktualität vor Ergebnis. Auf veralteten Daten meldet der Job nichts.
  • Melden und Bestätigen getrennt. Wer per API bestätigt, tut das mit einem eigenen Schlüssel, nicht aus dem Job.
  • Eine Umgebung. API-Schlüssel, Zahlungsquelle und Rechnungen liegen in derselben Umgebung.

Grenzen und Aufbewahrung

Was dieser Aufbau nicht löst

  • Sammelüberweisungen. Ein Betrag für drei Rechnungen ergibt keinen eindeutigen Treffer. Das ist die richtige Antwort, bleibt aber Handarbeit.
  • Zahlungen ohne Rechnungsnummer. Der Job meldet sie nicht; du ordnest sie selbst zu.
  • Skonto und Teilzahlungen. 98 € auf eine 100-€-Rechnung landen als „Bestätigung nötig“, und nach deiner Bestätigung ist die Rechnung teilweise bezahlt. Was die fehlenden 2 € in deinen Büchern bedeuten, ist keine Frage des Abgleichs; das klärst du mit deiner Steuerberatung.
  • Echtzeit. Siehe oben: Stunden, nicht Sekunden.

Aufbewahrung

Fortlauf archiviert das strukturierte Original jeder Rechnung schreibgeschützt, zusammen mit dem Validierungsbericht und, wenn du über Fortlauf versendest, dem Sendeprotokoll. Das Zahlungsjournal gehört nicht zu diesem Archiv; welche Unterlagen zu deinem Zahlungsverkehr du wie lange aufbewahrst, klärst du mit deiner Steuerberatung. Mehr dazu unter GoBD-Archivierung von E-Rechnungen.

Wir haben diesen Aufbau zuerst für unsere eigene GmbH gebaut: Die Rechnung war echt, die Überweisung war echt, der Job hat sie gefunden, und bestätigt haben wir sie selbst. Die 35-Zeichen-Falle kennen wir nicht aus einer Spezifikation, sondern weil ein „B etrag“ in unserem Log stand.

Rundherum:

Selbst ausprobieren? Der Zugang ist kostenlos und ohne Karte — Produkt-Einstieg für Entwickler →

Oder den eigenen Abrechnungsfluss konkret durchsprechen: 30 Min, technisch, mit dem Gründer →

Häufige Fragen

Sieht Fortlauf meine Kontoumsätze?
Nein. Den Zugang zu deinem Konto hältst du bei deinem Kontoinformationsdienst, und der Abgleich-Job läuft auf deiner eigenen Infrastruktur. An Fortlauf meldet er nur Zahlungen, deren Verwendungszweck eine deiner Rechnungsnummern nennt, mit Betrag, Wertstellung und Rechnungsnummer. Kontostand, Lastschriften, Gehälter und alle anderen Umsätze gehen nicht an Fortlauf. Was dein Job als Verwendungszweck mitsendet, speichert Fortlauf unveränderlich; eine Funktion, diesen Text wieder zu löschen, gibt es derzeit nicht. Die Beispiele dieser Anleitung senden dort deshalb nur die Rechnungsnummer, damit kein Name eines Zahlers mitgespeichert wird.
Wird eine Rechnung automatisch auf „bezahlt“ gesetzt?
Nur wenn du es ausdrücklich einschaltest: einmal für dein Unternehmen und einmal für die einzelne Zahlungsquelle. Auch dann bucht Fortlauf nur, wenn der Verwendungszweck genau eine offene Rechnung nennt und der Betrag auf den Cent dem offenen Betrag entspricht. Alles andere erscheint im Rechnungsbereich unter „Zahlungseingänge“ mit „Bestätigung nötig“, und du ordnest es mit einem Klick zu.
Was passiert, wenn eine Zahlung zu mehreren Rechnungen passen könnte?
Dann wird nichts zugeordnet. Fortlauf rät nicht: Nennt ein Verwendungszweck mehrere gleich gute Rechnungsnummern, wird die Meldung als „Kein Beleg gefunden“ protokolliert, und du ordnest die Zahlung selbst zu. Auch ein Betrag, der zu mehreren offenen Rechnungen passt, wird nicht zugeordnet.
Wie schnell wird ein Zahlungseingang erkannt?
In Stunden, nicht in Sekunden. Banken liefern Umsätze über PSD2 nur auf Anfrage, und Abrufe ohne dich als Nutzer sind begrenzt. Ein Job, der zwei- bis viermal täglich läuft, ist für offene Posten schnell genug, denn eine SEPA-Überweisung braucht ohnehin ein bis zwei Bankarbeitstage.
Brauche ich open-banking.io?
Nein. open-banking.io ist in dieser Anleitung ein Beispiel für einen Kontoinformationsdienst; jeder Dienst, der dir Betrag, Verwendungszweck und Datum deiner Gutschriften per API liefert, funktioniert genauso. Dein Vertrag besteht mit dem Dienst, nicht mit Fortlauf. Wir empfehlen keinen bestimmten Anbieter und erhalten für die Nennung keine Vergütung.
Kann eine Zahlung doppelt gebucht werden, wenn der Job sie mehrmals meldet?
Nein. Jede Meldung trägt eine Kennung, die du aus der Buchungskennung der Bank ableitest. Fortlauf verarbeitet je Zahlungsquelle jede angenommene Kennung nur einmal, ohne zeitliche Grenze; eine erneute Meldung wird als Duplikat protokolliert und nicht noch einmal verbucht. Eine abgewiesene Meldung, etwa wegen ungültiger Signatur, belegt ihre Kennung nicht: Die korrigierte Meldung wird normal verarbeitet.