API - Webhooks Admin

📕 Für Administratoren und Entwickler.

Webhooks ermöglichen es, Echtzeit-Benachrichtigungen über Aenderungen in spiritflow an externe Systeme zu senden. Wenn ein Ereignis eintritt (z.B. eine Aufgabe wird erstellt oder ein Kunde wird aktualisiert), sendet spiritflow einen HTTP POST-Request an die konfigurierten Webhook-Endpunkte.

Wichtig: Webhooks werden sowohl bei Aenderungen über die API als auch bei Aenderungen über die spiritflow-Oberflaeche ausgeloest.


Event-Typen

Aufgaben-Events

EventBeschreibung
task.createdNeue Aufgabe wurde erstellt
task.updatedAufgaben-Felder wurden geändert
task.status_changedAufgaben-Status wurde geändert
task.assignedBearbeiter einer Aufgabe wurde geändert
task.deletedAufgabe wurde gelöscht/archiviert

Hinweis: Bei Aufgaben kann ein einzelner Vorgang mehrere Events ausloesen (Dual-Delivery). Zum Beispiel loest eine Status-Aenderung sowohl task.updated als auch task.status_changed aus.

Projekt-Events

EventBeschreibung
project.createdNeues Projekt wurde erstellt
project.updatedProjekt-Felder wurden geändert
project.archivedProjekt wurde archiviert/gelöscht

Kunden-Events

EventBeschreibung
customer.createdNeuer Kunde wurde erstellt
customer.updatedKunden-Felder wurden geändert
customer.archivedKunde wurde archiviert/gelöscht

CTI-Events (Telefonie)

EventBeschreibung
cti.incoming_callEingehender Anruf wurde gemeldet
cti.dial_requestedAnruf wurde über Click-to-Dial angefordert

Hinweis: CTI-Events werden ausgeloest, wenn die CTI-Integration aktiv ist. cti.dial_requested wird gesendet, wenn ein Benutzer in der spiritflow-Oberflaeche auf eine Telefonnummer klickt. Die CTI-Middleware empfaengt diesen Webhook und initiiert den Anruf über die Telefonanlage.


Webhook-Payload-Format

Jeder Webhook-Request wird als HTTP POST mit einem JSON-Body gesendet. Alle Payloads folgen demselben Schema:

{
  "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "event": "task.updated",
  "created_at": "2025-03-01T10:30:00Z",
  "source": "api",
  "actor": {
    "id": 42,
    "type": "api_key",
    "name": "Mein API Key"
  },
  "data": {
    "id": 1,
    "title": "Webseite erstellen",
    "status": "IN_PROGRESS",
    "..."
  },
  "changes": {
    "status": {
      "from": "PLANNED",
      "to": "IN_PROGRESS"
    },
    "assigneeId": {
      "from": null,
      "to": 5
    }
  }
}

Payload-Felder

FeldTypBeschreibung
idstringEindeutige Event-ID (UUID)
eventstringEvent-Typ (z.B. task.created, project.updated)
created_atstringISO 8601 Zeitstempel in UTC
sourcestringQuelle des Events: "api" (API-Aenderung) oder "ui" (Oberflaeche)
actor.idnumberID des Ausloeser-Benutzers
actor.typestringTyp des Ausloeser: "api_key" oder "user"
actor.namestringName des API-Keys oder des Benutzers
dataobjectVollstaendiges Objekt nach der Aenderung (gleiche Struktur wie die jeweilige API-Response)
changesobjectGeaenderte Felder mit from/to-Werten. Leer bei *.created-Events.

data-Objekt je nach Event-Typ

Event-Praefixdata-ObjektReferenz
task.*PublicTaskDtoSiehe Aufgaben-Endpunkte
project.*PublicProjectDtoSiehe Projekt-Endpunkte
customer.*PublicCustomerDtoSiehe Kunden-Endpunkte

HTTP-Header

Jeder Webhook-Request enthält folgende Header:

HeaderBeschreibungBeispiel
Content-TypeImmer application/jsonapplication/json
User-AgentAbsender-Kennungspiritflow-Webhooks/1.0
X-spiritflow-EventEvent-Typtask.updated
X-spiritflow-DeliveryEindeutige Delivery-ID (UUID)a1b2c3d4-...
X-spiritflow-SignatureHMAC-SHA256 Signatursha256=abc123...

Signatur-Verifizierung

