Webhooks

Echtzeit-Benachrichtigungen über Batch-Statusänderungen

Übersicht

Webhooks ermöglichen es Ihrer Anwendung, automatisch über Ereignisse in der SteuerMappePro API informiert zu werden. Anstatt kontinuierlich den Status eines Batches abzufragen (Polling), sendet die API eine HTTP POST-Anfrage an einen von Ihnen konfigurierten Endpunkt, sobald ein Ereignis eintritt.

Event-Driven

Sofortige Benachrichtigung bei Statusänderungen

Effizient

Kein Polling notwendig, spart API-Anfragen

Sicher

HMAC-Signatur zur Verifizierung der Absender

Zuverlässig

Automatische Wiederholung bei Fehlern

Vorteile von Webhooks

  • Echtzeit-Updates: Erhalten Sie sofort Benachrichtigungen, wenn ein Batch abgeschlossen ist
  • Reduzierter API-Traffic: Keine kontinuierliche Statusabfrage notwendig
  • Automatisierung: Triggern Sie nachfolgende Prozesse automatisch
  • Skalierbarkeit: Verarbeiten Sie viele parallele Batches effizient

Ereignistypen

Die SteuerMappePro API sendet Webhook-Benachrichtigungen für folgende Ereignisse:

EreignisBeschreibungWann wird es ausgelöst?
batch.queuedBatch wurde erstelltDirekt nach erfolgreicher Batch-Erstellung
batch.processingVerarbeitung gestartetWenn die Dokumentenverarbeitung beginnt
batch.completedVerarbeitung erfolgreichWenn alle Dokumente erfolgreich verarbeitet wurden
batch.failedVerarbeitung fehlgeschlagenBei einem kritischen Fehler während der Verarbeitung

Webhook-Payload Format

Alle Webhook-Benachrichtigungen werden als HTTP POST-Anfragen mit einem JSON-Body gesendet:

Beispiel: batch.completed Ereignis

{
  "id": "evt_001",
  "type": "batch.completed",
  "created_at": "2025-10-06T10:10:00Z",

  "batch": {
    "id": "b_abc123",
    "status": "completed",
    "project_id": "prj_123",
    "mandant_id": "m_123"
  },

  "result": {
    "download_url": "https://files.partner.example/presigned/result.zip",
    "file_size_bytes": 5242880,
    "sha256": "a3b5c7d9e1f2..."
  },

  "usage": {
    "pages": 38,
    "documents": 12,
    "total_duration_seconds": 245
  },

  "billing": {
    "partner_code": "ABC123",
    "snapshot_id": "pcs_789",
    "total_cost_eur": "17.34"
  }
}

Beispiel: batch.failed Ereignis

{
  "id": "evt_002",
  "type": "batch.failed",
  "created_at": "2025-10-06T10:15:00Z",

  "batch": {
    "id": "b_abc456",
    "status": "failed",
    "project_id": "prj_123",
    "mandant_id": "m_456"
  },

  "error": {
    "code": "CONVERSION_FAILED",
    "message": "Dokument konnte nicht konvertiert werden",
    "details": "Unsupported file format: .xyz"
  }
}

Payload-Felder

FeldTypBeschreibung
idstringEindeutige Event-ID (evt_...)
typestringEreignistyp (z.B. "batch.completed")
created_atISO 8601Zeitstempel des Ereignisses
batchobjectBatch-Informationen (ID, Status, etc.)
resultobject?Verarbeitungsergebnis (nur bei batch.completed)
errorobject?Fehlerinformationen (nur bei batch.failed)

HMAC-Signaturverifizierung

Jede Webhook-Anfrage enthält eine HMAC-SHA256-Signatur im Header X-SteuerMappePro-Signature. Verifizieren Sie diese Signatur, um sicherzustellen, dass die Anfrage tatsächlich von SteuerMappePro stammt.

Sicherheitshinweis

Verifizieren Sie immer die HMAC-Signatur, bevor Sie Webhook-Daten verarbeiten. Dies schützt vor Man-in-the-Middle-Angriffen und gefälschten Anfragen.

Header-Format

