Skip to content
Als Markdown

Webhooks

Webhooks senden HTTP-POST-Benachrichtigungen an deinen Server, wenn Ereignisse in Mottainai auftreten. Nutze sie, um externe Systeme (n8n, Zapier, eigene Dienste) anzubinden, ohne eine dauerhafte WebSocket-Verbindung aufrechtzuerhalten.

Schnellstart

  1. Gehe zu Einstellungen > Entwickler > Webhooks und klicke auf Webhook hinzufügen.
  2. Gib deine HTTPS-Endpunkt-URL ein.
  3. Wähle die Ereignisse aus, die du empfangen möchtest (oder lasse die Auswahl leer für alle Ereignisse).
  4. Kopiere das Signiergeheimnis — es wird nur einmal angezeigt.
  5. Klicke auf Testereignis senden, um deinen Endpunkt zu überprüfen.

Du kannst Webhooks auch über die CLI (mo webhooks) oder die API verwalten.

Ereignistypen

EreignisAuslöser
item.createdEin Element wird einem Container hinzugefügt
item.updatedFelder eines Elements werden geändert (enthält changed_fields)
item.deletedEin Element wird entfernt
item.expiredDas Ablaufdatum eines Lagerartikels ist heute
item.expiring_soonEin Lagerartikel läuft innerhalb von 7 Tagen ab (wird auch alle 10 Tage für überfällige Artikel ausgelöst)
container.createdEin neuer Container wird erstellt
container.updatedEin Container wird umbenannt oder seine Einstellungen ändern sich
container.deletedEin Container wird gelöscht
member.addedEin Benutzer wird einem Container hinzugefügt
member.removedEin Benutzer wird aus einem Container entfernt
webhook.testWird manuell über die Testschaltfläche gesendet (nicht in der Warteschlange)

Du kannst in der Endpunkt-Konfiguration bestimmte Ereignisse abonnieren oder die Liste leer lassen, um alle Ereignisse zu empfangen.

Nutzlastformat

Jede Webhook-Zustellung ist ein HTTP POST mit Content-Type: application/json. Die Nutzlast folgt einem einheitlichen Format:

json
{
  "schema_version": "1",
  "event": "item.created",
  "event_id": "a1b2c3d4-...",
  "created_at": "2026-03-11T10:30:00Z",
  "actor_id": "benutzer-uuid",
  "container_id": "container-uuid",
  "container_type": "storage",
  "data": { ... },
  "batch_id": null
}

Felder

FeldTypBeschreibung
schema_versionstringImmer "1"
eventstringEreignistyp (z. B. item.created)
event_idUUIDEindeutige ID für dieses Ereignis
created_atISO 8601Zeitpunkt des Ereignisses
actor_idUUIDBenutzer, der die Aktion ausgelöst hat (fehlt bei Systemereignissen)
container_idUUIDBetroffener Container
container_typestringstorage, board, calendar usw.
dataobjectEreignisspezifische Daten (siehe unten)
batch_idUUIDVorhanden, wenn mehrere Ereignisse zu einer Sammeloperation gehören

Daten nach Ereignistyp

Elementereignisse (item.created, item.updated, item.deleted):

json
{
  "item_id": "element-uuid",
  "item_type": "storage",
  "title": "Hafermilch",
  "category": "Milchprodukte",
  "changed_fields": ["quantity"],
  "snapshot": { "id": "element-uuid", "title": "Hafermilch", "quantity": 3, "..." : "..." }
}

changed_fields ist nur bei item.updated vorhanden. snapshot enthält das vollständige Element wie von der API zurückgegeben und ist bei item.created und item.updated enthalten (fehlt bei item.deleted und positionalen Änderungen wie Kartenverschiebungen).

Ablaufereignisse (item.expired, item.expiring_soon):

json
{
  "item_id": "element-uuid",
  "title": "Joghurt",
  "expiration_date": "2026-03-11",
  "quantity": 2,
  "category": "Milchprodukte"
}

Diese werden systemgeneriert — actor_id fehlt.

Containerereignisse (container.created, container.updated, container.deleted):

json
{
  "container_id": "container-uuid",
  "container_type": "storage",
  "name": "Kühlschrank"
}

Mitgliederereignisse (member.added, member.removed):

json
{
  "user_id": "benutzer-uuid",
  "email": "benutzer@beispiel.de",
  "name": "Alice",
  "role": "member"
}

Testereignis (webhook.test):

json
{
  "schema_version": "1",
  "event": "webhook.test",
  "event_id": "...",
  "created_at": "2026-03-11T10:30:00Z",
  "actor_id": "benutzer-uuid",
  "message": "This is a test event from Mottainai."
}

HTTP-Header

Jede Zustellung enthält diese Header:

HeaderBeschreibung
Content-Typeapplication/json
User-AgentMottainai-Webhook/1.0
X-Mottainai-EventEreignistyp (z. B. item.created)
X-Mottainai-DeliveryEindeutige Zustellungs-ID (UUID)
X-Mottainai-Signature-256HMAC-SHA256-Signatur (sha256=<hex>)

Signaturüberprüfung

Jede Nutzlast wird mit dem Geheimnis deines Endpunkts per HMAC-SHA256 signiert. Überprüfe die Signatur, um sicherzustellen, dass die Anfrage von Mottainai stammt und nicht manipuliert wurde.

