Zum Inhalt springen

Ausgehende Webhooks

Geprüft

Ein Webhook ist das Gegenteil von REST und MCP: Nicht Sie fragen, sondern MitoOps meldet sich bei Ihnen. Wenn in Ihrem Konto ein Ereignis eintritt, das Sie abonniert haben, senden wir per POST einen kurzen signierten Umschlag an Ihre Adresse. Der Umschlag trägt Kennungen und Status, nicht den Inhalt der Bestellung: Die Details liest Ihr System über die REST-Schnittstelle mit seinem eigenen Schlüssel.

Abonnements verwalten Sie in der Anwendung: Einstellungen → API & MCP → Ausgehende Webhooks. Sie stehen ab dem Tarif Start zur Verfügung; erforderlich ist das Recht Ausgehende Webhooks (Lesen oder Verwalten).

Ein Endpoint hat einen Namen, eine Adresse, eine Liste von Ereignissen und einen Shop-Umfang. Beim Anlegen erhalten Sie ein Secret: Es wird einmal angezeigt, danach nie wieder; Sie können es jederzeit austauschen (Rotation), und das alte hört sofort auf zu gelten.

  • Die Adresse muss öffentlich sein und https:// auf dem Standardport verwenden. Adressen im internen Netz (der lokale Rechner, private Adressbereiche, Cloud-Metadaten, VPN-Bereiche) werden beim Speichern und bei jeder Zustellung abgelehnt: Der Name wird jedes Mal neu aufgelöst, und die Verbindung wird an die geprüften Adressen gebunden.
  • Weiterleitungen werden nicht verfolgt. Eine Antwort 3xx ist ein Fehlschlag.
  • Shops: alle (einschließlich später hinzugefügter) oder ausgewählte. Ein Ereignis aus einem Shop außerhalb des Umfangs wird nicht gesendet.
  • Eine Testzustellung über die Schaltfläche sendet ein Ereignis ping auf demselben Weg wie ein echtes, einschließlich Signatur und Eintrag im Protokoll.
  • Ein Endpoint lässt sich deaktivieren (ausstehende Zustellungen werden übersprungen) und löschen (das Protokoll bleibt erhalten).
Ereignis Wann resource.type Felder in data
order.created neue Bestellung aus dem Shop order order_code
order.paid Bestellung als bezahlt markiert order order_code
order.status_changed Bestellstatus geändert order order_code, status_id, previous_status_id
shipment.created Sendung beim Versanddienstleister angelegt shipment shipment_id, order_code, carrier_code, tracking_number
shipment.status_changed Sendungsstatus geändert (durch den Versanddienstleister oder die Mitarbeitenden) shipment shipment_id, order_code, carrier_code, status, previous_status, tracking_number
shipment.delivered Sendung zugestellt; zusätzlich zu shipment.status_changed shipment wie oben, status ist DELIVERED
inventory.stock_changed Lagerbewegung einer Position inventory_item item_id, sku, kind
invoice.created Rechnung ausgestellt invoice invoice_code, order_code
claim.created Reklamation angelegt (vom Kunden oder von den Mitarbeitenden) claim claim_code, order_code
claim.status_changed Reklamationsstatus geändert claim claim_code, order_code, status_id
claim.closed Reklamation abgeschlossen claim claim_code, order_code
search_console.connection.changed Verbindungsstatus von Search Console geändert (verbunden, Website geändert, Zugriff verloren, getrennt) search_console_property market, status, previous_status, property, previous_property, changed_at
search_console.sync.completed Archivsynchronisierung hat Tage hinzugefügt oder geändert search_console_property market, property, search_types, data_state, final_data_through, fresh_data_through, days_upserted, revised_dates, sync_version, synced_at
search_console.sync.failed Archivsynchronisierung fehlgeschlagen search_console_property market, property, error, error_code, last_successful_sync_at, retry_at
product_content.changed MitoOps hat eine geänderte Beschreibung, Kurzbeschreibung oder einen SEO-Text eines Produkts in den Shop geschrieben product store_id, market, entity, entity_id, canonical_url, source, source_run_id, changed_fields, before_hash, after_hash, changed_at
category_content.changed MitoOps hat eine geänderte obere oder untere Beschreibung oder einen SEO-Text einer Kategorie in den Shop geschrieben category wie bei product_content.changed
ping Testzustellung über die Schaltfläche webhook_endpoint message, endpoint

