Как заставить Event Rule NetBox срабатывать только при смене статуса, а не при каждом сохранении
Если webhook в NetBox срабатывает на каждое сохранение устройства, хотя условие проверяет только статус — вы столкнулись не с багом, а с тем, как вообще устроены условия Event Rule: они проверяют состояние объекта на момент события, а не факт изменения конкретного поля. В NetBox 4.7 для этого добавили честные операторы changed и unchanged — разбираю, как их использовать правильно.
Почему условие status=active стреляет при любом сохранении
Классическая ошибка при настройке Event Rule в NetBox — завести правило с условием вида {"attr": "status.value", "value": "active"} и ожидать, что webhook сработает именно в момент, когда устройство переходит в статус active. На деле условие проверяет текущее состояние объекта в момент события update, а не то, изменилось ли конкретное поле. Если устройство уже находится в active и администратор просто поправил, скажем, серийный номер или описание, событие update всё равно произойдёт, условие status.value == active всё равно останется истинным — и webhook снова уйдёт, хотя по смыслу задачи ничего важного не изменилось.
Для инфраструктуры, где автоматизация на базе NetBox через webhook запускает реальные действия — регистрацию в системе мониторинга Zabbix, создание задачи в service desk, синхронизацию с внешней CMDB — это не косметическая проблема. Повторные срабатывания создают шум в интеграции и в худшем случае многократно запускают операции над рабочей сетью, которые должны были выполниться один раз, в момент реального перехода состояния.
Мейнтейнеры NetBox в подобных обсуждениях (например, в GitHub Discussion #21492, февраль 2026) прямо формулируют это ограничение простыми словами: условие оценивается относительно состояния объекта в момент события update, оно не означает «сработать только при изменении конкретного поля». Это не баг и не недоработка — так изначально была спроектирована система условий, рассчитанная на проверку состояния, а не на отслеживание дельты. Понимание этой границы и привело к тому, что в NetBox отдельно появились snapshot-операторы, о которых я расскажу дальше — именно они закрывают разрыв между «текущее состояние» и «то, что реально изменилось». Такое же разграничение «текущее состояние» и «событие изменения» я держу в голове, когда настраиваю триггеры в Zabbix 7.0 LTS: там точно так же легко перепутать условие на пороговое значение с условием на переход через это значение и получить лавину одинаковых уведомлений вместо одного полезного.
Как устроены условия Event Rule и откуда взялась путаница
Условие в NetBox — это JSON-объект с ключами attr (какой атрибут проверяем), value (эталонное значение для сравнения), необязательным op (оператор сравнения, по умолчанию eq) и необязательным negate (инверсия результата). Стандартные операторы — eq, gt, gte, lt, lte, in, contains и появившийся тоже в 4.7 regex — сравнивают текущие данные объекта с заданным value. Ни один из них по своей природе не умеет отвечать на вопрос «а изменилось ли это значение только что» — они смотрят только на текущее состояние.
До NetBox 4.7 условия Event Rule вообще не видели предыдущего состояния объекта — они оценивались только по текущим данным. В том самом обсуждении #21492 на GitHub пользователь пытался собрать условие {"and": [{"attr": "status.value", "value": "active"}, {"attr": "snapshots.prechange.status.value", "value": "inventory"}]} — «текущий статус active, а был inventory» — и выяснил, что в условиях снапшоты не поддерживаются. Мейнтейнер предложил единственный рабочий на тот момент путь: принимать все события update, а сравнение делать на стороне приёмника. В теле webhook NetBox и раньше передавал блок snapshots с ключами prechange и postchange, так что скрипт-получатель мог сам сравнить статус «до» и «после» и молча выбросить лишние события.
Недостатки такого обходного пути очевидны: логику фильтрации приходится писать и поддерживать в каждом получателе отдельно, NetBox всё равно ставит в очередь и отправляет каждый лишний webhook, а если получатель — готовый сервис без возможности вставить свой код (service desk, мессенджер, система учёта), фильтровать просто негде. Плюс к этому у устройства в DCIM NetBox семь статусов (offline, active, planned, staged, failed, inventory, decommissioning), и объект может попасть в active практически из любого из них — любая самописная фильтрация на приёмнике быстро обрастает частными случаями. Именно эту дыру закрыли в 4.7: сравнение снапшотов переехало прямо в условия правила.
Операторы changed и unchanged в NetBox 4.7
NetBox 4.7.0 (release notes датированы 2 сентября 2026 года) добавил в условия Event Rule два новых snapshot-оператора — changed и unchanged, а заодно синтаксис snapshots.prechange.<attr> и snapshots.postchange.<attr>, которым любой стандартный оператор может прочитать значение «до» или «после». Операторы changed/unchanged сравнивают значение атрибута между двумя снапшотами напрямую, одним условием. Ключевая особенность, которую легко упустить: операторы changed и unchanged **не принимают** ключ value — они не сравнивают атрибут с конкретным эталоном, они сравнивают его сам с собой в двух состояниях.
Условие для «webhook срабатывает только когда статус реально поменялся именно на active» теперь собирается так:
{
"and": [
{"attr": "status.value", "value": "active"},
{"attr": "status", "op": "changed"}
]
}Первое условие проверяет текущее состояние (status.value == active), второе — оператором changed — требует, чтобы поле status действительно отличалось между до и после. Вместе они означают ровно то, что нужно: сработать только в момент перехода в active, а не при любом сохранении устройства, которое уже находится в этом статусе. Одна оговорка из документации: для события создания объекта снапшота prechange нет, и changed считается истинным для любого атрибута. Если правило подписано ещё и на создание объектов, то устройство, заведённое сразу в статусе active, тоже вызовет webhook — чаще всего это как раз то, что нужно, но об этом стоит помнить.
Почему в snapshot нельзя использовать .value так же, как в обычных условиях
Снапшоты в NetBox хранятся в формате сериализатора модели, а не в формате REST API — это важное и не всегда очевидное отличие. В обычном условии, которое сравнивается с текущим объектом через REST-представление, поле выбора (choice field) вроде статуса выглядит как вложенный объект {"value": "active", "label": "Active"}, поэтому путь к нему пишут как status.value. А в снапшотах то же самое поле представлено просто строкой "active" без вложенности — значит, путь для операторов снапшота нужно писать как status, без .value на конце.
Если по ошибке написать {"attr": "status.value", "op": "changed"} вместо {"attr": "status", "op": "changed"}, правило не упадёт при сохранении — JSON валиден, форма его примет. Но суффикс .value в снапшоте не разрешается, условие по документации «fails closed»: правило не срабатывает, а в Python-логгер netbox.event_rules пишется ошибка. Никакого отдельного журнала в веб-интерфейсе для этого нет — ошибку видно только в логах сервера NetBox (там, куда у вас настроен LOGGING), поэтому, если webhook после правки условия замолчал, первым делом смотрю именно туда. negate такую ошибку не «переворачивает» в совпадение — неразрешимый путь остаётся несрабатыванием.
Это же различие путей касается и других choice-полей объекта, не только статуса — роли устройства, платформы или, скажем, статуса IP-адреса. Правило простое и одинаковое для всех: если условие относится к текущему состоянию объекта — используйте формат REST API с .value; если условие использует changed/unchanged или прямо обращается к snapshots.prechange/snapshots.postchange — путь к choice-полю пишется без .value, потому что снапшот сериализует поле как обычную строку.
Как выбрать между changed, unchanged и старым способом через snapshots.prechange
Оператор changed подходит, когда важен сам факт перехода атрибута в нужное состояние, а не то, из какого именно состояния он пришёл — например, «уведомить, когда устройство стало active», независимо от того, было оно до этого planned, offline или staged. Это самый частый и самый правильный случай использования.
Прямое обращение к снапшоту через snapshots.prechange.status (именно без .value) нужно, если важен конкретный переход именно между двумя названными статусами — скажем, отличать «offline → active» (реальный ввод в эксплуатацию) от «staged → active» (плановое обновление уже работающего оборудования), и реагировать на них по-разному. В таком случае changed слишком грубый — он не различает, откуда именно пришло значение, поэтому условие собирается так: {"and": [{"attr": "status.value", "value": "active"}, {"attr": "snapshots.prechange.status", "value": "offline"}]}.
Оператор unchanged, обратный changed, полезен в противоположном сценарии: например, правило должно сработать при сохранении объекта, только если статус НЕ поменялся, а изменилось что-то другое — это нужно реже, но встречается в сценариях аудита конфигурации, когда важно отследить правки атрибутов без смены жизненного цикла объекта.
На практике я советую клиентам не пытаться с первого раза угадать идеальное условие, а проверять правило на тестовом объекте сразу после настройки: меняю статус в интерфейсе NetBox туда и обратно, правлю посторонние поля и смотрю на тестовом приёмнике webhook, какие запросы пришли, а в логе netbox.event_rules — нет ли ошибок разбора условия. Пять минут ручной проверки на тестовом устройстве экономят часы разбора задваивающихся задач в внешней системе спустя неделю — примерно так же, как я проверяю каждую новую интеграцию Zammad с Active Directory и почтой на тестовом тикете перед тем, как включать её на боевой очереди обращений.
Кейс: охотничий магазин «ОхотГрад», webhook, который дублировал заявки в 1С
В охотничьем магазине «ОхотГрад» (14 рабочих мест) NetBox использовался для учёта кассового и сетевого оборудования точек продаж. Event Rule на изменение устройства с условием status.value == active был настроен на отправку webhook во внутренний сервис, который создавал задачу в 1С на выезд инженера для финальной настройки нового оборудования на точке. Схема выглядела логично на бумаге: устройство завели в NetBox со статусом planned, инженер привёз и подключил кассу, перевёл статус в active — и по этому событию должна была прилетать одна-единственная задача на первичную настройку.
Проблема вскрылась не сразу: администратор магазина периодически правил в NetBox серийные номера и инвентарные метки уже работающих касс — банальная актуализация данных, статус при этом не менялся, оставался active. Но каждое такое сохранение всё равно проходило по условию status.value == active и заново отправляло webhook, а сервис на другом конце снова создавал задачу «настроить оборудование на точке», хотя оборудование давно настроено и работает. За месяц таким образом накопилось больше десятка дублирующих задач, которые инженеры закрывали вручную как ошибочные.
Решение заняло один вечер: я обновил условие правила до {"and": [{"attr": "status.value", "value": "active"}, {"attr": "status", "op": "changed"}]}, используя оператор changed, доступный после обновления магазина до NetBox 4.7. После этого webhook стал приходить ровно один раз — в момент, когда устройство реально переходит в active, а не при каждой правке карточки уже работающей кассы. За две недели после изменения ни одной дублирующей задачи в 1С не появилось, притом что администратор продолжил так же часто редактировать инвентарные данные оборудования.
Отдельно порекомендовал администратору магазина завести такое же условие с changed для остальных правил, которые он настраивал по образцу первого — до этого случая в NetBox было ещё два похожих Event Rule на других объектах (сетевое оборудование точек и POS-терминалы), скопированных с тем же условием без changed. Я проверил оба и в одном из них нашёл ту же проблему на ранней стадии — webhook туда шёл в сервис уведомлений в Telegram, просто дублирующиеся уведомления никто не воспринимал как баг, списывая на «шумный NetBox». Заодно сверил учёт самого сетевого оборудования магазина в NetBox с реальным парком — это отдельная задача, но она у меня обычно идёт в комплекте с документированием инфраструктуры на NetBox для небольшой компании, если клиент уже завёл систему, но не довёл структуру объектов до конца.
Частые вопросы
Почему webhook в NetBox срабатывает на каждое сохранение, а не только на смену статуса?
Потому что обычное условие проверяет текущее состояние объекта на момент события, а не то, изменилось ли конкретное поле. Если объект уже в нужном статусе, любое его сохранение — даже правка другого поля — снова удовлетворяет условию.
Какой оператор нужен, чтобы условие сработало только на реальную смену поля?
changed — он сравнивает значение атрибута в снапшотах до и после изменения. В паре с обычным условием на нужное значение он даёт срабатывание именно на переход в конкретный статус.
Можно ли передать value вместе с оператором changed?
Нет, операторы changed и unchanged не принимают ключ value — они не сравнивают атрибут с эталоном, а проверяют сам факт изменения (или неизменности) между pre-change и post-change снапшотами.
Почему в условии для changed путь status, а не status.value?
Снапшоты хранятся в формате сериализатора модели, а не REST API: choice-поля вроде статуса представлены там простой строкой, без вложенного объекта value/label. Путь .value относится только к обычным условиям над текущим объектом.
С какой версии NetBox доступны операторы changed и unchanged?
С версии 4.7.0 (release notes от 2 сентября 2026 года). До неё условия Event Rule не видели предыдущего состояния объекта: сравнивать snapshots.prechange и postchange из тела webhook приходилось на стороне приёмника.
Когда лучше использовать snapshots.prechange вместо changed?
Когда важен конкретный переход между двумя названными статусами, а не любое изменение — например, отличить offline → active от staged → active. Путь пишется без .value: snapshots.prechange.status. changed не различает, из какого состояния пришло значение.
Источники
- NetBox Labs Docs — Conditions (reference/conditions) — Проверено: синтаксис условий (attr, value, op, negate), список стандартных операторов, snapshot-операторы changed/unchanged без value, разница путей status.value (REST) и status (снапшот), пример условия перехода в active, fail closed и запись ошибки в логгер netbox.event_rules, поведение changed для событий создания/удаления. https://netboxlabs.com/docs/netbox/reference/conditions/
- NetBox Labs Docs — Event Rules (models/extras/eventrule) — Проверено: типы событий (создание/изменение/удаление объекта, старт/завершение/ошибка фонового задания), типы действий (webhook, скрипт, уведомление), поведение поля conditions при отсутствии условий. https://netboxlabs.com/docs/netbox/models/extras/eventrule/
- NetBox Release Notes v4.7 (netboxlabs.com/docs) — Проверено: v4.7.0 от 2026-09-02, snapshot-условия (changed/unchanged, snapshots.prechange/postchange), новый оператор regex, fail closed с логированием ошибки для неразрешимых атрибутов. https://netboxlabs.com/docs/netbox/release-notes/version-4.7/
- GitHub netbox-community/netbox — Discussion #21492, Event rule for object update always gets triggered — Проверено: кейс ложных срабатываний правила на update, ответ мейнтейнера (условие оценивается по состоянию объекта, а не по факту изменения; до 4.7 сравнивать prechange/postchange из тела webhook на стороне приёмника), неработающая попытка snapshots.prechange.status.value. https://github.com/netbox-community/netbox/discussions/21492
- GitHub netbox-community/netbox — исходный код choices (dcim/choices.py, ветка main) — Проверено: полный список статусов устройства DeviceStatusChoices — offline, active, planned, staged, failed, inventory, decommissioning. https://github.com/netbox-community/netbox/blob/main/netbox/dcim/choices.py