So funktioniert es

  1. Mottainai berechnet HMAC-SHA256(geheimnis, roher_anfragekörper).
  2. Das Ergebnis wird als X-Mottainai-Signature-256: sha256=<hex-digest> gesendet.
  3. Dein Server berechnet den HMAC neu und vergleicht mit zeitkonstantem Vergleich.

Beispiel (Node.js)

javascript
import crypto from 'node:crypto';

function verifySignature(secret, payload, signature) {
  const expected = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(expected),
    Buffer.from(signature)
  );
}

// In deinem Handler:
const signature = req.headers['x-mottainai-signature-256'];
const body = await getRawBody(req); // Rohbytes, nicht geparstes JSON
if (!verifySignature(process.env.WEBHOOK_SECRET, body, signature)) {
  return res.status(401).send('Ungültige Signatur');
}

Beispiel (Python)

python
import hashlib
import hmac

def verify_signature(secret: str, payload: bytes, signature: str) -> bool:
    expected = "sha256=" + hmac.new(
        secret.encode(), payload, hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(expected, signature)

Beispiel (Go)

go
func verifySignature(secret string, payload []byte, signature string) bool {
    mac := hmac.New(sha256.New, []byte(secret))
    mac.Write(payload)
    expected := "sha256=" + hex.EncodeToString(mac.Sum(nil))
    return hmac.Equal([]byte(expected), []byte(signature))
}

Geheimnisrotation

Rotiere Geheimnisse ohne Ausfallzeit:

  1. Rufe POST /api/webhooks/{id}/rotate-secret auf (oder klicke in den Einstellungen auf Geheimnis rotieren).
  2. Ein neues Geheimnis wird generiert und einmalig angezeigt.
  3. Das alte Geheimnis bleibt 1 Stunde gültig (die Antwort enthält expires_old_at).
  4. Überprüfe während des Überlappungszeitraums gegen beide Geheimnisse.
  5. Nach 1 Stunde ist das alte Geheimnis nicht mehr gültig.

Wiederholungsverhalten

Fehlgeschlagene Zustellungen werden mit exponentiellem Backoff wiederholt:

VersuchWartezeit nach Fehlschlag
1Sofort
21 Minute
35 Minuten
430 Minuten
52 Stunden
68 Stunden
724 Stunden
Nach 7Als gescheitert markiert

Gesamtes Wiederholungsfenster: ungefähr 35 Stunden.

Eine Zustellung gilt als erfolgreich, wenn dein Server einen beliebigen 2xx-Statuscode zurückgibt. Jeder andere Status, Timeout oder Verbindungsfehler löst eine Wiederholung aus.

Gescheiterte Zustellungen

Gescheiterte Zustellungen können manuell über Einstellungen > Entwickler > Webhooks > Protokolle oder über POST /api/webhooks/{id}/deliveries/{deliveryId}/retry erneut versucht werden.

Automatische Deaktivierung

Wenn ein Endpunkt 7 aufeinanderfolgende Tage nur fehlgeschlagene oder gescheiterte Zustellungen hat (keine erfolgreiche Zustellung), wird der Endpunkt automatisch deaktiviert. Du erhältst eine E-Mail-Benachrichtigung. Aktiviere den Endpunkt in den Einstellungen erneut, nachdem du das Problem behoben hast. Verpasste Ereignisse werden nicht nachgeholt.

SSRF-Einschränkungen

Um Missbrauch zu verhindern, werden Webhook-URLs vor jeder Zustellung überprüft:

  • HTTPS erforderlich (http:// ist nur im Entwicklungsmodus erlaubt)
  • Private IP-Bereiche blockiert: 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 127.0.0.0/8, 169.254.0.0/16, 0.0.0.0/8, 100.64.0.0/10, ::1/128, fc00::/7
  • DNS wird bei jedem Versuch neu aufgelöst, um DNS-Rebinding-Angriffe zu verhindern

Wenn deine URL zu einer blockierten IP auflöst, schlägt die Zustellung sofort mit einer klaren Fehlermeldung fehl.

CLI

bash
mo webhooks list
mo webhooks create --url "https://example.com/hook" [--events item.created,item.updated] [--containers ID1,ID2] [--description "Mein Hook"]
mo webhooks get WEBHOOK_ID
mo webhooks update WEBHOOK_ID --url "https://example.com/new-hook" [--events item.created]
mo webhooks delete WEBHOOK_ID --yes
mo webhooks test WEBHOOK_ID
mo webhooks rotate-secret WEBHOOK_ID
mo webhooks deliveries WEBHOOK_ID
mo webhooks retry WEBHOOK_ID DELIVERY_ID
mo webhooks enable WEBHOOK_ID

MCP-Tools

  • list_webhooks — alle Webhook-Endpunkte auflisten
  • create_webhook — neuen Endpunkt registrieren
  • get_webhook — Endpunktdetails abrufen
  • update_webhook — URL, Ereignisse oder Status aktualisieren
  • delete_webhook — Endpunkt löschen
  • test_webhook — Testereignis an einen Endpunkt senden
  • rotate_webhook_secret — Signiergeheimnis rotieren (1 Stunde Überlappung)
  • list_webhook_deliveries — Zustellversuche eines Endpunkts auflisten
  • retry_webhook_delivery — fehlgeschlagene oder gescheiterte Zustellung wiederholen

Limits

LimitWert
Maximale Endpunkte pro Benutzer5
Maximale Wiederholungsversuche7 (~35 Stunden)
Zustellungs-Timeout10 Sekunden
Erfasster Antwortkörper4096 Zeichen
Automatische Deaktivierung nach7 Tagen ausschließlicher Fehlschläge