Изходящи webhook-ове
Проверено
Накратко
Раздел „Накратко“Webhook е обратното на REST и MCP: не питате Вие, а MitoOps се обажда. Когато в акаунта възникне събитие, за което сте абонирани, изпращаме на Вашия адрес POST с кратък подписан плик. Пликът съдържа идентификатори и състояния, а не съдържанието на поръчката: детайла Вашата система зарежда чрез REST интерфейса със собствен ключ.
Абонаментите управлявате в приложението: Настройки → API & MCP → Изходящи webhook-ове. Налични са от тарифата Start. Нужно е правото Изходящи webhook-ове (четене или управление).
Абонамент (endpoint)
Раздел „Абонамент (endpoint)“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/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: 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 дни.