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
- Gehe zu Einstellungen > Entwickler > Webhooks und klicke auf Webhook hinzufügen.
- Gib deine HTTPS-Endpunkt-URL ein.
- Wähle die Ereignisse aus, die du empfangen möchtest (oder lasse die Auswahl leer für alle Ereignisse).
- Kopiere das Signiergeheimnis — es wird nur einmal angezeigt.
- Klicke auf Testereignis senden, um deinen Endpunkt zu überprüfen.
Du kannst Webhooks auch über die CLI (mo webhooks) oder die API verwalten.
Ereignistypen
| Ereignis | Auslöser |
|---|---|
item.created | Ein Element wird einem Container hinzugefügt |
item.updated | Felder eines Elements werden geändert (enthält changed_fields) |
item.deleted | Ein Element wird entfernt |
item.expired | Das Ablaufdatum eines Lagerartikels ist heute |
item.expiring_soon | Ein Lagerartikel läuft innerhalb von 7 Tagen ab (wird auch alle 10 Tage für überfällige Artikel ausgelöst) |
container.created | Ein neuer Container wird erstellt |
container.updated | Ein Container wird umbenannt oder seine Einstellungen ändern sich |
container.deleted | Ein Container wird gelöscht |
member.added | Ein Benutzer wird einem Container hinzugefügt |
member.removed | Ein Benutzer wird aus einem Container entfernt |
webhook.test | Wird 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:
{
"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
| Feld | Typ | Beschreibung |
|---|---|---|
schema_version | string | Immer "1" |
event | string | Ereignistyp (z. B. item.created) |
event_id | UUID | Eindeutige ID für dieses Ereignis |
created_at | ISO 8601 | Zeitpunkt des Ereignisses |
actor_id | UUID | Benutzer, der die Aktion ausgelöst hat (fehlt bei Systemereignissen) |
container_id | UUID | Betroffener Container |
container_type | string | storage, board, calendar usw. |
data | object | Ereignisspezifische Daten (siehe unten) |
batch_id | UUID | Vorhanden, wenn mehrere Ereignisse zu einer Sammeloperation gehören |
Daten nach Ereignistyp
Elementereignisse (item.created, item.updated, item.deleted):
{
"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):
{
"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):
{
"container_id": "container-uuid",
"container_type": "storage",
"name": "Kühlschrank"
}Mitgliederereignisse (member.added, member.removed):
{
"user_id": "benutzer-uuid",
"email": "benutzer@beispiel.de",
"name": "Alice",
"role": "member"
}Testereignis (webhook.test):
{
"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:
| Header | Beschreibung |
|---|---|
Content-Type | application/json |
User-Agent | Mottainai-Webhook/1.0 |
X-Mottainai-Event | Ereignistyp (z. B. item.created) |
X-Mottainai-Delivery | Eindeutige Zustellungs-ID (UUID) |
X-Mottainai-Signature-256 | HMAC-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
- Mottainai berechnet
HMAC-SHA256(geheimnis, roher_anfragekörper). - Das Ergebnis wird als
X-Mottainai-Signature-256: sha256=<hex-digest>gesendet. - Dein Server berechnet den HMAC neu und vergleicht mit zeitkonstantem Vergleich.
Beispiel (Node.js)
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)
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)
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:
- Rufe
POST /api/webhooks/{id}/rotate-secretauf (oder klicke in den Einstellungen auf Geheimnis rotieren). - Ein neues Geheimnis wird generiert und einmalig angezeigt.
- Das alte Geheimnis bleibt 1 Stunde gültig (die Antwort enthält
expires_old_at). - Überprüfe während des Überlappungszeitraums gegen beide Geheimnisse.
- Nach 1 Stunde ist das alte Geheimnis nicht mehr gültig.
Wiederholungsverhalten
Fehlgeschlagene Zustellungen werden mit exponentiellem Backoff wiederholt:
| Versuch | Wartezeit nach Fehlschlag |
|---|---|
| 1 | Sofort |
| 2 | 1 Minute |
| 3 | 5 Minuten |
| 4 | 30 Minuten |
| 5 | 2 Stunden |
| 6 | 8 Stunden |
| 7 | 24 Stunden |
| Nach 7 | Als 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
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_IDMCP-Tools
list_webhooks— alle Webhook-Endpunkte auflistencreate_webhook— neuen Endpunkt registrierenget_webhook— Endpunktdetails abrufenupdate_webhook— URL, Ereignisse oder Status aktualisierendelete_webhook— Endpunkt löschentest_webhook— Testereignis an einen Endpunkt sendenrotate_webhook_secret— Signiergeheimnis rotieren (1 Stunde Überlappung)list_webhook_deliveries— Zustellversuche eines Endpunkts auflistenretry_webhook_delivery— fehlgeschlagene oder gescheiterte Zustellung wiederholen
Limits
| Limit | Wert |
|---|---|
| Maximale Endpunkte pro Benutzer | 5 |
| Maximale Wiederholungsversuche | 7 (~35 Stunden) |
| Zustellungs-Timeout | 10 Sekunden |
| Erfasster Antwortkörper | 4096 Zeichen |
| Automatische Deaktivierung nach | 7 Tagen ausschließlicher Fehlschläge |