Jeder Webhook-Request wird mit einer HMAC-SHA256-Signatur versehen, um die Authentizitaet und Integritaet der Nachricht sicherzustellen.

Algorithmus:

  1. Den vollständigen Request-Body als UTF-8-String nehmen
  2. HMAC-SHA256 mit dem Webhook-Secret als Schlüssel berechnen
  3. Das Ergebnis als Hex-String formatieren
  4. Mit dem Wert aus dem X-spiritflow-Signature-Header vergleichen (nach Entfernen des sha256=-Praefixes)

Beispiel (Node.js):

const crypto = require('crypto');

function verifySignature(payload, secret, signatureHeader) {
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(payload, 'utf8')
    .digest('hex');

  const receivedSignature = signatureHeader.replace('sha256=', '');
  return crypto.timingSafeEqual(
    Buffer.from(expectedSignature, 'hex'),
    Buffer.from(receivedSignature, 'hex')
  );
}

Beispiel (Python):

import hmac
import hashlib

def verify_signature(payload: bytes, secret: str, signature_header: str) -> bool:
    expected = hmac.new(
        secret.encode('utf-8'),
        payload,
        hashlib.sha256
    ).hexdigest()
    received = signature_header.replace('sha256=', '')
    return hmac.compare_digest(expected, received)

Sicherheitshinweis: Das Webhook-Secret wird nur einmalig bei der Erstellung des Webhooks angezeigt. Verwenden Sie stets einen timing-safe Vergleich, um Timing-Angriffe zu vermeiden.


Retry-Verhalten

Wenn ein Webhook-Endpunkt nicht mit einem 2xx-Statuscode antwortet, versucht spiritflow die Zustellung automatisch erneut.

VersuchVerzoegerung
1Sofort
21 Minute
35 Minuten
430 Minuten
52 Stunden

Nach 5 erfolglosen Versuchen wird die Zustellung als fehlgeschlagen markiert.

Automatische Deaktivierung: Wenn ein Webhook-Endpunkt 15 aufeinanderfolgende Fehler erreicht (über alle Events hinweg), wird er automatisch deaktiviert. Er kann manuell über die API oder die Oberflaeche wieder aktiviert werden.

Timeout: Webhook-Endpunkte müssen innerhalb von 10 Sekunden antworten, andernfalls gilt die Zustellung als fehlgeschlagen.


Webhooks verwalten (Public API)

Webhooks können vollständig über die Public API verwaltet werden. Erforderliche Scopes: webhooks:read und/oder webhooks:write.

Basis-URL: /api/v1/webhooks

Hinweis: Jeder API-Key sieht nur die Webhooks, die mit ihm erstellt wurden.

Limit: Pro Mandant sind maximal 10 Webhooks zulässig. Bei Überschreitung antwortet der Server mit 409 Conflict.

Verfuegbare Event-Typen abrufen

GET /api/v1/webhooks/event-types

Scope: webhooks:read

Antwort:

{
  "data": [
    "task.created",
    "task.updated",
    "task.status_changed",
    "task.assigned",
    "task.deleted",
    "project.created",
    "project.updated",
    "project.archived",
    "customer.created",
    "customer.updated",
    "customer.archived",
    "cti.incoming_call",
    "cti.dial_requested"
  ]
}

Webhooks auflisten

GET /api/v1/webhooks

Scope: webhooks:read

Antwort:

{
  "data": [
    {
      "id": 1,
      "name": "Mein Webhook",
      "url": "https://example.com/webhook",
      "eventTypes": ["task.created", "task.updated", "project.created"],
      "description": "Benachrichtigung bei neuen Aufgaben und Projekten",
      "isActive": true,
      "consecutiveFailures": 0,
      "lastSuccessAt": "2025-03-01T10:00:00Z",
      "lastFailureAt": null,
      "lastFailureReason": null,
      "createdAt": "2025-02-15T08:00:00Z",
      "updatedAt": "2025-03-01T10:00:00Z"
    }
  ]
}

Einzelnen Webhook abrufen

GET /api/v1/webhooks/{id}

Scope: webhooks:read

Antwort: Einzelnes Webhook-Objekt im data-Feld (gleiche Struktur wie in der Liste).

Webhook erstellen

POST /api/v1/webhooks

Scope: webhooks:write

Request-Body:

