Към съдържанието

Изходящи webhook-ове

Проверено

Webhook е обратното на REST и MCP: не питате Вие, а MitoOps се обажда. Когато в акаунта възникне събитие, за което сте абонирани, изпращаме на Вашия адрес POST с кратък подписан плик. Пликът съдържа идентификатори и състояния, а не съдържанието на поръчката: детайла Вашата система зарежда чрез REST интерфейса със собствен ключ.

Абонаментите управлявате в приложението: Настройки → API & MCP → Изходящи webhook-ове. Налични са от тарифата Start. Нужно е правото Изходящи webhook-ове (четене или управление).

Endpoint-ът има име, адрес, списък от събития и обхват от онлайн магазини. При създаването получавате тайна: тя се показва веднъж и след това никога повече. Можете да я смените по всяко време (ротация), а старата престава да важи веднага.

  • Адресът трябва да е публичен и през https:// на стандартния порт. Адреси към вътрешна мрежа (локалната машина, частни диапазони, метаданни на облака, VPN диапазони) се отхвърлят при запазване и при всяка доставка: името се разрешава отново, а връзката се обвързва с проверените адреси.
  • Пренасочванията не се следват. Отговор 3xx е неуспех.
  • Онлайн магазини: всички (включително тези, които ще бъдат добавени) или избрани. Събитие от магазин извън обхвата не се изпраща.
  • Тестовата доставка от бутона изпраща събитието ping по същия път като реалната, включително подписа и записа в дневника.
  • Endpoint-ът може да се изключи (чакащите доставки се пропускат) и да се изтрие (дневникът остава).
Събитие Кога resource.type Полета в data
order.created нова поръчка от магазина order order_code
order.paid поръчката е отбелязана като платена order order_code
order.status_changed промяна на статуса на поръчката order order_code, status_id, previous_status_id
shipment.created пратката е създадена при куриера shipment shipment_id, order_code, carrier_code, tracking_number
shipment.status_changed промяна на състоянието на пратката (куриер или служител) shipment shipment_id, order_code, carrier_code, status, previous_status, tracking_number
shipment.delivered пратката е доставена; в допълнение към shipment.status_changed shipment както по-горе, status е DELIVERED
inventory.stock_changed движение на наличността на артикул inventory_item item_id, sku, kind
invoice.created издадена фактура invoice invoice_code, order_code
claim.created създадена рекламация (от клиента или от служител) claim claim_code, order_code
claim.status_changed промяна на статуса на рекламацията claim claim_code, order_code, status_id
claim.closed рекламацията е приключена claim claim_code, order_code
search_console.connection.changed промяна в състоянието на връзката със Search Console (свързване, смяна на сайта, загубен достъп, прекъсване) search_console_property market, status, previous_status, property, previous_property, changed_at
search_console.sync.completed синхронизацията на архива добави или промени дни 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 синхронизацията на архива е неуспешна search_console_property market, property, error, error_code, last_successful_sync_at, retry_at
product_content.changed MitoOps записа в магазина променено описание, кратко описание или SEO текст на продукт 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 записа в магазина променено горно или долно описание или SEO текст на категория category както при product_content.changed
ping тестова доставка от бутона webhook_endpoint message, endpoint

Събитията идват от същия бизнес слой като автоматизациите: това, което стартира автоматизация, може да се абонира и чрез webhook. Обратните пратки (връщания) не се изпращат като shipment.*: те принадлежат към рекламацията.

Промяна на текстовете на продукти и категории

Раздел „Промяна на текстовете на продукти и категории“

product_content.changed и category_content.changed възникват веднъж на запис и магазин, когато MitoOps запише в магазина текст, който наистина се е променил — от картата на продукта, масовата редакция, импорта, екрана с категории, от одобрено предложение или от автономен запис. Промяна на цена, наличност или код, както и повторното запазване на същия текст, не предизвикват събитие. Промени, направени директно в администрацията на магазина, това събитие не съобщава.

  • entity е product или category; entity_id (и resource.id) е кодът на продукта, съответно ID на категорията в магазина — същите, с които четете текстовете чрез products_content_get и categories_content_get. store_id е кодът на магазина, същият като store.
  • source: ui (човек в приложението), assistant (асистентът в приложението), mcp (предложение през MCP, одобрено от човек), autonomous (предложение, записано в автономен режим). Стойността rest е запазена — днес REST е само за четене.
  • source_run_id: ID на предложението при одобрен и автономен запис; при другите записи на продукт — ID на пакета запис (Промени на продукти); при категория, запазена извън предложение — null.
  • changed_fields изброява променените полета: за продукт description, shortDescription, metaTitle, metaDescription, за категория description, descriptionBottom, metaTitle, metaDescription. before_hash и after_hash съдържат за всяко от тях SHA-256 на текста в UTF-8 (липсваща стойност = отпечатъкът на празния низ) — същата стойност, която полето hash връща при четене. Самия текст пликът не съдържа.
  • След одобрение на предложение after_hash е отпечатъкът на това, което магазинът действително е съхранил (WordPress например премахва блок <style>), а не на предложеното.
  • canonical_url е публичният адрес на продукта или категорията, ако MitoOps го знае; иначе null.