Die Ereignisse stammen aus derselben Geschäftsschicht wie die Automatisierungen: Was eine Automatisierung auslöst, lässt sich auch als Webhook abonnieren. Rücksendungen werden nicht als shipment.* gesendet, sie gehören zur Reklamation.

product_content.changed und category_content.changed entstehen einmal pro Schreibvorgang und Shop, wenn MitoOps einen tatsächlich geänderten Text in den Shop schreibt — aus der Produktkarte, der Massenbearbeitung, dem Import, der Kategorieseite, aus einem freigegebenen Vorschlag oder einem autonomen Schreibvorgang. Eine Änderung von Preis, Bestand oder Code sowie das erneute Speichern desselben Textes lösen kein Ereignis aus. Änderungen direkt in der Shop-Administration meldet dieses Ereignis nicht.

  • entity ist product oder category; entity_id (und resource.id) ist der Produktcode bzw. die Kategorie-ID im Shop — dieselben, mit denen Sie Texte über products_content_get und categories_content_get lesen. store_id ist der Shop-Code, identisch mit store.
  • source: ui (eine Person in der Anwendung), assistant (der Assistent in der Anwendung), mcp (ein Vorschlag über MCP, den eine Person freigegeben hat), autonomous (ein im autonomen Modus geschriebener Vorschlag). Der Wert rest ist reserviert — REST ist heute nur lesend.
  • source_run_id: die Vorschlags-ID bei freigegebenen und autonomen Schreibvorgängen; bei anderen Produkt-Schreibvorgängen die ID des Schreibstapels (Produktänderungen); bei einer außerhalb eines Vorschlags gespeicherten Kategorie null.
  • changed_fields nennt die geänderten Felder: beim Produkt description, shortDescription, metaTitle, metaDescription, bei der Kategorie description, descriptionBottom, metaTitle, metaDescription. before_hash und after_hash enthalten für jedes davon den SHA-256 des Textes in UTF-8 (ein fehlender Wert ergibt den Hash der leeren Zeichenkette) — denselben Wert, den das Feld hash beim Lesen liefert. Den Text selbst enthält der Umschlag nicht.
  • Nach der Freigabe eines Vorschlags ist after_hash der Hash dessen, was der Shop nach dem Schreiben tatsächlich gespeichert hat (WordPress entfernt zum Beispiel einen <style>-Block), nicht dessen, was vorgeschlagen wurde.
  • canonical_url ist die öffentliche Adresse des Produkts oder der Kategorie, sofern MitoOps sie kennt; sonst null.

Was Ihr Schlüssel im jeweiligen Shop lesen und vorschlagen darf und ob ein Vorschlag automatisch geschrieben wird, erfahren Sie vorab über die Inhaltsrichtlinie: GET /api/v1/product-content/policy?market=… oder das MCP-Werkzeug products_content_policy (Scope product-content:read).

Jede Zustellung ist JSON mit einer Version des Umschlags. Ein neues Feld kann ohne Versionswechsel hinzukommen; eine Änderung der Bedeutung eines Feldes ist eine neue Version.

{
"event_id": "9b2f0c1e-6d3a-4c7e-9a2b-0f1e2d3c4b5a",
"event_type": "shipment.delivered",
"version": 1,
"occurred_at": "2026-09-03T08:41:12.318Z",
"tenant": "00001",
"store": "sk",
"resource": { "type": "shipment", "id": "5521" },
"data": {
"shipment_id": "5521",
"order_code": "2026001234",
"carrier_code": "gls",
"status": "DELIVERED",
"previous_status": "IN_TRANSIT",
"tracking_number": "GLS123456789"
}
}
  • event_id ist je Ereignis eindeutig. Wenn zwei Ihrer Endpoints dasselbe Ereignis abonnieren, erhalten sie dieselbe ID. Eine Wiederholung trägt dieselbe ID und denselben Body.
  • store ist der Shop-Code (market), derselbe, den REST verwendet.
  • tenant ist die Kennung Ihres Kontos bei MitoOps.
