Salta ai contenuti

Webhook in uscita

Verificato

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).

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 ping per 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.

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 è product o category; entity_id (e resource.id) è il codice del prodotto o l’ID della categoria nell’e-shop — gli stessi con cui legge i testi tramite products_content_get e categories_content_get. store_id è il codice dell’e-shop, uguale a store.
  • 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 valore rest è 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_fields elenca i campi modificati: per il prodotto description, shortDescription, metaTitle, metaDescription, per la categoria description, descriptionBottom, metaTitle, metaDescription. before_hash e after_hash riportano per ciascuno lo SHA-256 del testo in UTF-8 (un valore mancante dà l’impronta della stringa vuota) — lo stesso valore che il campo hash restituisce 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; altrimenti null.

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.
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

La 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.

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)

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.

  • Rispondete 2xx entro 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 è FAILED e attende una ripetizione manuale dal registro.
  • Una risposta 3xx e 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
  • 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.

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.

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.