{
  "name": "Mein Webhook",
  "url": "https://example.com/webhook",
  "eventTypes": ["task.created", "task.updated", "project.created"],
  "description": "Benachrichtigung bei neuen Aufgaben"
}
FeldTypPflichtBeschreibung
namestringJaName des Webhooks (max. 100 Zeichen)
urlstringJaZiel-URL (max. 2048 Zeichen, muss mit http:// oder https:// beginnen)
eventTypesstring[]JaMindestens ein Event-Typ
descriptionstringNeinBeschreibung (max. 500 Zeichen)

Antwort (201 Created):

{
  "data": {
    "id": 1,
    "name": "Mein Webhook",
    "url": "https://example.com/webhook",
    "eventTypes": ["task.created", "task.updated", "project.created"],
    "description": "Benachrichtigung bei neuen Aufgaben",
    "isActive": true,
    "secret": "whsec_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    "createdAt": "2025-03-01T10:00:00Z",
    "updatedAt": "2025-03-01T10:00:00Z"
  }
}

Wichtig: Das secret wird nur einmalig bei der Erstellung angezeigt. Speichern Sie es sicher ab — es wird für die Signatur-Verifizierung benötigt und kann später nicht mehr abgerufen werden.

Webhook aktualisieren

PUT /api/v1/webhooks/{id}

Scope: webhooks:write

Request-Body:

{
  "name": "Aktualisierter Name",
  "url": "https://example.com/new-webhook-url",
  "eventTypes": ["task.created", "project.created", "customer.created"],
  "description": "Aktualisierte Beschreibung"
}

Alle Felder sind optional. Nur angegebene Felder werden aktualisiert.

Antwort (200 OK): Aktualisiertes Webhook-Objekt im data-Feld.

Webhook löschen

DELETE /api/v1/webhooks/{id}

Scope: webhooks:write

Antwort: 204 No Content


Webhooks verwalten (Interne API)

Zusaetzlich zur Public API stehen über die interne API weitere Verwaltungsfunktionen zur Verfuegung (Testen, Aktivieren/Deaktivieren, Zustellungshistorie). Zugriff erfordert die Rolle TENANT_ADMIN und Session-basierte Authentifizierung (JWT-Cookie).

Basis-URL: /api/webhooks

Webhook aktivieren

PATCH /api/webhooks/{id}/enable

Antwort (200 OK): Aktualisiertes Webhook-Objekt mit isActive: true.

Webhook deaktivieren

PATCH /api/webhooks/{id}/disable

Antwort (200 OK): Aktualisiertes Webhook-Objekt mit isActive: false.

Webhook testen

Sendet ein Test-Ping-Event an den Webhook-Endpunkt.

POST /api/webhooks/{id}/test

Antwort:

{
  "success": true,
  "httpStatus": 200,
  "durationMs": 150,
  "responseBody": "OK",
  "errorMessage": null
}
FeldTypBeschreibung
successbooleanOb der Endpunkt mit 2xx geantwortet hat
httpStatusnumber?HTTP-Statuscode der Antwort (null bei Verbindungsfehler)
durationMsnumber?Antwortzeit in Millisekunden
responseBodystring?Antwort-Body (max. 500 Zeichen)
errorMessagestring?Fehlermeldung bei Verbindungsfehler

Der Test-Payload hat das Event ping:

{
  "id": "a1b2c3d4-...",
  "event": "ping",
  "created_at": "2025-03-01T10:30:00Z",
  "source": "test",
  "data": {
    "message": "This is a test webhook delivery from spiritflow."
  }
}

Zustellungs-Historie abrufen

GET /api/webhooks/{id}/deliveries?page=0&size=20

Query-Parameter:

ParameterTypStandardBeschreibung
pageint0Seite (0-basiert)
sizeint20Einträge pro Seite (max. 100)

Antwort:

{
  "content": [
    {
      "id": 1,
      "eventId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "eventType": "task.created",
      "status": "DELIVERED",
      "httpStatus": 200,
      "errorMessage": null,
      "attempt": 1,
      "requestDurationMs": 150,
      "createdAt": "2025-03-01T10:30:00Z",
      "deliveredAt": "2025-03-01T10:30:01Z",
      "nextRetryAt": null
    }
  ],
  "totalElements": 42,
  "totalPages": 3,
  "page": 0,
  "size": 20
}

Delivery-Status-Werte:

StatusBeschreibung
PENDINGZustellung steht aus
DELIVEREDErfolgreich zugestellt
FAILEDZustellung endgültig fehlgeschlagen
RETRYINGErneuter Zustellversuch geplant