Zum Hauptinhalt springen

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

  1. Gehen Sie in Ihrem Frihet-Konto zu Einstellungen > Webhooks.
  2. Klicken Sie auf Webhook erstellen.
  3. Geben Sie einen aussagekräftigen Namen, die Ziel-URL ein und wählen Sie die Ereignisse aus, die Sie empfangen möchten.
  4. Optional definieren Sie ein Geheimnis für die HMAC-Verifizierung (empfohlen).
  5. 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
FeldTypErforderlichBeschreibung
urlstringJaZiel-URL (HTTPS in Produktion obligatorisch)
eventsstring[]JaListe der zu empfangenden Ereignistypen
secretstringNeinGeheimnis für HMAC-SHA256-Signatur (empfohlen)
activebooleanNeinWebhook-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 localhost und 127.0.0.1 wä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)

EreignisBeschreibung
invoice.createdEine neue Rechnung wurde erstellt
invoice.updatedEine bestehende Rechnung wurde geändert
invoice.paidEine Rechnung wurde als bezahlt markiert
invoice.overdueEine Rechnung hat ihr Fälligkeitsdatum überschritten

Ausgaben (2 Ereignisse)

EreignisBeschreibung
expense.createdEine neue Ausgabe wurde erfasst
expense.updatedEine bestehende Ausgabe wurde geändert

Angebote (4 Ereignisse)

EreignisBeschreibung
quote.createdEin neues Angebot wurde erstellt
quote.updatedEin bestehendes Angebot wurde geändert
quote.acceptedEin Kunde hat ein Angebot angenommen (Statusänderung zu accepted)
quote.rejectedEin Kunde hat ein Angebot abgelehnt (Statusänderung zu rejected)

Kunden (2 Ereignisse)

EreignisBeschreibung
client.createdEin neuer Kunde wurde registriert
client.updatedDie Kundendaten wurden geändert

Produkte (2 Ereignisse)

EreignisBeschreibung
product.createdEin neues Produkt oder eine Dienstleistung wurde erstellt
product.updatedEin 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:

HeaderBeschreibungBeispiel
Content-TypeInhaltstypapplication/json
X-Frihet-EventEreignistypinvoice.paid
X-Frihet-Delivery-IdEindeutiger Zustellungs-Identifikatord4e5f6a7b8c9
X-Frihet-TimestampISO 8601 Zeitstempel2026-02-12T14:30:00.000Z
X-Frihet-SignatureHMAC-SHA256-Signatur der Payloadsha256=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:

  1. Den Roh-Body der Anfrage nehmen
  2. Den HMAC-SHA256 unter Verwendung des Geheimnisses als Schlüssel berechnen
  3. Das Ergebnis mit dem Wert des X-Frihet-Signature-Headers vergleichen (ohne das Präfix sha256=)

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:

VersuchVerzögerungKumulierte Zeit
1 (initial)sofort0s
2 (erster Wiederholungsversuch)2 Sekunden2s
3 (zweiter Wiederholungsversuch)4 Sekunden6s
  • 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 failed markiert

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:

  1. Gehen Sie zu Einstellungen > Entwickler > Webhooks.
  2. Klicken Sie auf den Webhook, den Sie überprüfen möchten.
  3. Ö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, retrying oder failed

Häufige Probleme

SymptomWahrscheinliche UrsacheLösung
Alle Zustellungen schlagen mit Timeout fehlEndpoint braucht länger als 30s, um zu antwortenAkzeptieren Sie das Ereignis sofort mit einem 200 und verarbeiten Sie es asynchron
SSL/TLS-FehlerZertifikat abgelaufen oder Kette unvollständigErneuern Sie das Zertifikat und überprüfen Sie die Kette mit openssl s_client
Systematischer 403-CodeFirewall blockiert eingehende POST-AnfragenErlauben Sie Traffic von Google Cloud IPs (us-central1)
502/503-CodeServer 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

  1. Überprüfen Sie, ob die Webhook-URL korrekt und aus dem Internet erreichbar ist.
  2. Stellen Sie sicher, dass der Webhook im Dashboard auf aktiv steht.
  3. Überprüfen Sie die Zustellungsprotokolle auf Fehler.
  4. Wenn Sie eine Firewall verwenden, stellen Sie sicher, dass eingehende POST-Anfragen von Google Cloud (us-central1) zugelassen sind.

Signaturen stimmen nicht überein

  1. Stellen Sie sicher, dass Sie den Roh-Body der Anfrage verwenden (bevor Sie das JSON parsen).
  2. Bestätigen Sie, dass das Geheimnis in Ihrem Code mit dem im Frihet-Dashboard konfigurierten übereinstimmt.
  3. Ändern oder formatieren Sie den Body nicht, bevor Sie die Signatur überprüfen.
  4. Überprüfen Sie, ob Ihr Framework den Body nicht automatisch parst, bevor Sie auf den Roh-Body zugreifen können.

Wiederholungsversuche kommen nicht an

  1. Wiederholungsversuche werden alle 5 Minuten verarbeitet. Wenn Ihr Endpoint kurzzeitig ausgefallen war, sind die Versuche möglicherweise bereits erschöpft.
  2. Überprüfen Sie den Zustellungsverlauf, um den Status jedes Versuchs zu bestätigen.
  3. 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.