Przejdź do głównej zawartości

Webhooki wychodzące

Zweryfikowane

Webhook to odwrotność REST i MCP: to nie Państwo pytają, tylko MitoOps się odzywa. Gdy na koncie wystąpi zdarzenie, które jest subskrybowane, wysyłamy na podany adres POST z krótką podpisaną kopertą. Koperta zawiera identyfikatory i statusy, a nie treść zamówienia; szczegóły system odczytuje przez interfejs REST własnym kluczem.

Subskrypcjami zarządza się w aplikacji: Ustawienia → API & MCP → Webhooki wychodzące. Są dostępne od taryfy Start; potrzebne jest uprawnienie Webhooki wychodzące (odczyt lub zarządzanie).

Endpoint ma nazwę, adres, listę zdarzeń i zakres e-shopów. Przy zakładaniu otrzymuje się tajny klucz: wyświetla się raz, a potem już nigdy; w każdej chwili można go wymienić (rotacja), a stary natychmiast przestaje obowiązywać.

  • Adres musi być publiczny i używać https:// na standardowym porcie. Adresy w sieci wewnętrznej (maszyna lokalna, zakresy prywatne, metadane chmury, zakresy VPN) są odrzucane przy zapisie i przy każdym doręczeniu: nazwa jest tłumaczona ponownie, a połączenie wiązane ze zweryfikowanymi adresami.
  • Przekierowania nie są wykonywane. Odpowiedź 3xx oznacza niepowodzenie.
  • E-shopy: wszystkie (także te, które dojdą) lub wybrane. Zdarzenie z e-shopu spoza zakresu nie jest wysyłane.
  • Testowe doręczenie z przycisku wysyła zdarzenie ping tą samą drogą co produkcyjne, łącznie z podpisem i wpisem do dziennika.
  • Endpoint można wyłączyć (oczekujące doręczenia są pomijane) i usunąć (dziennik pozostaje).
Zdarzenie Kiedy resource.type Pola w data
order.created nowe zamówienie z e-shopu order order_code
order.paid zamówienie oznaczone jako opłacone order order_code
order.status_changed zmiana statusu zamówienia order order_code, status_id, previous_status_id
shipment.created przesyłka utworzona u przewoźnika shipment shipment_id, order_code, carrier_code, tracking_number
shipment.status_changed zmiana statusu przesyłki (przewoźnik lub obsługa) shipment shipment_id, order_code, carrier_code, status, previous_status, tracking_number
shipment.delivered przesyłka doręczona; dodatkowo do shipment.status_changed shipment jak wyżej, status to DELIVERED
inventory.stock_changed ruch zapasu pozycji inventory_item item_id, sku, kind
invoice.created wystawiona faktura invoice invoice_code, order_code
claim.created założona reklamacja (przez klienta lub obsługę) claim claim_code, order_code
claim.status_changed zmiana statusu reklamacji claim claim_code, order_code, status_id
claim.closed reklamacja zamknięta claim claim_code, order_code
search_console.connection.changed zmiana stanu połączenia Search Console (połączenie, zmiana witryny, utrata dostępu, odłączenie) search_console_property market, status, previous_status, property, previous_property, changed_at
search_console.sync.completed synchronizacja archiwum dodała lub zmieniła dni 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 synchronizacja archiwum nie powiodła się search_console_property market, property, error, error_code, last_successful_sync_at, retry_at
product_content.changed MitoOps zapisał w sklepie zmieniony opis, krótki opis lub tekst SEO produktu 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 zapisał w sklepie zmieniony górny lub dolny opis albo tekst SEO kategorii category jak przy product_content.changed
ping testowe doręczenie z przycisku webhook_endpoint message, endpoint

Zdarzenia pochodzą z tej samej warstwy biznesowej co automatyzacje: to, co uruchamia automatyzację, można subskrybować także webhookiem. Przesyłki zwrotne (zwroty) nie są wysyłane jako shipment.*; należą do reklamacji.

