Sari la conținut

Webhookuri de ieșire

Verificat

Webhookul este opusul REST și MCP: nu întrebați dumneavoastră, se anunță MitoOps. Când în cont apare un eveniment la care sunteți abonat, trimitem la adresa dumneavoastră un POST cu un plic scurt, semnat. Plicul poartă identificatori și stări, nu conținutul comenzii: detaliul îl încarcă sistemul dumneavoastră prin interfața REST, cu propria cheie.

Abonamentele le gestionați în aplicație: Setări → API & MCP → Webhookuri de ieșire. Sunt disponibile de la tariful Start; este necesar dreptul Webhookuri de ieșire (citire sau gestionare).

Endpointul are un nume, o adresă, o listă de evenimente și un domeniu de magazine online. La creare primiți un secret: se afișează o singură dată, apoi niciodată; îl puteți înlocui oricând (rotire), iar cel vechi încetează imediat să fie valabil.

  • Adresa trebuie să fie publică și prin https://, pe portul standard. Adresele către rețeaua internă (mașina locală, intervale private, metadatele cloudului, intervale VPN) se refuză la salvare și la fiecare livrare: numele se rezolvă din nou, iar conexiunea se leagă de adresele verificate.
  • Redirecționările nu sunt urmate. Un răspuns 3xx este un eșec.
  • Magazine online: toate (inclusiv cele care se vor adăuga) sau cele alese. Un eveniment dintr-un magazin online în afara domeniului nu se trimite.
  • Livrarea de test, din buton, trimite evenimentul ping pe aceeași cale ca unul real, inclusiv semnătura și înregistrarea în jurnal.
  • Endpointul poate fi dezactivat (livrările în așteptare se sar) și șters (jurnalul rămâne).
Eveniment Când resource.type Câmpuri în data
order.created comandă nouă din magazinul online order order_code
order.paid comandă marcată ca plătită order order_code
order.status_changed schimbarea stării comenzii order order_code, status_id, previous_status_id
shipment.created expediere creată la curier shipment shipment_id, order_code, carrier_code, tracking_number
shipment.status_changed schimbarea stării expedierii (curier sau operator) shipment shipment_id, order_code, carrier_code, status, previous_status, tracking_number
shipment.delivered expediere livrată; în plus față de shipment.status_changed shipment ca mai sus, status este DELIVERED
inventory.stock_changed mișcarea stocului unui articol inventory_item item_id, sku, kind
invoice.created factură emisă invoice invoice_code, order_code
claim.created reclamație deschisă (de client sau de operator) claim claim_code, order_code
claim.status_changed schimbarea stării reclamației claim claim_code, order_code, status_id
claim.closed reclamație închisă claim claim_code, order_code
search_console.connection.changed starea conexiunii Search Console s-a schimbat (conectare, schimbare site, acces pierdut, deconectare) search_console_property market, status, previous_status, property, previous_property, changed_at
search_console.sync.completed sincronizarea arhivei a adăugat sau a modificat zile 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 sincronizarea arhivei a eșuat search_console_property market, property, error, error_code, last_successful_sync_at, retry_at
product_content.changed MitoOps a scris în magazin o descriere, o descriere scurtă sau un text SEO al produsului modificate 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 a scris în magazin o descriere de sus sau de jos ori un text SEO al categoriei modificate category ca la product_content.changed
ping livrare de test din buton webhook_endpoint message, endpoint

Evenimentele provin din același strat comercial ca automatizările: ce pornește o automatizare poate fi urmărit și printr-un webhook. Coletele retur nu se trimit ca shipment.*: aparțin reclamației.

