Webhooks und Ereignisse

Statt im Minutentakt zu fragen, ob sich etwas getan hat: Sie hinterlegen eine URL und bekommen Bescheid.

Endpunkt registrieren

curl -X POST https://api.maklerportal.example/v1/webhooks \
  -H "apikey: $MAKLERPORTAL_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
        "url": "https://crm.ihre-firma.de/hooks/maklerportal",
        "ereignisse": ["expose.fertiggestellt", "anfrage.eingegangen", "termin.bestaetigt"],
        "beschreibung": "CRM-Anbindung Produktion"
      }'

Die Antwort enthält ein signatur_geheimnis. Es wird nur einmal ausgegeben und dient zur Prüfung eingehender Zustellungen.

Verfügbare Ereignisse

EreignisAusgelöst durch
objekt.angelegtEin neues Objekt wurde gespeichert
objekt.aktualisiertStammdaten, Preis oder Medien haben sich geändert
objekt.archiviertObjekt wurde aus der Vermarktung genommen
expose.fertiggestelltGenerierung abgeschlossen, PDF steht bereit
expose.veroeffentlichtExposé ist auf mindestens einem Kanal live
anfrage.eingegangenEin Interessent hat sich auf ein Objekt gemeldet
anfrage.bewertetDas Matching hat eine Anfrage eingestuft
termin.bestaetigtEin Besichtigungstermin wurde zugesagt
termin.abgesagtTermin wurde von einer Seite abgesagt
abrechnung.erstelltEine Provisionsabrechnung wurde erzeugt

Aufbau einer Zustellung

{
  "id": "evt_0d41c8",
  "typ": "expose.fertiggestellt",
  "erzeugt_am": "2026-08-20T09:41:20Z",
  "daten": {
    "expose_id": "exp_4d9b2e",
    "objekt_id": "obj_8f2c1a",
    "status": "fertig",
    "pdf_url": "https://api.maklerportal.example/v1/exposes/exp_4d9b2e/pdf"
  }
}

Kopfzeilen jeder Zustellung:

HeaderInhalt
X-Maklerportal-EreignisEreignistyp, z. B. expose.fertiggestellt
X-Maklerportal-ZustellungEindeutige ID dieser Zustellung
X-Maklerportal-Signaturt=<unixzeit>,v1=<hex>

Signatur prüfen

Signiert wird "<unixzeit>.<rohbody>" per HMAC-SHA256 mit Ihrem signatur_geheimnis.

import hashlib, hmac, time

def signatur_gueltig(rohbody: bytes, header: str, geheimnis: str, toleranz: int = 300) -> bool:
    teile = dict(p.split("=", 1) for p in header.split(","))
    zeitstempel, signatur = teile["t"], teile["v1"]

    if abs(time.time() - int(zeitstempel)) > toleranz:
        return False  # zu alt – schützt vor Wiedereinspielung

    erwartet = hmac.new(
        geheimnis.encode(),
        f"{zeitstempel}.".encode() + rohbody,
        hashlib.sha256,
    ).hexdigest()

    return hmac.compare_digest(erwartet, signatur)

Prüfen Sie gegen den rohen Anfragekörper. Wer erst nach JSON parst und wieder serialisiert, verändert Whitespace und Schlüsselreihenfolge – die Signatur passt dann nicht mehr.

Wiederholungen und Idempotenz

Wir erwarten innerhalb von fünf Sekunden einen 2xx-Status. Bleibt er aus, wiederholen wir mit wachsendem Abstand: nach 1 min, 5 min, 30 min, 2 h und 6 h. Danach gilt die Zustellung als gescheitert und der Endpunkt wird nach 24 Stunden ohne Erfolg automatisch pausiert.

Zustellungen können mehrfach ankommen. Führen Sie die verarbeiteten id-Werte mindestens sieben Tage lang mit und verwerfen Sie Wiederholungen. Antworten Sie sofort mit 202 und arbeiten Sie asynchron weiter – langsame Verarbeitung im Request-Handler ist der häufigste Grund für unnötige Wiederholungen.