product_content.changed i category_content.changed powstają raz na zapis i sklep, gdy MitoOps zapisze w sklepie tekst, który naprawdę się zmienił — z karty produktu, edycji zbiorczej, importu, ekranu kategorii, z zatwierdzonej propozycji lub z zapisu autonomicznego. Zmiana ceny, stanu magazynowego czy kodu ani ponowne zapisanie tego samego tekstu nie wywołują zdarzenia. Zmian wprowadzonych bezpośrednio w panelu sklepu to zdarzenie nie zgłasza.

  • entity to product lub category; entity_id (oraz resource.id) to kod produktu lub ID kategorii w sklepie — te same, którymi czytasz teksty przez products_content_get i categories_content_get. store_id to kod sklepu, taki sam jak store.
  • source: ui (osoba w aplikacji), assistant (asystent w aplikacji), mcp (propozycja przez MCP zatwierdzona przez osobę), autonomous (propozycja zapisana w trybie autonomicznym). Wartość rest jest zarezerwowana — REST służy dziś tylko do odczytu.
  • source_run_id: ID propozycji przy zapisie zatwierdzonym i autonomicznym; przy pozostałych zapisach produktu ID partii zapisu (Zmiany produktów); przy kategorii zapisanej poza propozycją null.
  • changed_fields wymienia zmienione pola: dla produktu description, shortDescription, metaTitle, metaDescription, dla kategorii description, descriptionBottom, metaTitle, metaDescription. before_hash i after_hash zawierają dla każdego z nich SHA-256 tekstu w UTF-8 (brak wartości = odcisk pustego ciągu) — tę samą wartość, którą przy odczycie zwraca pole hash. Sama koperta tekstu nie zawiera.
  • Po zatwierdzeniu propozycji after_hash jest odciskiem tego, co sklep faktycznie zapisał (WordPress usuwa na przykład blok <style>), a nie tego, co zaproponowano.
  • canonical_url to publiczny adres produktu lub kategorii, jeśli MitoOps go zna; w przeciwnym razie null.

Co Twój klucz może czytać i proponować w danym sklepie oraz czy propozycja zapisze się sama, sprawdzisz z wyprzedzeniem w zasadach treści: GET /api/v1/product-content/policy?market=… lub narzędziem MCP products_content_policy (zakres product-content:read).

Każde doręczenie to JSON z wersją koperty. Nowe pole może dojść bez zmiany wersji; zmiana znaczenia pola oznacza nową wersję.

{
"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 jest unikalne dla zdarzenia. Jeśli to samo zdarzenie subskrybują dwa endpointy, otrzymają to samo ID. Ponowna próba niesie to samo ID i tę samą treść.
  • store to kod e-shopu (market), ten sam, którego używa REST.
  • tenant to identyfikator konta w 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

Podpis to HMAC-SHA256 z kluczem = tajny klucz endpointu nad ciągiem:

<X-MitoOps-Timestamp> + "." + <dokładna treść żądania>

Wynik jest szesnastkowy i w nagłówku ma prefiks wersji v1=. Znacznik czasu to czas uniksowy w sekundach.

Weryfikację należy wykonywać na surowej treści żądania (bajtach w takiej postaci, w jakiej przyszły), a nie na ponownie zserializowanym JSON-ie: każda zmiana odstępu lub kolejności kluczy unieważnia podpis. Porównanie powinno odbywać się w stałym czasie, a wiadomości starsze niż 5 minut należy odrzucać.

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; // stary lub brakujący znacznik czasu
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)

