Ausgehende Webhooks
Geprüft
Kurz gesagt
Abschnitt betitelt „Kurz gesagt“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).
Abonnement (Endpoint)
Abschnitt betitelt „Abonnement (Endpoint)“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
3xxist 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
pingauf 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).
Ereignisse
Abschnitt betitelt „Ereignisse“| 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.
Änderung von Produkt- und Kategorietexten
Abschnitt betitelt „Änderung von Produkt- und Kategorietexten“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.
entityistproductodercategory;entity_id(undresource.id) ist der Produktcode bzw. die Kategorie-ID im Shop — dieselben, mit denen Sie Texte überproducts_content_getundcategories_content_getlesen.store_idist der Shop-Code, identisch mitstore.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 Wertrestist 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 Kategorienull.changed_fieldsnennt die geänderten Felder: beim Produktdescription,shortDescription,metaTitle,metaDescription, bei der Kategoriedescription,descriptionBottom,metaTitle,metaDescription.before_hashundafter_hashenthalten 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 Feldhashbeim Lesen liefert. Den Text selbst enthält der Umschlag nicht.- Nach der Freigabe eines Vorschlags ist
after_hashder 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_urlist die öffentliche Adresse des Produkts oder der Kategorie, sofern MitoOps sie kennt; sonstnull.
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).
Umschlag
Abschnitt betitelt „Umschlag“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_idist je Ereignis eindeutig. Wenn zwei Ihrer Endpoints dasselbe Ereignis abonnieren, erhalten sie dieselbe ID. Eine Wiederholung trägt dieselbe ID und denselben Body.storeist der Shop-Code (market), derselbe, den REST verwendet.tenantist die Kennung Ihres Kontos bei MitoOps.
Header und Signatur
Abschnitt betitelt „Header und Signatur“Content-Type: application/jsonUser-Agent: MitoOps-Webhooks/1X-MitoOps-Event: shipment.deliveredX-MitoOps-Event-Id: 9b2f0c1e-6d3a-4c7e-9a2b-0f1e2d3c4b5aX-MitoOps-Timestamp: 1756888872X-MitoOps-Signature: v1=3f1a…c9e0X-MitoOps-Delivery: 184Die 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üfung auf Ihrer Seite
Abschnitt betitelt „Prüfung auf Ihrer Seite“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)Schutz vor Replay-Angriffen
Abschnitt betitelt „Schutz vor Replay-Angriffen“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.
Antwort, Wiederholungen und Protokoll
Abschnitt betitelt „Antwort, Wiederholungen und Protokoll“- Antworten Sie mit
2xxinnerhalb 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 ZustellungFAILEDund wartet auf eine manuelle Wiederholung aus dem Protokoll. - Eine Antwort
3xxund 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 |
Sicherheit
Abschnitt betitelt „Sicherheit“- 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.
Webhook aus einer Automatisierung
Abschnitt betitelt „Webhook aus einer Automatisierung“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.
Automatische Pause
Abschnitt betitelt „Automatische Pause“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.