Checkout-Webhooks absichern: API-Key und Signatur in Make, Zapier und n8n
Aktualisiert am
Jeder Checkout-Webhook von Rechnungskit trägt zwei Sicherungen: eine HMAC-SHA256-Signatur im Header X-Rechnungskit-Signature und optional einen von dir gewählten API-Key, der als X-Api-Key und zusätzlich als x-make-apikey mitgeschickt wird. In Make aktivierst du damit die eingebaute API-Key-Authentifizierung, in n8n die Header Auth des Webhook-Knotens, in Zapier vergleichst du den Header per Filter hinter einem Catch Raw Hook.
Mit dem Rechnungskit-Checkout verkaufst du über einen Link, die Rechnung entsteht automatisch. Was nach dem Kauf passiert, steuerst du über einen Webhook: Zugang freischalten, Lizenz erzeugen, den Versand anstoßen, in Make, Zapier oder n8n weiterverarbeiten. Diese Seite zeigt, wie du deinen Webhook-Empfänger so einrichtest, dass er nur echte Nachrichten von Rechnungskit akzeptiert.
Was Rechnungskit sendet
Rechnungskit schickt bei jedem Ergebnis eines Checkouts einen POST-Request mit JSON-Body an deine URL, bei Erfolg als checkout.paid, bei Fehlschlag als checkout.failed. Jede Nachricht trägt diese Header:
| Header | Inhalt |
|---|---|
X-Rechnungskit-Signature |
sha256=<hex>, die HMAC-SHA256-Signatur des Bodys mit deinem Signatur-Secret |
X-Rechnungskit-Event-Id |
Eindeutige Event-ID für die Dubletten-Erkennung (steht auch im Body) |
X-Api-Key |
Dein optionaler API-Key, falls beim Anlegen gesetzt |
x-make-apikey |
Derselbe API-Key noch einmal, im Header-Namen, den Make nativ prüft |
Welche Ereignisse ein Endpunkt erhält, wählst du beim Anlegen (und jederzeit später) über die Ereignis-Checkliste am Webhook. Zugestellt wird mindestens einmal: Antwortet dein Empfänger nicht mit einem 2xx-Status, versucht es Rechnungskit bis zu sechsmal mit wachsendem Abstand erneut. Mit dem Test-Button am Webhook prüfst du die ganze Strecke je Ereignis mit einem Beispiel-Event, ohne einen echten Kauf auszulösen.
Abo-Ereignisse (subscription.*)
Verkaufst du über den Checkout auch Abonnements, meldet Rechnungskit dir jeden Schritt im Abo-Lebenszyklus als eigenes Ereignis, damit dein System Zugänge automatisch schalten kann:
| Ereignis | Wann es feuert | Deine typische Reaktion |
|---|---|---|
subscription.activated |
Das Abo ist aktiv (erste Zahlung bestätigt oder Test-Phase gestartet) | Zugang freischalten |
subscription.charged |
Eine Abbuchung war erfolgreich (Start, Verlängerung oder Upgrade, siehe charge.kind) |
Laufzeit verlängern, Beleg ablegen |
subscription.payment_failed |
Eine Abbuchung ist fehlgeschlagen, charge.attempt/charge.maxAttempts/charge.nextAttemptAt zeigen die Wiederholungs-Leiter |
Noch NICHT sperren, erst am Ende der Leiter |
subscription.updated |
Plan oder Menge wurden geändert, change trägt vorher/nachher |
Sitzplätze/Features anpassen |
subscription.canceled |
Eine Kündigung ist eingegangen, wirkt zum Periodenende | Vormerken, noch nichts sperren |
subscription.ended |
Das Abo ist wirklich vorbei (endedReason: period_end, immediate oder dunning) |
Jetzt Zugang sperren |
subscription.withdrawn |
Verbraucherwiderruf nach § 356a BGB | Zugang sperren, Rückzahlung läuft |
Die subscription.*-Nachrichten nutzen eine eigene, versionierte Envelope ("version": 1): Neben event, eventId und occurredAt steht unter subscription immer der komplette aktuelle Zustand (Status, Produkt, Plan, Menge, Betrag, Periode, Käufer, deine Metadaten; gebuchte Add-ons als addons-Liste). change (bei Änderungen) und charge (bei Abbuchungen) kommen als eigene Blöcke dazu. Verlasse dich auf den absoluten Zustand, nicht auf die Reihenfolge der Nachrichten: Zugestellt wird mindestens einmal und ohne Reihenfolge-Garantie; erkenne Dubletten an der eventId und nimm bei subscription.updated den Stand mit dem jüngsten occurredAt.
Werbe-Attribution im Payload (Klick-IDs und UTM)
Landet ein Käufer mit Werbe-Parametern auf dem Checkout (fbclid, gclid, msclkid, ttclid oder utm_source bis utm_term), friert Rechnungskit sie am Kauf ein und liefert sie in jeder Webhook-Nachricht mit: bei checkout.* als zusätzliches Feld attribution am Ende des Payloads, bei subscription.* innerhalb von subscription. Das Feld ist null, wenn der Käufer ohne Werbe-Parameter kam. Damit kannst du Conversions serverseitig an Werbeplattformen melden (etwa an die Meta Conversions API, mit fbclid für die Zuordnung zur Kampagne) oder deine Kanäle selbst auswerten. Die bisherige Feldreihenfolge vor dem neuen Feld bleibt unverändert, bestehende Signatur-Prüfungen laufen weiter. Zusätzlich hängt Rechnungskit dieselben Parameter (plus rk_order, rk_event_id, rk_amount_cents, rk_currency; rk_event_id ist die Event-ID, die auch die native Meta-Integration verwendet, für die Dubletten-Erkennung mit einem eigenen Browser-Pixel) an die Weiterleitung zur eigenen Danke-Seite an, wenn du am Produkt eine Return-URL hinterlegt hast, damit dort dein eigenes Conversion-Tag feuern kann.
Die Absicherung ist identisch zu den Checkout-Ereignissen: derselbe Endpunkt, derselbe API-Key, dieselbe Signatur. Ein Endpunkt kann Checkout- und Abo-Ereignisse gleichzeitig abonnieren.
Zwei Sicherungswege: API-Key und Signatur
Deine Webhook-URL ist bereits die erste Sicherung: Sie enthält einen langen Zufallsanteil und ist praktisch nicht erratbar. Wer es genauer möchte, hat zwei Stufen:
- API-Key (einfach): Beim Anlegen des Webhooks in Rechnungskit trägst du einen frei gewählten Key ein. Er wird bei jeder Zustellung als Header mitgeschickt. Dein Empfänger vergleicht nur noch den Wert, ganz ohne Kryptografie. Das ist der richtige Weg für Make, Zapier und n8n.
- HMAC-Signatur (stark): Rechnungskit signiert den exakten JSON-Body mit deinem Signatur-Secret (
whsec_…, wird beim Anlegen genau einmal angezeigt). Wer die Signatur nachrechnet, weiß sicher, dass die Nachricht von Rechnungskit stammt und nicht verändert wurde. Das ist der richtige Weg für eigenen Code und für Empfänger mit Krypto-Unterstützung.
Make: API-Key-Authentifizierung
Make prüft API-Keys nativ am Custom Webhook und erwartet den Key im Header x-make-apikey. Genau dorthin schickt Rechnungskit deinen Key automatisch, es ist also keine Umbenennung oder Zwischenlogik nötig.
- Lege in deinem Szenario das Modul Webhooks, Custom webhook an (oder öffne deinen bestehenden Webhook) und öffne die erweiterten Einstellungen des Webhooks.
- Klicke bei API Key authentication auf Add API key und lege einen neuen Schlüsselbund (Keychain) an. Trage dort exakt den Key ein, den du in Rechnungskit beim Anlegen des Webhooks als API-Key gesetzt hast. Make zeigt den Wert danach aus Sicherheitsgründen nicht mehr an.
- Speichere. Ab jetzt beantwortet Make jede Nachricht ohne passenden Key mit HTTP 401, dein Szenario startet nur noch für echte Rechnungskit-Events.
Zwei nützliche Extras in denselben Webhook-Einstellungen: IP restrictions (eine kommagetrennte Liste erlaubter Absender-IPs) und Get request headers, falls du Header-Werte im Szenario weiterverwenden möchtest. Die HMAC-Signatur lässt sich in Make ebenfalls nachrechnen (die Funktion sha256 akzeptiert einen Key-Parameter und liefert dann ein HMAC), dafür müssen aber JSON-Pass-through und Header-Übernahme aktiv sein und ein Filter die Werte vergleichen. Für Make empfehlen wir den API-Key.
n8n: Header Auth
Der Webhook-Knoten von n8n bringt die Header-Prüfung als eigene Authentifizierungsart mit:
- Öffne deinen Webhook-Knoten und stelle Authentication auf Header Auth.
- Lege unter Credential for Header Auth eine neue Credential vom Typ Header Auth an. Als Name trägst du
X-Api-Keyein, als Value deinen in Rechnungskit gesetzten API-Key. Die Groß- und Kleinschreibung des Header-Namens spielt keine Rolle. - Speichere und aktiviere den Workflow. Nachrichten ohne passenden Key lehnt n8n mit HTTP 403 ab.
Wenn du stattdessen die Signatur prüfen willst: Aktiviere am Webhook-Knoten die Option Raw Body, damit der exakte Body erhalten bleibt. Danach berechnet entweder der Crypto-Knoten (Action Hmac, Type SHA256, Encoding HEX, Secret = dein whsec_…) oder ein Code-Knoten mit require('crypto') die Signatur, und ein IF-Knoten vergleicht "sha256=" + hmac mit dem Header x-rechnungskit-signature.
Zapier: Catch Raw Hook und Filter
Zapier hat keine eingebaute Key-Prüfung am Webhook-Trigger; die unerratbare Catch-URL ist dort die Standard-Sicherung. Wenn du zusätzlich den API-Key prüfen willst, brauchst du den Trigger-Typ mit Headern:
- Verwende als Trigger Webhooks by Zapier mit dem Event Catch Raw Hook (nicht Catch Hook: der normale Catch Hook parst nur den Body und liefert keine Header). Catch Raw Hook setzt einen kostenpflichtigen Zapier-Plan voraus.
- Löse ein Test-Event aus ("Test-Ereignis senden" in Rechnungskit), damit Zapier die Struktur kennt. Die Header erscheinen mit Unterstrichen, dein Key steht also im Feld
headers X_Api_Key. - Füge direkt nach dem Trigger einen Filter by Zapier ein: Only continue if, Feld
X_Api_Key, Bedingung (Text) Exactly matches, Wert = dein API-Key.
Auch die Signatur lässt sich in Zapier prüfen: Der Catch Raw Hook liefert den unveränderten Body im Feld raw, ein Code by Zapier-Schritt (Python mit hmac/hashlib oder JavaScript mit crypto) berechnet daraus die Signatur und gibt ein Vergleichsergebnis aus, auf das der Filter prüft. Für die meisten Zaps ist der API-Key-Filter der pragmatische Weg.
Die Signatur selbst prüfen (HMAC-SHA256)
Für eigene Endpunkte (dein Server, eine Cloud Function, der n8n-Code-Knoten) rechnest du die Signatur über den unveränderten Request-Body nach, mit dem Signatur-Secret aus der Webhook-Einrichtung:
const crypto = require('crypto');
function verify(rawBody, signatureHeader, secret) {
const expected =
'sha256=' + crypto.createHmac('sha256', secret).update(rawBody, 'utf8').digest('hex');
return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signatureHeader));
}
Drei Stolpersteine aus der Praxis: Erstens muss wirklich der rohe Body gehasht werden, nicht ein neu serialisiertes JSON (schon eine andere Feldreihenfolge ändert die Signatur). Zweitens ist der Vergleich mit timingSafeEqual dem einfachen === vorzuziehen. Drittens lohnt sich die Dubletten-Erkennung über die Event-ID: Rechnungskit stellt mindestens einmal zu, bei Wiederholungen kommt dieselbe X-Rechnungskit-Event-Id erneut.
Häufige Fragen
Brauche ich API-Key und Signatur gleichzeitig?
Nein. Der API-Key ist der einfache Weg für No-Code-Werkzeuge und für die meisten Fälle völlig ausreichend, zumal deine Webhook-URL ohnehin nicht erratbar ist. Die Signatur ist der kryptografisch stärkere Nachweis, dass die Nachricht wirklich von Rechnungskit stammt und unterwegs nicht verändert wurde. Beide Header werden immer mitgeschickt, du entscheidest empfangsseitig, was du prüfst.
Wo lege ich den API-Key in Rechnungskit fest?
Beim Anlegen des Webhooks unter Einstellungen, Produkte im Feld "API-Key (optional)". Der Key wird verschlüsselt gespeichert und danach nicht mehr angezeigt. Um ihn zu ändern, legst du den Webhook neu an.
Warum sehe ich das Signatur-Secret nur einmal?
Das Secret wird wie ein Passwort behandelt: Es wird verschlüsselt gespeichert und nach der Erstellung nie wieder ausgeliefert. Kopiere es direkt beim Anlegen in dein Zielsystem. Geht es verloren, legst du den Webhook einfach neu an und erhältst ein neues Secret.
Was passiert, wenn mein Empfänger den Webhook ablehnt?
Rechnungskit stellt jede Nachricht bis zu sechsmal mit wachsendem Abstand zu (beginnend bei 15 Sekunden, verdoppelnd bis maximal eine Stunde). Jeder Versuch wird serverseitig mit Status-Code und Fehlertext protokolliert; bei hartnäckigen Zustellproblemen hilft dir der Support mit dem Protokoll weiter. Antwortet dein Empfänger mit einem 2xx-Status, gilt die Nachricht als zugestellt.
Welche Ereignisse und Daten enthält der Webhook?
Für Einmalkäufe gibt es checkout.paid und checkout.failed; der JSON-Body enthält unter anderem Produkt, Betrag und Währung, E-Mail und Name des Käufers, das Lieferland, die Rechnungsnummer (sobald erstellt) und deine durchgereichten Metadaten. Für Abos gibt es sieben eigene Ereignisse (subscription.activated bis subscription.withdrawn) mit einer versionierten Envelope, die immer den kompletten Abo-Zustand trägt. Eine eindeutige Event-ID steht jeweils im Body und im Header X-Rechnungskit-Event-Id, damit du doppelte Zustellungen erkennen kannst.