Content-Type: application/json
User-Agent: MitoOps-Webhooks/1
X-MitoOps-Event: shipment.delivered
X-MitoOps-Event-Id: 9b2f0c1e-6d3a-4c7e-9a2b-0f1e2d3c4b5a
X-MitoOps-Timestamp: 1756888872
X-MitoOps-Signature: v1=3f1a…c9e0
X-MitoOps-Delivery: 184

Die Signatur ist ein HMAC-SHA256 mit dem Secret des Endpoints als Schlüssel über der Zeichenkette:

<X-MitoOps-Timestamp> + "." + <exakter Request-Body>

Das Ergebnis ist hexadezimal und steht im Header mit dem Versionspräfix v1=. Der Zeitstempel ist Unix-Zeit in Sekunden.

Prüfen Sie über dem rohen Body der Anfrage (den Bytes so, wie sie angekommen sind), nicht über neu serialisiertem JSON: Jede Änderung an Leerzeichen oder an der Reihenfolge der Schlüssel macht die Signatur ungültig. Vergleichen Sie in konstanter Zeit und lehnen Sie Nachrichten ab, die älter als 5 Minuten sind.

import { createHmac, timingSafeEqual } from 'node:crypto';
function verifyMitoOpsWebhook({ secret, headers, rawBody, now = Math.floor(Date.now() / 1000) }) {
const ts = Number(headers['x-mitoops-timestamp']);
if (!Number.isFinite(ts) || Math.abs(now - ts) > 300) return false; // alter oder fehlender Zeitstempel
const received = String(headers['x-mitoops-signature'] || '')
.split(',').map((s) => s.trim()).find((s) => s.startsWith('v1='));
if (!received) return false;
const expected = createHmac('sha256', secret)
.update(String(ts) + '.').update(rawBody).digest('hex');
const a = Buffer.from(received.slice(3), 'hex');
const b = Buffer.from(expected, 'hex');
return a.length === b.length && timingSafeEqual(a, b);
}
import hmac, hashlib, time
def verify_mitoops_webhook(secret: str, headers: dict, raw_body: bytes) -> bool:
try:
ts = int(headers["X-MitoOps-Timestamp"])
except (KeyError, ValueError):
return False
if abs(int(time.time()) - ts) > 300:
return False
sig = next((s.strip() for s in headers.get("X-MitoOps-Signature", "").split(",") if s.strip().startswith("v1=")), None)
if not sig:
return False
expected = hmac.new(secret.encode(), f"{ts}.".encode() + raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(sig[3:], expected)

Die Signatur deckt den Zeitstempel ab, daher lässt sich eine abgefangene Nachricht später nicht mit einer neuen Zeit erneut senden. Bewahren Sie zusätzlich zur Zeitprüfung die event_id der letzten Zustellungen auf und ignorieren Sie Duplikate: Eine wiederholte Zustellung ist eine Eigenschaft des Systems, kein Fehler.

  • Antworten Sie mit 2xx innerhalb von 10 Sekunden. Der Body der Antwort wird nicht gelesen. Reihen Sie längere Verarbeitung im Hintergrund ein und antworten Sie sofort.
  • Jede andere Antwort (4xx, 5xx), eine Zeitüberschreitung oder ein Verbindungsfehler ist ein Fehlschlag und wird mit Abständen von 1, 5, 15, 60 und 240 Minuten wiederholt; insgesamt sechs Versuche. Danach ist die Zustellung FAILED und wartet auf eine manuelle Wiederholung aus dem Protokoll.
  • Eine Antwort 3xx und eine Adresse, die nicht mehr in ein öffentliches Netz aufgelöst wird, sind Fehlschläge ohne Wiederholung: Sie beheben sich nicht von selbst.
  • Das Zustellungsprotokoll in der Anwendung zeigt für jeden Versuch Status, HTTP-Code, Zeit, Anzahl der Versuche und Ereignis. Der Body der Nachricht wird im Protokoll nicht angezeigt.
Status Bedeutung
PENDING wartet auf einen Versuch oder auf die nächste Wiederholung
SENT der Empfänger hat mit 2xx geantwortet
FAILED Versuche ausgeschöpft oder dauerhafter Fehler; lässt sich manuell wiederholen
SKIPPED der Endpoint wurde inzwischen deaktiviert oder gelöscht
  • Das Secret wird verschlüsselt gespeichert und nie in Logs oder in das Zustellungsprotokoll geschrieben. Es wird einmal angezeigt; das Austauschen funktioniert auch dann, wenn der Tarif keine Webhooks mehr enthält, denn es ist eine Sicherheitsoperation.
  • Keine personenbezogenen Daten. Der Umschlag trägt Kennungen und Status. Name, Adresse und E-Mail des Kunden werden nicht gesendet; wer sie braucht, liest sie über REST mit eigenem Berechtigungsbereich und eigenem Prüfprotokoll.
  • Nur HTTPS, nur öffentliche Adressen, keine Weiterleitungen. Die Prüfung beim Speichern und bei jeder Zustellung schützt vor dem Routing in ein internes Netz und vor DNS-Änderungen.
  • Ein Abonnement legt ein Mensch an, kein Schlüssel. Weder ein REST-Zugriffsschlüssel noch eine OAuth-Einwilligung kann ein Abonnement anlegen: Lesen auf Anfrage und ein dauerhafter ausgehender Strom von Ereignissen sind zwei verschiedene Berechtigungen.
  • Isolation. Abonnements und Protokoll liegen im Bereich Ihres Kontos; der Shop-Umfang wird bei jedem Ereignis ausgewertet.
Tarif Ausgehende Webhooks Endpoints
Testversion — 0
Einsteiger — 0
Start ja 5
Wachstum ja 20
Pro ja 50
Maßgeschneidert nach Vereinbarung nach Vereinbarung

Gezählt werden nicht gelöschte Endpoints, aktivierte wie deaktivierte. Ein Wechsel in einen niedrigeren Tarif löscht bestehende Endpoints nicht; es lässt sich nur kein neuer hinzufügen, bis die Anzahl unter das Limit sinkt, und bei einem Tarif ohne Webhooks werden keine Ereignisse mehr gesendet, bis der Tarif wiederhergestellt ist.

Ein Endpoint kann auch das Ziel des Schritts Webhook senden in den Automatisierungen sein: Die Nachricht wird erst gesendet, wenn die Bedingungen der Regel erfüllt sind, nicht bei jedem Ereignis. Der Schritt wählt nur den Endpoint aus; Adresse, Secret und Shop-Umfang bleiben im Register.

Die Nachricht hat den Typ automation.action (er lässt sich nicht direkt abonnieren) und denselben Umschlag, dieselbe Signatur, dieselben Wiederholungen und dasselbe Protokoll wie andere Ereignisse. In data trägt sie automation.workflow_id, automation.run_id, automation.node_id, automation.source_event (das Ereignis, das die Regel ausgelöst hat) und die Kennungen der Bestellung, der Sendung oder der Reklamation; resource ist die Entität der Ausführung. Details laden Sie über die REST-API.

Wenn der Empfänger 30 Versuche in Folge nicht erfolgreich beantwortet oder ein Endpoint mehr als 5.000 ausstehende Zustellungen hat, pausiert MitoOps den Endpoint und benachrichtigt Sie. Ausstehende Zustellungen bleiben erhalten; beheben Sie den Fehler beim Empfänger und aktivieren Sie den Endpoint, dann werden sie nachgeholt. Das Zustellungsprotokoll wird 30 Tage aufbewahrt.