X-SteuerMappePro-Signature: sha256=a3b5c7d9e1f2...

Signatur-Verifizierung (TypeScript/Node.js)

import crypto from 'crypto';

export async function verifyWebhookSignature(
  request: Request,
  webhookSecret: string
): Promise<boolean> {
  // 1. Signatur aus Header extrahieren
  const signature = request.headers.get('X-SteuerMappePro-Signature');
  if (!signature) {
    throw new Error('Fehlende Webhook-Signatur');
  }

  // 2. Request Body lesen
  const body = await request.text();

  // 3. Erwartete Signatur berechnen
  const expectedSignature = `sha256=${crypto
    .createHmac('sha256', webhookSecret)
    .update(body)
    .digest('hex')}`;

  // 4. Signaturen vergleichen (timing-safe)
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature)
  );
}

Webhook-Secret

Der webhookSecret wird bei der Webhook-Registrierung generiert. Sie können ihn entweder selbst beim Erstellen eines Batches mitgeben (webhook_secret Feld) oder von der API generieren lassen.

Wiederholungsrichtlinie

Falls Ihr Webhook-Endpunkt nicht erreichbar ist oder einen Fehler zurückgibt (HTTP Status > 299), versucht die SteuerMappePro API die Zustellung automatisch erneut.

Exponential Backoff-Strategie

VersuchWartezeitGesamtzeit
1. VersuchSofort-
2. Versuch1 Minute+1 min
3. Versuch5 Minuten+6 min
4. Versuch30 Minuten+36 min
5. Versuch (Final)2 Stunden+2h 36 min

Maximale Versuche

Nach 5 fehlgeschlagenen Versuchen wird der Webhook als "failed" markiert und keine weiteren Zustellversuche unternommen. Sie können fehlgeschlagene Webhooks in der Admin-Konsole einsehen.

Erfolgreiche Webhook-Zustellung

Ihr Endpunkt sollte mit einem HTTP-Statuscode 200-299 antworten, um eine erfolgreiche Zustellung zu signalisieren. Der Response-Body wird ignoriert.

Sicherheits-Best-Practices

Signatur-Verifizierung

  • Immer die HMAC-Signatur verifizieren
  • ✓ Timing-safe Vergleich verwenden (z.B. crypto.timingSafeEqual)
  • ✓ Unbekannte/ungültige Anfragen mit 401 ablehnen

HTTPS verwenden

  • ✓ Webhook-Endpunkt muss über HTTPS erreichbar sein
  • ✓ Gültiges TLS-Zertifikat verwenden (Let's Encrypt empfohlen)
  • ✓ TLS 1.2 oder höher

Idempotenz

  • ✓ Webhooks können mehrfach zugestellt werden (bei Retries)
  • ✓ Verwenden Sie die Event-ID für Duplikatserkennung
  • ✓ Implementieren Sie idempotente Verarbeitung

Schnelle Antwortzeiten

  • ✓ Webhook-Endpunkt sollte innerhalb von 10 Sekunden antworten
  • ✓ Zeitintensive Verarbeitung asynchron durchführen (z.B. Job Queue)
  • ✓ Sofort mit 200 OK antworten, dann verarbeiten

Code-Beispiele

Kompletter Webhook-Endpunkt (TypeScript/Node.js)

import { NextRequest, NextResponse } from 'next/server';
import crypto from 'crypto';

// Webhook-Secret (aus Umgebungsvariable)
const WEBHOOK_SECRET = process.env.STEUERMAPPE_WEBHOOK_SECRET!;

// Signatur verifizieren
function verifySignature(body: string, signature: string): boolean {
  const expectedSignature = `sha256=${crypto
    .createHmac('sha256', WEBHOOK_SECRET)
    .update(body)
    .digest('hex')}`;

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature)
  );
}