product_content.changed și category_content.changed apar o singură dată pe scriere și magazin, când MitoOps scrie în magazin un text care s-a schimbat cu adevărat — din fișa produsului, editarea în masă, import, ecranul categoriilor, dintr-o propunere aprobată sau dintr-o scriere autonomă. Modificarea prețului, a stocului sau a codului, ori salvarea din nou a aceluiași text nu declanșează evenimentul. Modificările făcute direct în administrarea magazinului nu sunt raportate de acest eveniment.

  • entity este product sau category; entity_id (și resource.id) este codul produsului, respectiv ID-ul categoriei în magazin — aceleași cu care citiți textele prin products_content_get și categories_content_get. store_id este codul magazinului, același ca store.
  • source: ui (o persoană în aplicație), assistant (asistentul din aplicație), mcp (o propunere prin MCP aprobată de o persoană), autonomous (o propunere scrisă în regim autonom). Valoarea rest este rezervată — REST este astăzi doar pentru citire.
  • source_run_id: ID-ul propunerii la scrierile aprobate și autonome; la celelalte scrieri ale produsului, ID-ul lotului de scriere (Modificări produse); la o categorie salvată în afara unei propuneri, null.
  • changed_fields enumeră câmpurile modificate: la produs description, shortDescription, metaTitle, metaDescription, la categorie description, descriptionBottom, metaTitle, metaDescription. before_hash și after_hash conțin pentru fiecare SHA-256 al textului în UTF-8 (o valoare lipsă = amprenta șirului gol) — aceeași valoare pe care o întoarce câmpul hash la citire. Plicul nu conține textul propriu-zis.
  • După aprobarea unei propuneri, after_hash este amprenta a ceea ce magazinul a salvat efectiv (WordPress elimină, de exemplu, un bloc <style>), nu a ceea ce s-a propus.
  • canonical_url este adresa publică a produsului sau a categoriei, dacă MitoOps o cunoaște; altfel null.

Ce poate citi și propune cheia dumneavoastră pe un anumit magazin și dacă o propunere se va scrie singură aflați dinainte din politica de conținut: GET /api/v1/product-content/policy?market=… sau instrumentul MCP products_content_policy (domeniul product-content:read).

Fiecare livrare este un JSON cu versiunea plicului. Un câmp nou poate apărea fără schimbarea versiunii; o schimbare a semnificației unui câmp înseamnă o versiune nouă.

