Webhooks & Events
Einleitung
Hallo, ich bin Sophie! 👋 Mit Webhooks wirst Sie in Echtzeit über wichtige Ereignisse in BuchhaltGenie informiert. Anstatt ständig unsere API abzufragen, senden wir Ihnen automatisch Benachrichtigungen, sobald etwas Relevantes passiert - zum Beispiel wenn eine Rechnung bezahlt wurde oder ein neuer Kunde angelegt wurde.
Das ist nicht nur effizienter, sondern ermöglicht Ihnen auch, sofort auf Änderungen zu reagieren!
Sicherheitshinweis: Alle Webhooks werden mit HMAC-SHA256 signiert. Validiere IMMER die Signatur, bevor Sie einen Webhook verarbeitest. Das schützt Sie vor gefälschten Anfragen.
Wie funktionieren Webhooks?
Webhooks sind HTTP-POST-Anfragen, die BuchhaltGenie an Ihren Server sendet, wenn bestimmte Events eintreten:
Verfügbare Events
BuchhaltGenie sendet Webhooks für folgende Event-Kategorien:
Rechnungen
| Event | Beschreibung | Wann ausgelöst |
|---|---|---|
invoice.created | Neue Rechnung erstellt | Bei Erstellung einer Rechnung |
invoice.sent | Rechnung versendet | Wenn E-Mail an Kunden geht |
invoice.paid | Rechnung bezahlt | Bei Zahlungseingang |
invoice.overdue | Rechnung überfällig | Wenn Fälligkeit überschritten |
invoice.cancelled | Rechnung storniert | Bei Stornierung |
Kunden
| Event | Beschreibung | Wann ausgelöst |
|---|---|---|
customer.created | Neuer Kunde angelegt | Bei Kundenanlage |
customer.updated | Kundendaten geändert | Bei Änderung der Stammdaten |
customer.deleted | Kunde gelöscht | Bei Löschung (Soft-Delete) |
Zahlungen
| Event | Beschreibung | Wann ausgelöst |
|---|---|---|
payment.received | Zahlung eingegangen | Bei Zahlungseingang |
payment.failed | Zahlung fehlgeschlagen | Bei fehlgeschlagener Zahlung |
payment.refunded | Zahlung rückerstattet | Bei Rückerstattung |
Abonnements
| Event | Beschreibung | Wann ausgelöst |
|---|---|---|
subscription.created | Neues Abonnement | Bei Abo-Abschluss |
subscription.updated | Abo geändert | Bei Upgrade/Downgrade |
subscription.cancelled | Abo gekündigt | Bei Kündigung |
subscription.renewed | Abo verlängert | Bei automatischer Verlängerung |
FinanzOnline (Österreich-spezifisch)
| Event | Beschreibung | Wann ausgelöst |
|---|---|---|
uva.submitted | UVA eingereicht | Bei UVA-Übermittlung |
uva.confirmed | UVA bestätigt | Bei Finanzamt-Bestätigung |
uva.rejected | UVA abgelehnt | Bei Ablehnung durch Finanzamt |
Sophie’s Tipp: Sie können mehrere Webhooks für unterschiedliche Event-Typen einrichten. So kannst Sie zum Beispiel Zahlungs-Events an Ihr Buchhaltungssystem und Kunden-Events an Ihr CRM senden.
Webhook einrichten
Endpoint erstellen
Erstelle einen HTTP-Endpoint auf Ihrem Server, der POST-Anfragen empfangen kann:
// Beispiel: Next.js API Route
export async function POST(request: Request) {
const body = await request.text();
const signature = request.headers.get('x-bg-signature');
// Signatur validieren (siehe nächster Abschnitt)
if (!verifySignature(body, signature)) {
return new Response('Ungültige Signatur', { status: 401 });
}
const event = JSON.parse(body);
// Event verarbeiten
switch (event.type) {
case 'invoice.paid':
await handleInvoicePaid(event.data);
break;
// ... weitere Events
}
return new Response('OK', { status: 200 });
}Webhook in BuchhaltGenie registrieren
Navigiere zu Einstellungen > API > Webhooks und klicken Sie auf “Webhook hinzufügen”:
- Gib Ihre Endpoint-URL ein (muss HTTPS sein)
- Wählen Sie die Events aus, die Sie empfangen möchtest
- Optional: Gib einen benutzerdefinierten Header hinzu
- Klicken Sie auf “Speichern”
Webhook-Secret sichern
Nach dem Speichern erhältst Sie ein Webhook-Secret im Format whsec_xxxx. Speichern Sie dieses sicher als Umgebungsvariable:
# .env
BUCHHALTGENIE_WEBHOOK_SECRET=whsec_xxxxxxxxxxxxxxxxxxxxxxxxEndpoint testen
Klicken Sie auf “Test senden”, um einen Test-Webhook zu versenden und Ihre Integration zu prüfen.
Signatur-Validierung
Kritisch: Validiere IMMER die Webhook-Signatur! Ohne Validierung könnte ein Angreifer gefälschte Webhooks an Ihren Server senden und Ihr System manipulieren.
So funktioniert die Signatur
BuchhaltGenie signiert jeden Webhook mit HMAC-SHA256:
- Timestamp + Payload werden kombiniert
- HMAC-SHA256 wird mit Ihrem Webhook-Secret berechnet
- Signatur wird im Header
x-bg-signaturemitgesendet - Timestamp wird im Header
x-bg-timestampmitgesendet
Validierung implementieren
TypeScript
import crypto from 'crypto';
interface VerificationResult {
valid: boolean;
error?: string;
}
function verifyWebhookSignature(
payload: string,
signature: string | null,
timestamp: string | null,
secret: string
): VerificationResult {
// Signatur und Timestamp prüfen
if (!signature || !timestamp) {
return { valid: false, error: 'Fehlende Signatur oder Timestamp' };
}
// Timestamp-Freshness prüfen (max 5 Minuten)
const timestampMs = parseInt(timestamp, 10) * 1000;
const now = Date.now();
const fiveMinutes = 5 * 60 * 1000;
if (Math.abs(now - timestampMs) > fiveMinutes) {
return { valid: false, error: 'Timestamp zu alt (Replay-Angriff?)' };
}
// Erwartete Signatur berechnen
const signedPayload = `${timestamp}.${payload}`;
const expectedSignature = crypto
.createHmac('sha256', secret)
.update(signedPayload)
.digest('hex');
// Timing-sicherer Vergleich
const isValid = crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
return { valid: isValid };
}
// Verwendung in Ihrem Webhook-Handler
export async function POST(request: Request) {
const payload = await request.text();
const signature = request.headers.get('x-bg-signature');
const timestamp = request.headers.get('x-bg-timestamp');
const verification = verifyWebhookSignature(
payload,
signature,
timestamp,
process.env.BUCHHALTGENIE_WEBHOOK_SECRET!
);
if (!verification.valid) {
console.error('Webhook-Validierung fehlgeschlagen:', verification.error);
return new Response('Ungültige Signatur', { status: 401 });
}
// Webhook verarbeiten...
const event = JSON.parse(payload);
// ...
}Sicherheits-Checkliste
| Prüfung | Warum wichtig |
|---|---|
| Signatur validieren | Verhindert gefälschte Webhooks |
| Timestamp prüfen | Schützt vor Replay-Angriffen |
| HTTPS verwenden | Verschlüsselt Daten während Übertragung |
| Timing-sicherer Vergleich | Verhindert Timing-Angriffe |
| Secret sicher speichern | Niemals im Code oder Repository |
Webhook-Payload-Format
Jeder Webhook enthält eine standardisierte Payload-Struktur:
{
"id": "evt_1234567890abcdef",
"type": "invoice.paid",
"created": "2026-01-15T14:30:00Z",
"api_version": "2026-01",
"data": {
"object": {
"id": "inv_abc123",
"customer_id": "cus_xyz789",
"amount": 11900,
"currency": "EUR",
"status": "paid",
"paid_at": "2026-01-15T14:30:00Z"
},
"previous_attributes": {
"status": "sent"
}
},
"business_id": "bus_123456",
"correlation_id": "corr_xxxxxxxx"
}Payload-Felder erklärt
| Feld | Typ | Beschreibung |
|---|---|---|
id | string | Eindeutige Event-ID (für Idempotenz) |
type | string | Event-Typ (z.B. invoice.paid) |
created | ISO 8601 | Zeitpunkt des Events |
api_version | string | API-Version |
data.object | object | Das betroffene Objekt |
data.previous_attributes | object | Vorherige Werte (bei Updates) |
business_id | string | Deine Business-ID |
correlation_id | string | ID zur Nachverfolgung |
Sophie’s Tipp: Speichern Sie die id jedes verarbeiteten Webhooks in Ihrer Datenbank. So kannst Sie Duplikate erkennen und Webhooks idempotent verarbeiten - falls der gleiche Webhook mehrfach zugestellt wird.
Retry-Logik
BuchhaltGenie versucht, fehlgeschlagene Webhooks automatisch erneut zuzustellen:
Retry-Verhalten
| Versuch | Wartezeit | Zeitpunkt nach erstem Versuch |
|---|---|---|
| 1 | Sofort | 0 Minuten |
| 2 | 1 Minute | 1 Minute |
| 3 | 5 Minuten | 6 Minuten |
| 4 | 30 Minuten | 36 Minuten |
| 5 | 2 Stunden | 2,6 Stunden |
| 6 | 8 Stunden | 10,6 Stunden |
| 7 | 24 Stunden | 34,6 Stunden |
Nach 7 fehlgeschlagenen Versuchen wird der Webhook als endgültig fehlgeschlagen markiert.
Was gilt als “erfolgreich”?
Ein Webhook gilt als erfolgreich zugestellt, wenn Ihr Endpoint mit einem HTTP-Statuscode 2xx (200-299) antwortet. Alle anderen Statuscodes lösen einen Retry aus.
// ✅ Erfolgreiche Antworten
return new Response('OK', { status: 200 });
return new Response(JSON.stringify({ received: true }), { status: 200 });
// ❌ Lösen Retry aus
return new Response('Error', { status: 500 });
return new Response('Not Found', { status: 404 });
// Timeout (keine Antwort innerhalb 30 Sekunden)Best Practices für Ihre Webhook-Handler
Performance-Tipp: Antworte IMMER schnell (unter 30 Sekunden)! Führe zeitaufwändige Verarbeitung asynchron durch.
// ✅ RICHTIG: Schnelle Antwort, asynchrone Verarbeitung
export async function POST(request: Request) {
const event = await parseAndValidate(request);
// Event in Queue einreihen (schnell)
await queue.add('process-webhook', event);
// Sofort antworten
return new Response('OK', { status: 200 });
}
// ❌ FALSCH: Lange Verarbeitung vor Antwort
export async function POST(request: Request) {
const event = await parseAndValidate(request);
// Zeitaufwändige Verarbeitung (kann Timeout verursachen!)
await sendEmailNotification(event);
await updateDatabase(event);
await syncWithExternalSystem(event);
return new Response('OK', { status: 200 });
}Idempotenz
Webhooks können mehrfach zugestellt werden (bei Retries oder Netzwerkproblemen). Ihre Verarbeitung sollte daher idempotent sein:
async function handleInvoicePaid(event: WebhookEvent) {
const eventId = event.id;
const invoiceId = event.data.object.id;
// Prüfen ob Event bereits verarbeitet wurde
const existingEvent = await db.webhookEvents.findUnique({
where: { event_id: eventId }
});
if (existingEvent) {
console.log(`Event ${eventId} bereits verarbeitet, überspringe.`);
return { success: true, skipped: true };
}
// Event als "in Verarbeitung" markieren
await db.webhookEvents.create({
data: {
event_id: eventId,
event_type: event.type,
status: 'processing',
received_at: new Date()
}
});
try {
// Eigentliche Verarbeitung
await markInvoiceAsPaid(invoiceId);
// Erfolgreich abgeschlossen
await db.webhookEvents.update({
where: { event_id: eventId },
data: { status: 'processed', processed_at: new Date() }
});
return { success: true };
} catch (error) {
// Fehler protokollieren
await db.webhookEvents.update({
where: { event_id: eventId },
data: { status: 'failed', error: error.message }
});
throw error;
}
}Webhook-Logs einsehen
In BuchhaltGenie kannst Sie alle Webhook-Zustellungen einsehen:
- Navigiere zu Einstellungen > API > Webhooks
- Klicken Sie auf den gewünschten Webhook
- Wählen Sie den Tab “Zustellungsprotokoll”
Für jeden Zustellversuch siehst du:
- Zeitpunkt der Zustellung
- HTTP-Statuscode der Antwort
- Antwortzeit in Millisekunden
- Request-Payload (zum Debuggen)
- Response-Body (falls vorhanden)
Häufige Probleme und Lösungen
Troubleshooting-Guide
Webhook kommt nicht an
- URL prüfen: Ist die URL korrekt und von außen erreichbar?
- HTTPS: BuchhaltGenie sendet nur an HTTPS-Endpoints
- Firewall: Erlaubt Ihre Firewall eingehende Verbindungen?
- Logs prüfen: Schau in das Zustellungsprotokoll
Signatur-Validierung schlägt fehl
- Raw Body: Verwende den unveränderten Request-Body
- Encoding: Body muss UTF-8-kodiert sein
- Secret: Prüfe, ob das richtige Secret verwendet wird
- Timestamp: Ist die Serverzeit korrekt synchronisiert?
Webhooks kommen doppelt an
- Idempotenz: Implementiere Duplikat-Erkennung
- Event-ID: Speichern Sie verarbeitete Event-IDs
- Timeout: Antworte schneller als 30 Sekunden
Webhook-Handler ist zu langsam
- Async Processing: Verarbeite asynchron
- Queue: Verwende eine Message Queue
- Acknowledge First: Antworte sofort mit 200
Compliance und Datenschutz
DSGVO & BAO Compliance
Datenschutz (DSGVO Art. 32)
- Webhooks übertragen personenbezogene Daten (Kundennamen, Adressen)
- Verwende HTTPS für verschlüsselte Übertragung
- Speichern Sie Webhook-Daten gemäß Ihrer Datenschutzerklärung
- Lösche nicht mehr benötigte Daten
Aufbewahrung (BAO 132)
- Steuerrelevante Webhook-Daten (Rechnungen, Zahlungen) müssen 7 Jahre aufbewahrt werden
- BuchhaltGenie protokolliert alle Webhooks intern
- Führe eigene Protokolle für Ihre Verarbeitung
Audit-Trail
Alle Webhook-Zustellungen werden mit folgenden Daten protokolliert:
- Zeitpunkt der Zustellung
- Event-Typ und -ID
- HTTP-Statuscode der Antwort
- IP-Adresse des Empfängers
- Correlation-ID für Nachverfolgung
API-Referenz: Webhook-Management
Webhooks auflisten
GET /api/v1/webhooks{
"data": [
{
"id": "wh_abc123",
"url": "https://example.com/webhook",
"events": ["invoice.paid", "invoice.created"],
"status": "active",
"created_at": "2026-01-15T10:00:00Z"
}
]
}Webhook erstellen
POST /api/v1/webhooks
Content-Type: application/json
{
"url": "https://example.com/webhook",
"events": ["invoice.paid", "customer.created"]
}Webhook aktualisieren
PATCH /api/v1/webhooks/{id}
Content-Type: application/json
{
"events": ["invoice.paid", "invoice.created", "customer.created"]
}Webhook löschen
DELETE /api/v1/webhooks/{id}Secret rotieren
POST /api/v1/webhooks/{id}/rotate-secretWeiterführende Ressourcen
Best Practices für sichere API-Integrationen
API-SicherheitAPI-Schlüssel und OAuth 2.0
AuthentifizierungLimits und Throttling verstehen
Rate LimitsVollständige Endpunkt-Dokumentation
API-EndpunkteSupport
Bei Fragen zu Webhooks hilft Ihnen unser Entwickler-Support:
- E-Mail: office@buchhaltgenie.at
- Status-Seite: status.buchhaltgenie.at
- Webhook-Logs: Einstellungen > API > Webhooks > Zustellungsprotokoll
Hinweis: Diese Dokumentation dient als technische Referenz. Bei komplexen Integrationsanforderungen empfehlen wir, einen erfahrenen Entwickler hinzuzuziehen. Für steuerrechtliche Fragen wende Sie bitte an Ihren Steuerberater.