// Webhook-Handler
export async function POST(request: NextRequest) {
  try {
    // 1. Signatur aus Header extrahieren
    const signature = request.headers.get('X-SteuerMappePro-Signature');
    if (!signature) {
      return NextResponse.json(
        { error: 'Missing signature' },
        { status: 401 }
      );
    }

    // 2. Request Body lesen
    const body = await request.text();

    // 3. Signatur verifizieren
    if (!verifySignature(body, signature)) {
      return NextResponse.json(
        { error: 'Invalid signature' },
        { status: 401 }
      );
    }

    // 4. Payload parsen
    const event = JSON.parse(body);

    // 5. Event-Typ prüfen und verarbeiten
    switch (event.type) {
      case 'batch.completed':
        await handleBatchCompleted(event);
        break;

      case 'batch.failed':
        await handleBatchFailed(event);
        break;

      case 'batch.processing':
        await handleBatchProcessing(event);
        break;

      default:
        console.log(`Unbekannter Event-Typ: ${event.type}`);
    }

    // 6. Erfolgreiche Antwort
    return NextResponse.json({ received: true }, { status: 200 });

  } catch (error) {
    console.error('Webhook-Fehler:', error);
    return NextResponse.json(
      { error: 'Internal server error' },
      { status: 500 }
    );
  }
}

// Event-Handler
async function handleBatchCompleted(event: any) {
  console.log(`Batch ${event.batch.id} abgeschlossen!`);
  console.log(`Download-URL: ${event.result.download_url}`);

  // TODO: Ergebnis herunterladen und weiterverarbeiten
  // z.B. in DATEV importieren, E-Mail versenden, etc.
}

async function handleBatchFailed(event: any) {
  console.error(`Batch ${event.batch.id} fehlgeschlagen!`);
  console.error(`Fehler: ${event.error.message}`);

  // TODO: Fehlerbehandlung, z.B. Admin benachrichtigen
}

async function handleBatchProcessing(event: any) {
  console.log(`Batch ${event.batch.id} wird verarbeitet...`);
  // Optional: UI aktualisieren, Fortschritt anzeigen
}

Idempotente Verarbeitung mit Duplikatserkennung

import { prisma } from '@/lib/prisma';

async function handleBatchCompleted(event: any) {
  // Prüfen ob Event bereits verarbeitet wurde
  const existingEvent = await prisma.webhookEvent.findUnique({
    where: { eventId: event.id }
  });

  if (existingEvent) {
    console.log(`Event ${event.id} bereits verarbeitet, überspringe...`);
    return;
  }

  // Event verarbeiten
  await processResult(event.result.download_url);

  // Event als verarbeitet markieren
  await prisma.webhookEvent.create({
    data: {
      eventId: event.id,
      eventType: event.type,
      batchId: event.batch.id,
      processedAt: new Date()
    }
  });
}

Webhooks lokal testen

Für lokale Entwicklung und Tests gibt es mehrere Möglichkeiten, Webhooks zu empfangen:

Option 1: ngrok

Erstellen Sie einen sicheren Tunnel zu Ihrem lokalen Server:

# ngrok installieren (https://ngrok.com)
# Tunnel zu localhost:3000 erstellen
ngrok http 3000

# Ausgabe:
# Forwarding https://abc123.ngrok.io -> http://localhost:3000

# Webhook-URL in SteuerMappePro konfigurieren:
# https://abc123.ngrok.io/api/webhooks/steuermappe

Option 2: webhook.site

Für schnelle Tests ohne Code können Sie webhook.site verwenden:

  • 1. Besuchen Sie https://webhook.site
  • 2. Kopieren Sie die generierte URL
  • 3. Verwenden Sie diese URL als webhook_url beim Batch-Erstellen
  • 4. Sehen Sie eingehende Webhooks in Echtzeit im Browser

Option 3: Lokaler Test-Server

// test-webhook-server.ts
import express from 'express';

const app = express();
app.use(express.json());

app.post('/webhook', (req, res) => {
  console.log('Webhook empfangen:');
  console.log('Headers:', req.headers);
  console.log('Body:', JSON.stringify(req.body, null, 2));

  res.status(200).json({ received: true });
});

app.listen(3000, () => {
  console.log('Webhook-Test-Server läuft auf http://localhost:3000');
});

// Starten:
// npx ts-node test-webhook-server.ts

ngrok-Empfehlung

Für realistische Tests empfehlen wir ngrok, da es HTTPS-Unterstützung bietet und Sie Ihre eigene Verifizierungslogik testen können.