{
"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 este unic pentru eveniment. Dacă același eveniment este urmărit de două endpointuri ale dumneavoastră, ele primesc același ID. O încercare repetată poartă același ID și același corp.
  • store este codul magazinului online (market), același pe care îl folosește REST.
  • tenant este identificatorul contului dumneavoastră în 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

Semnătura este HMAC-SHA256 cu cheia = secretul endpointului, peste șirul:

<X-MitoOps-Timestamp> + "." + <corpul exact al cererii>

Rezultatul este hexazecimal și apare în antet cu prefixul de versiune v1=. Marcajul de timp este timpul unix în secunde.

Verificați pe corpul brut al cererii (octeții așa cum au sosit), nu pe un JSON serializat din nou: orice schimbare a unui spațiu sau a ordinii cheilor invalidează semnătura. Comparați în timp constant și refuzați mesajele mai vechi de 5 minute.

import { createHmac, timingSafeEqual } from 'node:crypto';
function overMitoOpsWebhook({ tajomstvo, hlavicky, suroveTelo, teraz = Math.floor(Date.now() / 1000) }) {
const cas = Number(hlavicky['x-mitoops-timestamp']);
if (!Number.isFinite(cas) || Math.abs(teraz - cas) > 300) return false; // marcaj vechi sau lipsă
const prijaty = String(hlavicky['x-mitoops-signature'] || '')
.split(',').map((s) => s.trim()).find((s) => s.startsWith('v1='));
if (!prijaty) return false;
const ocakavany = createHmac('sha256', tajomstvo)
.update(String(cas) + '.').update(suroveTelo).digest('hex');
const a = Buffer.from(prijaty.slice(3), 'hex');
const b = Buffer.from(ocakavany, 'hex');
return a.length === b.length && timingSafeEqual(a, b);
}
import hmac, hashlib, time
def over_mitoops_webhook(tajomstvo: str, hlavicky: dict, surove_telo: bytes) -> bool:
try:
cas = int(hlavicky["X-MitoOps-Timestamp"])
except (KeyError, ValueError):
return False
if abs(int(time.time()) - cas) > 300:
return False
podpis = next((s.strip() for s in hlavicky.get("X-MitoOps-Signature", "").split(",") if s.strip().startswith("v1=")), None)
if not podpis:
return False
ocakavany = hmac.new(tajomstvo.encode(), f"{cas}.".encode() + surove_telo, hashlib.sha256).hexdigest()
return hmac.compare_digest(podpis[3:], ocakavany)

Semnătura acoperă marcajul de timp, deci un mesaj interceptat nu poate fi trimis mai târziu cu o oră nouă. Pe lângă verificarea timpului, păstrați event_id al ultimelor livrări și ignorați duplicatele: livrarea repetată este o proprietate a sistemului, nu o eroare.

  • Răspundeți cu 2xx în 10 secunde. Corpul răspunsului nu se citește. O procesare care durează mai mult puneți-o în fundal și răspundeți imediat.
  • Un alt răspuns (4xx, 5xx), depășirea timpului sau o eroare de conexiune = eșec și repetare la intervale de 1, 5, 15, 60 și 240 de minute; în total șase încercări. Apoi livrarea este FAILED și așteaptă o repetare manuală din jurnal.
  • Un răspuns 3xx și o adresă care a încetat să se rezolve în rețeaua publică sunt eșecuri fără repetare: nu se rezolvă singure.
  • Jurnalul livrărilor din aplicație arată pentru fiecare încercare starea, codul HTTP, ora, numărul de încercări și evenimentul. Corpul mesajului nu se afișează în jurnal.
Stare Semnificație
PENDING așteaptă o încercare sau următoarea repetare
SENT destinatarul a răspuns 2xx
FAILED încercările s-au epuizat sau eroare permanentă; poate fi repetată manual
SKIPPED între timp endpointul a fost dezactivat sau șters
  • Secretul se stochează criptat și nu se scrie niciodată în jurnale sau în jurnalul livrărilor. Se afișează o singură dată; rotirea funcționează chiar și atunci când tariful nu mai include webhookuri: este o operație de securitate.
  • Fără date personale. Plicul poartă identificatori și stări. Numele, adresa sau e-mailul clientului nu se trimit; cine are nevoie de ele le citește prin REST, cu propriul domeniu și audit.
  • Doar HTTPS, doar adrese publice, fără redirecționări. Verificarea la salvare și la fiecare livrare protejează împotriva direcționării în rețeaua internă și împotriva schimbării DNS.
  • Abonamentul îl creează un om, nu o cheie. O cheie de acces la REST sau un consimțământ OAuth nu pot crea un abonament: citirea la cerere și un flux permanent de evenimente către exterior sunt două permisiuni diferite.
  • Izolare. Abonamentele și jurnalul se află în spațiul contului dumneavoastră; domeniul magazinelor online se evaluează la fiecare eveniment.
Tarif Webhookuri de ieșire Endpointuri
Versiune de probă — 0
Începător — 0
Start da 5
Creștere da 20
Pro da 50
Personalizat conform acordului conform acordului

Se numără endpointurile neșterse, atât activate, cât și dezactivate. Reducerea tarifului nu șterge endpointurile existente; unul nou nu se adaugă până când numărul nu scade sub limită, iar la un tarif fără webhookuri evenimentele încetează să fie trimise până când tariful revine.

Endpointul îl puteți folosi și ca pas Trimite webhook în Automatizări: mesajul pleacă abia după îndeplinirea condițiilor regulii, nu la fiecare eveniment. Pasul doar alege endpointul: adresa, secretul și domeniul magazinelor online rămân în registru.

Mesajul are tipul automation.action (nu poate fi urmărit direct) și același plic, aceeași semnătură, repetare și jurnal ca celelalte evenimente. În data poartă automation.workflow_id, automation.run_id, automation.node_id, automation.source_event (evenimentul care a trezit regula) și identificatorii comenzii, expedierii sau cazului de reclamație; resource este entitatea rulării. Detaliul îl încărcați prin API-ul REST.

Dacă destinatarul nu răspunde la 30 de încercări la rând sau endpointul are mai mult de 5 000 de livrări în așteptare, MitoOps suspendă endpointul și vă trimite o notificare. Livrările în așteptare rămân; după repararea destinatarului, activați endpointul, iar ele se recuperează. Jurnalul livrărilor se păstrează 30 de zile.