Webhook in uscita
Verificato
In breve
Sezione intitolata “In breve”Il webhook è l’opposto di REST e MCP: non siete voi a chiedere, è MitoOps a farsi vivo. Quando nell’account nasce un evento a cui siete iscritti, inviamo al vostro indirizzo una POST con una breve busta firmata. La busta contiene identificatori e stati, non il contenuto dell’ordine: il dettaglio il vostro sistema lo carica tramite l’interfaccia REST con la propria chiave.
Le iscrizioni si gestiscono nell’applicazione: Impostazioni → API & MCP → Webhook in uscita. Sono disponibili dal piano Start; serve il diritto Webhook in uscita (lettura o gestione).
Iscrizione (endpoint)
Sezione intitolata “Iscrizione (endpoint)”Un endpoint ha un nome, un indirizzo, un elenco di eventi e un ambito di e-shop. Alla creazione ricevete un segreto: viene mostrato una volta sola, poi mai più; lo potete sostituire in qualsiasi momento (rotazione) e quello vecchio smette di valere immediatamente.
- L’indirizzo deve essere pubblico e raggiungibile tramite
https://sulla porta standard. Gli indirizzi della rete interna (macchina locale, intervalli privati, metadati del cloud, intervalli VPN) vengono rifiutati sia al salvataggio sia a ogni consegna: il nome viene risolto di nuovo e la connessione si lega agli indirizzi verificati. - I reindirizzamenti non vengono seguiti. Una risposta
3xxè un fallimento. - E-shop: tutti (anche quelli che si aggiungeranno) oppure selezionati. Un evento di un e-shop fuori dall’ambito non viene inviato.
- La consegna di prova dal pulsante invia l’evento
pingper la stessa via di quella reale, firma e registrazione nel registro comprese. - L’endpoint si può disattivare (le consegne in attesa vengono saltate) ed eliminare (il registro resta).
| Evento | Quando | resource.type |
Campi in data |
|---|---|---|---|
order.created |
nuovo ordine dall’e-shop | order |
order_code |
order.paid |
ordine contrassegnato come pagato | order |
order_code |
order.status_changed |
cambio di stato dell’ordine | order |
order_code, status_id, previous_status_id |
shipment.created |
spedizione creata presso il corriere | shipment |
shipment_id, order_code, carrier_code, tracking_number |
shipment.status_changed |
cambio di stato della spedizione (corriere o personale) | shipment |
shipment_id, order_code, carrier_code, status, previous_status, tracking_number |
shipment.delivered |
spedizione consegnata; in aggiunta a shipment.status_changed |
shipment |
come sopra, status è DELIVERED |
inventory.stock_changed |
movimento di scorte di un articolo | inventory_item |
item_id, sku, kind |
invoice.created |
fattura emessa | invoice |
invoice_code, order_code |
claim.created |
reclamo aperto (dal cliente o dal personale) | claim |
claim_code, order_code |
claim.status_changed |
cambio di stato del reclamo | claim |
claim_code, order_code, status_id |
claim.closed |
reclamo chiuso | claim |
claim_code, order_code |
search_console.connection.changed |
stato della connessione Search Console cambiato (collegamento, cambio sito, accesso perso, scollegamento) | search_console_property |
market, status, previous_status, property, previous_property, changed_at |
search_console.sync.completed |
la sincronizzazione dell’archivio ha aggiunto o modificato giorni | 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 |
sincronizzazione dell’archivio non riuscita | search_console_property |
market, property, error, error_code, last_successful_sync_at, retry_at |
product_content.changed |
MitoOps ha scritto nell’e-shop una descrizione, una descrizione breve o un testo SEO del prodotto modificati | 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 ha scritto nell’e-shop una descrizione superiore o inferiore o un testo SEO della categoria modificati | category |
come per product_content.changed |
ping |
consegna di prova dal pulsante | webhook_endpoint |
message, endpoint |
Gli eventi provengono dallo stesso livello commerciale delle automazioni: ciò che avvia un’automazione si può ricevere anche tramite webhook. Le spedizioni di reso non vengono inviate come shipment.*: appartengono al reclamo.
Modifica dei testi di prodotti e categorie
Sezione intitolata “Modifica dei testi di prodotti e categorie”product_content.changed e category_content.changed nascono una volta per scrittura ed e-shop quando MitoOps scrive nell’e-shop un testo effettivamente cambiato — dalla scheda prodotto, dalla modifica in blocco, dall’importazione, dalla schermata delle categorie, da una proposta approvata o da una scrittura autonoma. Una modifica di prezzo, scorte o codice e il nuovo salvataggio dello stesso testo non generano l’evento. Le modifiche fatte direttamente nell’amministrazione dell’e-shop non vengono segnalate da questo evento.
entityèproductocategory;entity_id(eresource.id) è il codice del prodotto o l’ID della categoria nell’e-shop — gli stessi con cui legge i testi tramiteproducts_content_getecategories_content_get.store_idè il codice dell’e-shop, uguale astore.source:ui(una persona nell’applicazione),assistant(l’assistente dell’applicazione),mcp(una proposta tramite MCP approvata da una persona),autonomous(una proposta scritta in modalità autonoma). Il valorerestè riservato — oggi REST è solo in lettura.source_run_id: l’ID della proposta per le scritture approvate e autonome; per le altre scritture del prodotto, l’ID del lotto di scrittura (Modifiche prodotti); per una categoria salvata fuori da una proposta,null.changed_fieldselenca i campi modificati: per il prodottodescription,shortDescription,metaTitle,metaDescription, per la categoriadescription,descriptionBottom,metaTitle,metaDescription.before_hasheafter_hashriportano per ciascuno lo SHA-256 del testo in UTF-8 (un valore mancante dà l’impronta della stringa vuota) — lo stesso valore che il campohashrestituisce in lettura. La busta non contiene il testo.- Dopo l’approvazione di una proposta,
after_hashè l’impronta di ciò che l’e-shop ha effettivamente salvato (WordPress, ad esempio, rimuove un blocco<style>), non di ciò che è stato proposto. canonical_urlè l’indirizzo pubblico del prodotto o della categoria, se MitoOps lo conosce; altrimentinull.
Cosa può leggere e proporre la sua chiave in un dato e-shop, e se una proposta verrà scritta automaticamente, lo verifica in anticipo con i criteri dei contenuti: GET /api/v1/product-content/policy?market=… o lo strumento MCP products_content_policy (ambito product-content:read).
Ogni consegna è un JSON con la versione della busta. Un nuovo campo può aggiungersi senza cambiare la versione; un cambio di significato di un campo è una nuova versione.
{ "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è univoco per l’evento. Se lo stesso evento è ricevuto da due vostri endpoint, ricevono lo stesso ID. Un nuovo tentativo porta lo stesso ID e lo stesso corpo.storeè il codice dell’e-shop (market), lo stesso usato da REST.tenantè l’identificatore del vostro account in MitoOps.
Intestazioni e firma
Sezione intitolata “Intestazioni e firma”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: 184La firma è un HMAC-SHA256 con chiave = segreto dell’endpoint sulla stringa:
<X-MitoOps-Timestamp> + "." + <corpo esatto della richiesta>Il risultato è esadecimale e nell’intestazione compare con il prefisso di versione v1=. Il timestamp è il tempo Unix in secondi.
Verifica dal vostro lato
Sezione intitolata “Verifica dal vostro lato”Verificate sul corpo grezzo della richiesta (i byte così come sono arrivati), non su un JSON serializzato di nuovo: ogni modifica di uno spazio o dell’ordine delle chiavi invalida la firma. Confrontate in tempo costante e rifiutate i messaggi più vecchi di 5 minuti.
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; // timestamp vecchio o mancante
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)Protezione dalla ripetizione
Sezione intitolata “Protezione dalla ripetizione”La firma copre il timestamp, quindi un messaggio intercettato non si può inviare più tardi con un nuovo orario. Oltre al controllo dell’orario conservate l’event_id delle ultime consegne e ignorate i duplicati: la consegna ripetuta è una caratteristica del sistema, non un errore.
Risposta, ripetizioni e registro
Sezione intitolata “Risposta, ripetizioni e registro”- Rispondete
2xxentro 10 secondi. Il corpo della risposta non viene letto. Un’elaborazione più lunga mettetela in background e rispondete subito. - Un’altra risposta (
4xx,5xx), un timeout o un errore di connessione = fallimento e ripetizione con intervalli di 1, 5, 15, 60 e 240 minuti; in totale sei tentativi. Poi la consegna èFAILEDe attende una ripetizione manuale dal registro. - Una risposta
3xxe un indirizzo che non si risolve più verso la rete pubblica sono fallimenti senza ripetizione: non si risolvono da soli. - Il Registro consegne nell’applicazione mostra per ogni tentativo lo stato, il codice HTTP, l’ora, il numero di tentativi e l’evento. Il corpo del messaggio nel registro non viene mostrato.
| Stato | Significato |
|---|---|
PENDING |
in attesa di un tentativo o della ripetizione successiva |
SENT |
il destinatario ha risposto 2xx |
FAILED |
tentativi esauriti o errore permanente; si può ripetere manualmente |
SKIPPED |
nel frattempo l’endpoint è stato disattivato o eliminato |
Sicurezza
Sezione intitolata “Sicurezza”- Il segreto viene salvato cifrato e non viene mai scritto nei log né nel registro. Viene mostrato una volta sola; la rotazione funziona anche quando il piano non include più i webhook: è un’operazione di sicurezza.
- Senza dati personali. La busta contiene identificatori e stati. Nome, indirizzo ed e-mail del cliente non vengono inviati; chi ne ha bisogno li legge tramite REST con un proprio ambito e audit.
- Solo HTTPS, solo indirizzi pubblici, senza reindirizzamenti. Il controllo al salvataggio e a ogni consegna protegge dall’instradamento verso la rete interna e da una modifica del DNS.
- L’iscrizione la crea una persona, non una chiave. Né una chiave di accesso a REST né un consenso OAuth possono creare un’iscrizione: la lettura su richiesta e un flusso permanente di eventi verso l’esterno sono due autorizzazioni diverse.
- Isolamento. Iscrizioni e registro stanno nello spazio del vostro account; l’ambito degli e-shop viene valutato a ogni evento.
| Piano | Webhook in uscita | Endpoint |
|---|---|---|
| Versione di prova | — | 0 |
| Principiante | — | 0 |
| Start | sì | 5 |
| Crescita | sì | 20 |
| Pro | sì | 50 |
| Su misura | secondo accordo | secondo accordo |
Si contano gli endpoint non eliminati, attivi e disattivati. Il passaggio a un piano inferiore non elimina gli endpoint esistenti; non se ne aggiunge uno nuovo finché il numero non scende sotto il limite, e con un piano senza webhook gli eventi smettono di essere inviati finché il piano non torna.
Webhook da un’automazione
Sezione intitolata “Webhook da un’automazione”Un endpoint si può usare anche come passaggio Invia webhook nelle Automazioni: il messaggio parte solo quando sono soddisfatte le condizioni della regola, non a ogni evento. Il passaggio si limita a selezionare l’endpoint: indirizzo, segreto e ambito degli e-shop restano nel registro.
Il messaggio ha il tipo automation.action (non si può ricevere direttamente) e la stessa busta, firma, ripetizione e registro degli altri eventi. In data porta automation.workflow_id, automation.run_id, automation.node_id, automation.source_event (l’evento che ha risvegliato la regola) e gli identificatori dell’ordine, della spedizione o del caso di reclamo; resource è l’entità dell’esecuzione. Il dettaglio lo caricate tramite la REST API.
Sospensione automatica
Sezione intitolata “Sospensione automatica”Se il destinatario non risponde per 30 tentativi consecutivi o l’endpoint ha più di 5 000 consegne in attesa, MitoOps sospende l’endpoint e vi invia un avviso. Le consegne in attesa restano; dopo aver sistemato il destinatario riattivate l’endpoint e verranno completate. Il registro delle consegne viene conservato per 30 giorni.