Webhook registrieren

Sie können Webhooks auf zwei Arten registrieren:

Option 1: Bei Batch-Erstellung

Geben Sie webhook_url und optional webhook_secret beim Erstellen eines Batches an:

{
  "batch_name": "Q4 Steuererklärungen",
  "documents": [...],
  "webhook_url": "https://api.ihr-system.de/webhooks/steuermappe",
  "webhook_secret": "ihr_geheimer_webhook_key"
}

Option 2: Persistente Webhook-Konfiguration

Konfigurieren Sie einen Webhook dauerhaft für Ihren API-Client (erfordert webhooks:manage Scope):

POST /v1/webhooks
Authorization: Bearer {access_token}

{
  "url": "https://api.ihr-system.de/webhooks/steuermappe",
  "events": ["batch.completed", "batch.failed"],
  "secret": "ihr_geheimer_webhook_key"
}

Webhook-Secret generieren

Falls Sie kein webhook_secret angeben, generiert die API automatisch einen sicheren Schlüssel und gibt ihn in der Response zurück.

Fehlerbehebung

Webhook wird nicht zugestellt

  • ✓ Prüfen Sie, ob Ihr Endpunkt über HTTPS erreichbar ist
  • ✓ Stellen Sie sicher, dass Ihr Server innerhalb von 10 Sekunden antwortet
  • ✓ Überprüfen Sie Firewall-Regeln und IP-Whitelisting
  • ✓ Sehen Sie in den Webhook-Logs in der Admin-Konsole nach

Signatur-Verifizierung schlägt fehl

  • ✓ Verwenden Sie den korrekten webhook_secret
  • ✓ Berechnen Sie den HMAC über den rohen Request Body (nicht geparst)
  • ✓ Verwenden Sie SHA-256 Hashing-Algorithmus
  • ✓ Vergleichen Sie mit timing-safe Methode (crypto.timingSafeEqual)

Doppelte Webhook-Zustellungen

  • ✓ Dies ist normales Verhalten bei Netzwerkproblemen oder Timeouts
  • ✓ Implementieren Sie Duplikatserkennung anhand der id
  • ✓ Stellen Sie sicher, dass Ihre Verarbeitung idempotent ist

Support

Bei weiteren Fragen oder Problemen kontaktieren Sie bitte: api@steuermappe-pro.de

Sicherheit

Webhooks werden mit einer HMAC-Signatur versehen, damit Sie die Echtheit der Anfragen verifizieren können.

Signatur-Header

  • X-SMP-Signature: HMAC-SHA256 übertimestamp + '.' + rawBody
  • X-SMP-Timestamp: UNIX-Sekunden (Zeitfenster ±5 Minuten)
  • X-SMP-Event-Id: Eindeutige Ereignis-ID (zur Replay-Abwehr)

Beispiel: TypeScript-Verifizierung

import crypto from 'crypto';

function timingSafeEqual(a: string, b: string) {
  const ab = Buffer.from(a);
  const bb = Buffer.from(b);
  if (ab.length !== bb.length) return false;
  return crypto.timingSafeEqual(ab, bb);
}

export function verifyWebhook({
  bodyRaw,
  signature,
  timestamp,
  secret,
}: {
  bodyRaw: string;
  signature: string;
  timestamp: string;
  secret: string;
}) {
  // 1) Timestamp window check (±5 minutes)
  const now = Math.floor(Date.now() / 1000);
  const ts = parseInt(timestamp, 10);
  if (Number.isNaN(ts) || Math.abs(now - ts) > 300) {
    throw new Error('Timestamp outside allowed window');
  }

  // 2) Compute expected signature
  const payload = timestamp + '.' + bodyRaw;
  const expected = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');

  if (!timingSafeEqual(expected, signature)) {
    throw new Error('Invalid webhook signature');
  }
}

Retries

Bei temporären Fehlern (z. B. 5xx) wird mit exponentiellem Backoff erneut zugestellt (mehrere Versuche). Verwenden SieX-SMP-Event-Id, um Doppellieferungen idempotent zu verarbeiten.