Webhooks
Die Frihet-Webhooks ermöglichen den Empfang von HTTP-Benachrichtigungen in Echtzeit, wenn Ereignisse in Ihrem Konto auftreten. Anstatt die API abzufragen, konfigurieren Sie eine URL, und Frihet sendet einen POST mit den Ereignisdaten zum Zeitpunkt ihres Auftretens.
Konfiguration
Sie können Webhooks über das Frihet-Dashboard (Einstellungen > Webhooks) oder programmatisch über die REST-API verwalten.
Über das Dashboard
- Gehen Sie in Ihrem Frihet-Konto zu Einstellungen > Webhooks.
- Klicken Sie auf Webhook erstellen.
- Geben Sie einen aussagekräftigen Namen, die Ziel-URL ein und wählen Sie die Ereignisse aus, die Sie empfangen möchten.
- Optional definieren Sie ein Geheimnis für die HMAC-Verifizierung (empfohlen).
- Speichern Sie den Webhook.
Webhook REST API
Verwalten Sie Webhooks programmatisch mit vollständigen CRUD-Endpoints.
Webhooks auflisten
GET /v1/webhooks
curl https://api.frihet.io/v1/webhooks \
-H "X-API-Key: fri_tu-clave-aqui"
Antwort (200):
{
"data": [
{
"id": "wh_abc123",
"url": "https://mi-app.com/hook",
"events": ["invoice.paid", "invoice.created"],
"active": true,
"createdAt": "2026-02-01T10:00:00.000Z"
}
],
"total": 1,
"limit": 50,
"offset": 0
}
Webhook abrufen
GET /v1/webhooks/:id
curl https://api.frihet.io/v1/webhooks/wh_abc123 \
-H "X-API-Key: fri_tu-clave-aqui"
Webhook erstellen
POST /v1/webhooks
| Feld | Typ | Erforderlich | Beschreibung |
|---|---|---|---|
url | string | Ja | Ziel-URL (HTTPS in Produktion obligatorisch) |
events | string[] | Ja | Liste der zu empfangenden Ereignistypen |
secret | string | Nein | Geheimnis für HMAC-SHA256-Signatur (empfohlen) |
active | boolean | Nein | Webhook-Status (Standard: true) |
curl -X POST https://api.frihet.io/v1/webhooks \
-H "X-API-Key: fri_tu-clave-aqui" \
-H "Content-Type: application/json" \
-d '{
"url": "https://mi-app.com/hook",
"events": ["invoice.paid", "expense.created"],
"secret": "mi-secreto-hmac"
}'
Antwort (201):
{
"id": "wh_def456",
"url": "https://mi-app.com/hook",
"events": ["invoice.paid", "expense.created"],
"active": true,
"createdAt": "2026-03-18T10:00:00.000Z"
}
Webhook aktualisieren
PATCH /v1/webhooks/:id
Sie müssen nur die Felder senden, die Sie ändern möchten.
curl -X PATCH https://api.frihet.io/v1/webhooks/wh_def456 \
-H "X-API-Key: fri_tu-clave-aqui" \
-H "Content-Type: application/json" \
-d '{
"events": ["invoice.paid", "invoice.created", "expense.created"],
"active": false
}'
Antwort (200): Aktualisiertes Webhook-Objekt.
Webhook löschen
DELETE /v1/webhooks/:id
curl -X DELETE https://api.frihet.io/v1/webhooks/wh_def456 \
-H "X-API-Key: fri_tu-clave-aqui"
Antwort: 204 No Content
URL-Anforderungen:
- Muss HTTPS verwenden (HTTP ist nur für
localhostund127.0.0.1während der Entwicklung erlaubt) - Private IPs (10.x, 172.16-31.x, 192.168.x) sind nicht erlaubt, um SSRF-Angriffe zu verhindern
- Muss innerhalb von 30 Sekunden mit einem 2xx-Code antworten
Limits:
- Maximal 20 Webhooks pro Konto
- Maximale Payload-Größe von 100 KB pro Zustellung
Ereignistypen
Frihet sendet 14 Ereignistypen, gruppiert nach Ressource.
Rechnungen (4 Ereignisse)
| Ereignis | Beschreibung |
|---|---|
invoice.created | Eine neue Rechnung wurde erstellt |
invoice.updated | Eine bestehende Rechnung wurde geändert |
invoice.paid | Eine Rechnung wurde als bezahlt markiert |
invoice.overdue | Eine Rechnung hat ihr Fälligkeitsdatum überschritten |
Ausgaben (2 Ereignisse)
| Ereignis | Beschreibung |
|---|---|
expense.created | Eine neue Ausgabe wurde erfasst |
expense.updated | Eine bestehende Ausgabe wurde geändert |
Angebote (4 Ereignisse)
| Ereignis | Beschreibung |
|---|---|
quote.created | Ein neues Angebot wurde erstellt |
quote.updated | Ein bestehendes Angebot wurde geändert |
quote.accepted | Ein Kunde hat ein Angebot angenommen (Statusänderung zu accepted) |
quote.rejected | Ein Kunde hat ein Angebot abgelehnt (Statusänderung zu rejected) |
Kunden (2 Ereignisse)
| Ereignis | Beschreibung |
|---|---|
client.created | Ein neuer Kunde wurde registriert |
client.updated | Die Kundendaten wurden geändert |
Produkte (2 Ereignisse)
| Ereignis | Beschreibung |
|---|---|
product.created | Ein neues Produkt oder eine Dienstleistung wurde erstellt |
product.updated | Ein bestehendes Produkt oder eine Dienstleistung wurde geändert |
Payload-Struktur
Jede Webhook-Zustellung ist eine POST-Anfrage mit einem JSON-Body. Die Struktur ist für alle Ereignistypen gleich:
{
"event": "invoice.paid",
"timestamp": "2026-02-12T14:30:00.000Z",
"data": {
"id": "inv_abc123",
"clientName": "Acme S.L.",
"items": [
{ "description": "Consultoria", "quantity": 10, "unitPrice": 75 }
],
"total": 750,
"status": "paid",
"paidAt": "2026-02-12T14:29:58.000Z",
"createdAt": "2026-01-15T10:30:00.000Z",
"updatedAt": "2026-02-12T14:30:00.000Z"
}
}
Das data-Feld enthält den vollständigen Zustand der Ressource zum Zeitpunkt des Ereignisses.
Zustellungs-Header
Jede Webhook-Anfrage enthält die folgenden Header:
| Header | Beschreibung | Beispiel |
|---|---|---|
Content-Type | Inhaltstyp | application/json |
X-Frihet-Event | Ereignistyp | invoice.paid |
X-Frihet-Delivery-Id | Eindeutiger Zustellungs-Identifikator | d4e5f6a7b8c9 |
X-Frihet-Timestamp | ISO 8601 Zeitstempel | 2026-02-12T14:30:00.000Z |
X-Frihet-Signature | HMAC-SHA256-Signatur der Payload | sha256=a1b2c3d4e5f6... |
Der X-Frihet-Signature-Header wird nur eingeschlossen, wenn Sie ein Geheimnis im Webhook konfiguriert haben.
Signaturprüfung
Wenn Sie beim Erstellen des Webhooks ein Geheimnis konfigurieren, signiert Frihet jede Payload mit HMAC-SHA256. Der Verifizierungsprozess besteht aus:
- Den Roh-Body der Anfrage nehmen
- Den HMAC-SHA256 unter Verwendung des Geheimnisses als Schlüssel berechnen
- Das Ergebnis mit dem Wert des
X-Frihet-Signature-Headers vergleichen (ohne das Präfixsha256=)
Mit dem SDK (empfohlen)
import { Webhooks } from '@frihet/sdk';
app.post('/webhook/frihet', express.raw({ type: 'application/json' }), (req, res) => {
const isValid = Webhooks.verifySignature(
req.body,
req.headers['x-frihet-signature'],
process.env.FRIHET_WEBHOOK_SECRET,
);
if (!isValid) return res.status(401).send('Ungültige Signatur');
const event = JSON.parse(req.body.toString());
console.log(req.headers['x-frihet-event'], event.data);
res.sendStatus(200);
});
Manuelles Beispiel in Node.js
const crypto = require('crypto');
function verifyWebhookSignature(rawBody, signature, secret) {
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex');
const receivedSignature = signature.replace('sha256=', '');
return crypto.timingSafeEqual(
Buffer.from(expectedSignature, 'hex'),
Buffer.from(receivedSignature, 'hex')
);
}
// Verwendung in einem Express-Server
app.post('/webhook/frihet', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-frihet-signature'];
const secret = process.env.FRIHET_WEBHOOK_SECRET;
if (!signature || !verifyWebhookSignature(req.body.toString(), signature, secret)) {
return res.status(401).send('Ungültige Signatur');
}
const event = JSON.parse(req.body.toString());
const eventType = req.headers['x-frihet-event'];
console.log(`Ereignis empfangen: ${eventType}`, event.data);
// Ereignis entsprechend seinem Typ verarbeiten
switch (eventType) {
case 'invoice.paid':
// Ihr Buchhaltungssystem aktualisieren
break;
case 'expense.created':
// Das Finanzteam benachrichtigen
break;
}
res.status(200).send('OK');
});
Beispiel in Python
import hmac
import hashlib
from flask import Flask, request, abort
app = Flask(__name__)
WEBHOOK_SECRET = 'tu-secreto-aqui'
def verify_signature(payload: bytes, signature: str, secret: str) -> bool:
expected = hmac.new(
secret.encode('utf-8'),
payload,
hashlib.sha256
).hexdigest()
received = signature.replace('sha256=', '')
return hmac.compare_digest(expected, received)
@app.route('/webhook/frihet', methods=['POST'])
def handle_webhook():
signature = request.headers.get('X-Frihet-Signature', '')
if not verify_signature(request.data, signature, WEBHOOK_SECRET):
abort(401)
event = request.get_json()
event_type = request.headers.get('X-Frihet-Event')
print(f'Ereignis empfangen: {event_type}')
if event_type == 'invoice.paid':
# Ihr Buchhaltungssystem aktualisieren
pass
elif event_type == 'quote.accepted':
# Angebot in Rechnung umwandeln
pass
return 'OK', 200
Wichtig: Verwenden Sie immer einen Vergleich mit konstanter Zeit (timingSafeEqual in Node.js, compare_digest in Python), um Timing-Angriffe zu vermeiden.
Wiederholungsrichtlinie
Wenn die Zustellung eines Webhooks fehlschlägt (Nicht-2xx-Antwortcode oder Timeout), versucht Frihet automatisch mit exponentiellem Backoff erneut:
| Versuch | Verzögerung | Kumulierte Zeit |
|---|---|---|
| 1 (initial) | sofort | 0s |
| 2 (erster Wiederholungsversuch) | 2 Sekunden | 2s |
| 3 (zweiter Wiederholungsversuch) | 4 Sekunden | 6s |
- Maximal 3 Versuche pro Zustellung (1 initial + 2 Wiederholungsversuche)
- Maximale Verzögerung von 30 Sekunden zwischen Wiederholungsversuchen
- Timeout von 30 Sekunden pro Anfrage
- Wenn alle 3 Versuche fehlschlagen, wird die Zustellung als
failedmarkiert
Die Wiederholungsversuche werden über einen Job verarbeitet, der alle 5 Minuten ausgeführt wird, wodurch die Zuverlässigkeit auch dann gewährleistet ist, wenn der Hauptprozess neu gestartet wird.
Sie können den Zustellungsverlauf jedes Webhooks im Frihet-Dashboard einsehen, einschließlich des Antwortcodes, des Antwort-Bodies (erste 1000 Zeichen) und der Fehler jedes Versuchs.
Tests
Im Frihet-Dashboard können Sie ein Test-Ereignis an jeden konfigurierten Webhook senden. Die Test-Payload hat die folgende Struktur:
{
"event": "webhook.test",
"timestamp": "2026-02-12T14:30:00.000Z",
"data": {
"message": "This is a test webhook from Frihet ERP"
}
}
Dies ermöglicht die Überprüfung, ob Ihr Endpoint erreichbar ist, die Signatur korrekt validiert wird und Ihr System die Ereignisse fehlerfrei verarbeitet.
Best Practices
Schnell antworten
Ihr Endpoint sollte so schnell wie möglich mit einem 200-Code antworten. Wenn Sie eine aufwändige Verarbeitung durchführen müssen (E-Mails senden, externe Datenbanken aktualisieren usw.), akzeptieren Sie das Ereignis und verarbeiten Sie es asynchron in einer Arbeitswarteschlange.
Idempotenz verwalten
Es ist möglich, dass dasselbe Ereignis mehr als einmal zugestellt wird (z. B. wenn Ihr Server mit einem Timeout geantwortet, das Ereignis aber verarbeitet hat). Verwenden Sie das Feld X-Frihet-Delivery-Id als Idempotenzschlüssel, um Duplikate zu vermeiden.
Signatur immer prüfen
Vertrauen Sie niemals einem Webhook ohne Überprüfung des X-Frihet-Signature-Headers. Jeder Akteur mit Zugriff auf Ihre URL könnte gefälschte Payloads senden.
HTTPS verwenden
In der Produktion muss Ihr Endpoint mit HTTPS geschützt sein. Frihet lehnt HTTP-URLs ab (außer in der lokalen Entwicklung).
Fehler überwachen
Überprüfen Sie regelmäßig die Zustellungsprotokolle im Frihet-Dashboard. Wenn Sie wiederholt fehlgeschlagene Zustellungen sehen, überprüfen Sie, ob Ihr Endpoint verfügbar ist und innerhalb von 30 Sekunden antwortet.
Ereignisse filtern
Abonnieren Sie nur die Ereignisse, die Sie benötigen. Jeder Webhook kann einen oder mehrere Ereignistypen abhören. Je weniger unnötige Ereignisse Sie verarbeiten, desto geringer ist die Last auf Ihrem Server.
Zustellungs-Debugging
Frihet protokolliert das Ergebnis jeder Webhook-Zustellung. Sie können es über das Dashboard einsehen:
- Gehen Sie zu Einstellungen > Entwickler > Webhooks.
- Klicken Sie auf den Webhook, den Sie überprüfen möchten.
- Öffnen Sie den Tab Zustellungen.
Jeder Eintrag zeigt:
- HTTP-Antwortcode, der von Ihrem Endpoint zurückgegeben wurde
- Antwortzeit in Millisekunden
- Antwort-Body (erste 1000 Zeichen)
- Datum und Uhrzeit jedes Versuchs (einschließlich Wiederholungsversuche)
- Endgültiger Status:
delivered,retryingoderfailed
Häufige Probleme
| Symptom | Wahrscheinliche Ursache | Lösung |
|---|---|---|
| Alle Zustellungen schlagen mit Timeout fehl | Endpoint braucht länger als 30s, um zu antworten | Akzeptieren Sie das Ereignis sofort mit einem 200 und verarbeiten Sie es asynchron |
| SSL/TLS-Fehler | Zertifikat abgelaufen oder Kette unvollständig | Erneuern Sie das Zertifikat und überprüfen Sie die Kette mit openssl s_client |
| Systematischer 403-Code | Firewall blockiert eingehende POST-Anfragen | Erlauben Sie Traffic von Google Cloud IPs (us-central1) |
| 502/503-Code | Server ausgefallen oder in Wartung | Überprüfen Sie die Protokolle Ihres Servers und stellen Sie sicher, dass der Prozess aktiv ist |
Test-Button
Auf dem Detailbildschirm jedes Webhooks sendet der Button Test senden ein synthetisches webhook.test-Ereignis an Ihre URL. Verwenden Sie diesen Button, um zu überprüfen:
- Ob die URL aus dem Internet erreichbar ist
- Ob die HMAC-Signatur korrekt validiert wird
- Ob Ihr Server mit einem
2xx-Code antwortet
Das Testergebnis erscheint sofort im Zustellungsprotokoll.
Fehlerbehebung
Ich erhalte keine Webhooks
- Überprüfen Sie, ob die Webhook-URL korrekt und aus dem Internet erreichbar ist.
- Stellen Sie sicher, dass der Webhook im Dashboard auf aktiv steht.
- Überprüfen Sie die Zustellungsprotokolle auf Fehler.
- Wenn Sie eine Firewall verwenden, stellen Sie sicher, dass eingehende POST-Anfragen von Google Cloud (
us-central1) zugelassen sind.
Signaturen stimmen nicht überein
- Stellen Sie sicher, dass Sie den Roh-Body der Anfrage verwenden (bevor Sie das JSON parsen).
- Bestätigen Sie, dass das Geheimnis in Ihrem Code mit dem im Frihet-Dashboard konfigurierten übereinstimmt.
- Ändern oder formatieren Sie den Body nicht, bevor Sie die Signatur überprüfen.
- Überprüfen Sie, ob Ihr Framework den Body nicht automatisch parst, bevor Sie auf den Roh-Body zugreifen können.
Wiederholungsversuche kommen nicht an
- Wiederholungsversuche werden alle 5 Minuten verarbeitet. Wenn Ihr Endpoint kurzzeitig ausgefallen war, sind die Versuche möglicherweise bereits erschöpft.
- Überprüfen Sie den Zustellungsverlauf, um den Status jedes Versuchs zu bestätigen.
- Wenn alle 3 Versuche fehlgeschlagen sind, wird die Zustellung nicht automatisch erneut versucht.
Payload zu groß
Wenn die dem Ereignis zugeordnete Ressource sehr groß ist (viele Zeilen in einer Rechnung, umfangreiche Felder), kann die Payload das Limit von 100 KB überschreiten und die Zustellung wird abgelehnt. Vereinfachen Sie die Ressourcendaten oder kontaktieren Sie den Support, wenn Sie ein höheres Limit benötigen.