Какво може да чете и предлага вашият ключ в даден магазин и дали предложението ще се запише само, проверявате предварително чрез политиката за съдържание: GET /api/v1/product-content/policy?market=… или инструмента MCP products_content_policy (обхват product-content:read).

Всяка доставка е JSON с версия на плика. Ново поле може да бъде добавено без промяна на версията. Промяната на значението на поле е нова версия.

{
"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 е уникален за събитието. Ако едно и също събитие се абонира от два Ваши endpoint-а, те получават едно и също ID. Повторният опит носи същото ID и същото тяло.
  • store е кодът на магазина (market): същият, който използва REST.
  • tenant е идентификаторът на Вашия акаунт в 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

Подписът е HMAC-SHA256 с ключ = тайната на endpoint-а върху низа:

<X-MitoOps-Timestamp> + "." + <точното тяло на заявката>

Резултатът е шестнадесетичен и стои в заглавката с префикс на версията v1=. Времевият печат е Unix време в секунди.

Проверка от Ваша страна

Раздел „Проверка от Ваша страна“

Проверявайте върху суровото тяло на заявката (байтовете така, както са пристигнали), а не върху отново сериализиран JSON: всяка промяна на интервал или ред на ключовете обезсилва подписа. Сравнявайте в константно време и отхвърляйте съобщения, по-стари от 5 минути.

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; // стар или липсващ времеви печат
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)

Защита срещу повторно изпращане

Раздел „Защита срещу повторно изпращане“

Подписът покрива времевия печат, така че прихванато съобщение не може по-късно да бъде изпратено с ново време. Освен проверката на времето съхранявайте event_id на последните доставки и игнорирайте дубликатите: повторната доставка е свойство на системата, а не грешка.

Отговор, повторни опити и дневник

Раздел „Отговор, повторни опити и дневник“
  • Отговорете с 2xx до 10 секунди. Тялото на отговора не се чете. Обработка, която отнема повече време, поставете на заден план и отговорете веднага.
  • Друг отговор (4xx, 5xx), изтичане на времето или грешка при свързване = неуспех и повторен опит с интервали от 1, 5, 15, 60 и 240 минути, общо шест опита. След това доставката е FAILED и чака ръчно повторение от дневника.
  • Отговор 3xx и адрес, който е престанал да се разрешава към публична мрежа, са неуспехи без повторен опит: те не се решават сами.
  • Дневникът на доставките в приложението показва за всеки опит състоянието, HTTP кода, часа, броя опити и събитието. Тялото на съобщението не се показва в дневника.
Състояние Значение
PENDING чака опит или следващ повторен опит
SENT получателят е отговорил с 2xx
FAILED опитите са изчерпани или грешката е трайна; може да се повтори ръчно
SKIPPED endpoint-ът междувременно е изключен или изтрит
  • Тайната се съхранява криптирана и никога не се записва в логове или в дневника. Показва се веднъж. Ротацията работи дори когато тарифата вече не включва webhook-ове: това е операция по сигурността.
  • Без лични данни. Пликът съдържа идентификатори и състояния. Име, адрес или имейл на клиента не се изпращат. Който се нуждае от тях, ги чете чрез REST със собствен обхват и одит.
  • Само HTTPS, само публични адреси, без пренасочвания. Проверката при запазване и при всяка доставка защитава от насочване към вътрешната мрежа и от промяна на DNS.
  • Абонаментът се създава от човек, а не от ключ. Нито ключ за достъп до REST, нито съгласие чрез OAuth може да създаде абонамент: четенето при поискване и постоянният поток от събития навън са две различни права.
  • Изолация. Абонаментите и дневникът са в пространството на Вашия акаунт. Обхватът от магазини се оценява при всяко събитие.
Тарифа Изходящи webhook-ове Endpoint-и
Пробна не 0
Начинаещ не 0
Start да 5
Растеж да 20
Pro да 50
По поръчка по договаряне по договаряне

Броят се неизтритите endpoint-и: включени и изключени. Понижаването на тарифата не изтрива съществуващите endpoint-и. Нов не може да бъде добавен, докато броят не спадне под лимита, а при тарифа без webhook-ове събитията престават да се изпращат, докато тарифата не бъде върната.

Webhook от автоматизация

Раздел „Webhook от автоматизация“

Endpoint-а можете да използвате и като стъпка Изпрати webhook в Автоматизации: съобщението се изпраща едва след изпълнение на условията на правилото, а не при всяко събитие. Стъпката само избира endpoint-а: адресът, тайната и обхватът от магазини остават в регистъра.

Съобщението има тип automation.action (не може да бъде абонирано директно) и същия плик, подпис, повторни опити и дневник като другите събития. В data то носи automation.workflow_id, automation.run_id, automation.node_id, automation.source_event (събитието, което е събудило правилото) и идентификатори на поръчката, пратката или рекламационния случай. resource е обектът на изпълнението. Детайла зареждате чрез REST API.

Автоматично поставяне на пауза

Раздел „Автоматично поставяне на пауза“

Ако получателят не отговори на 30 поредни опита или endpoint-ът има повече от 5 000 чакащи доставки, MitoOps поставя endpoint-а на пауза и Ви изпраща известие. Чакащите доставки остават. След поправка на получателя включете endpoint-а и те ще бъдат доставени. Дневникът на доставките се пази 30 дни.