Podpis obejmuje znacznik czasu, więc przechwyconej wiadomości nie można później wysłać z nowym czasem. Oprócz kontroli czasu warto przechowywać event_id ostatnich doręczeń i ignorować duplikaty: ponowne doręczenie jest cechą systemu, a nie błędem.

  • Należy odpowiedzieć 2xx w ciągu 10 sekund. Treść odpowiedzi nie jest odczytywana. Przetwarzanie, które trwa dłużej, należy przenieść do tła i odpowiedzieć od razu.
  • Inna odpowiedź (4xx, 5xx), przekroczenie czasu lub błąd połączenia oznacza niepowodzenie i ponowienie z odstępami 1, 5, 15, 60 i 240 minut; łącznie sześć prób. Potem doręczenie ma status FAILED i czeka na ręczne ponowienie z dziennika.
  • Odpowiedź 3xx oraz adres, który przestał się tłumaczyć na sieć publiczną, to niepowodzenia bez ponowienia: same się nie rozwiążą.
  • Dziennik doręczeń w aplikacji pokazuje dla każdej próby status, kod HTTP, czas, liczbę prób i zdarzenie. Treść wiadomości nie jest w dzienniku wyświetlana.
Status Znaczenie
PENDING czeka na próbę lub kolejne ponowienie
SENT odbiorca odpowiedział 2xx
FAILED próby wyczerpane lub błąd trwały; można ponowić ręcznie
SKIPPED endpoint został w międzyczasie wyłączony lub usunięty
  • Tajny klucz jest przechowywany w postaci zaszyfrowanej i nigdy nie jest zapisywany w logach ani w dzienniku. Wyświetla się raz; rotacja działa także wtedy, gdy taryfa nie obejmuje już webhooków, ponieważ jest to operacja bezpieczeństwa.
  • Bez danych osobowych. Koperta zawiera identyfikatory i statusy. Imię i nazwisko, adres ani e-mail klienta nie są wysyłane; kto ich potrzebuje, odczytuje je przez REST z własnym zakresem i audytem.
  • Tylko HTTPS, tylko adresy publiczne, bez przekierowań. Kontrola przy zapisie i przy każdym doręczeniu chroni przed kierowaniem do sieci wewnętrznej i przed zmianą DNS.
  • Subskrypcję zakłada człowiek, a nie klucz. Klucz dostępu do REST ani zgoda OAuth nie mogą założyć subskrypcji: odczyt na żądanie i stały strumień zdarzeń na zewnątrz to dwa różne uprawnienia.
  • Izolacja. Subskrypcje i dziennik znajdują się w przestrzeni konta; zakres e-shopów jest oceniany przy każdym zdarzeniu.
Taryfa Webhooki wychodzące Endpointów
Wersja próbna nie 0
Początkujący nie 0
Start tak 5
Wzrost tak 20
Pro tak 50
Na miarę według umowy według umowy

Liczą się nieusunięte endpointy, włączone i wyłączone. Obniżenie taryfy nie usuwa istniejących endpointów; nowy nie zostanie dodany, dopóki liczba nie spadnie poniżej limitu, a przy taryfie bez webhooków zdarzenia przestają być wysyłane do czasu przywrócenia taryfy.

Endpoint można użyć także jako kroku Wyślij webhook w Automatyzacjach: wiadomość wychodzi dopiero po spełnieniu warunków reguły, a nie przy każdym zdarzeniu. Krok tylko wybiera endpoint; adres, tajny klucz i zakres e-shopów pozostają w rejestrze.

Wiadomość ma typ automation.action (nie można go subskrybować bezpośrednio) oraz taką samą kopertę, podpis, ponowienia i dziennik jak pozostałe zdarzenia. W data zawiera automation.workflow_id, automation.run_id, automation.node_id, automation.source_event (zdarzenie, które wywołało regułę) oraz identyfikatory zamówienia, przesyłki lub zgłoszenia reklamacyjnego; resource to encja przebiegu. Szczegóły odczytuje się przez REST API.

Jeśli odbiorca nie odpowie na 30 kolejnych prób lub endpoint ma więcej niż 5 000 oczekujących doręczeń, MitoOps wstrzymuje endpoint i wysyła powiadomienie. Oczekujące doręczenia pozostają; po naprawie odbiorcy należy włączyć endpoint, a doręczenia zostaną zrealizowane. Dziennik doręczeń jest przechowywany przez 30 dni.