Неделя 144. Замкнули цикл: лендинг → CRM

Неделя 144. Замкнули цикл: лендинг → CRM

Разбор этой статьи

AI-подкаст BotsellerПочему Botseller чинит фундамент вместо фасада
0:00 / 0:00

Эту тему разобрали в подкасте. Слушай параллельно с чтением.

Это сто сорок четвёртая неделя с момента, как мы запустили Botseller в августе 2023 года. Прошлый понедельник я писал про мульти-доменный переезд на botseller.ru - один продукт, две юрисдикции, одна кодовая база. Эта неделя стала логическим продолжением: мы вернулись к ядру и закрыли давнюю архитектурную дыру. До понедельника лендинг botseller.ai и наша CRM жили как два отдельных мира. Визиты не трекались, формы заявок уходили в WhatsApp руками, а в карточке лида не было ни одной строки про то, откуда он пришёл и какие страницы читал. За семь дней мы это починили.

Меня зовут Дмитрий Дьяконов, я основатель и CEO Botseller AI. Если читаете журнал впервые - короткий контекст: мы делаем SaaS-платформу с ИИ-продавцом, CRM и рассылками в 15 мессенджерах, а рядом стоят Botseller Academy и Clubator. Каждый понедельник я пишу честный лог: что смержили в main, что сломали, какие гипотезы проверили. Поехали по 144-й.

Пульс недели: что в цифрах

Инженерный пульс 144-й недели Botseller: 80+ feature-веток в восьми репозиториях с лидером в виде альтернативного режима

Цифры для тех, кто любит цифры. За семь дней через main в восьми репозиториях прошло около 80 содержательных feature-веток. Темп ощутимо выше, чем на 143-й (там было ~45), и причина та же, по которой 143-я была ниже 142-й: на прошлой неделе одна большая архитектурная задача (мульти-домен) съела весь параллелизм. На этой неделе мульти-домен закрыт, и команда снова распараллелилась по продуктовым направлениям.

НаправлениеMerged ветокОсновной фокус недели
Альтернативный режим (4 слоя стека)35Таблицы расписания + бизнес-логика чтения часов в нижнем слое, 3 PR на платформенный API, Tabs Основной/Альтернативный + ScheduleSheet + mode-aware drag-drop, фикс мессенджера Макс
Атрибуция трафика и Pixel-трекер13customer_touchpoints + tracking endpoints, page_view, sticky pixel, cross-subdomain cookies
Редизайн редактора автоматизаций17condition-нода, init_dialog, DAG с orphan detection, atomic ops, dot-grid canvas, bezier edges
Конструктор веб-форм84 итерации UI, 3 способа встраивания, redirect в мессенджер, антиспам, JSONB-расширение
Карточка лида: таб «Маршрут»4TouchpointTimeline, TrafficSourceCard двухуровневый, UI «Трекер сайта» с проверкой установки
Clubator: биллинговая стабилизация6Накопление access_until, fallback subscription_id, tz-сравнение, статус «Расхождение»
Лендинг и блог: контент и перформанс15+Две новые статьи, эпизод 15 подкаста, CSP под трекер, 410 Gone, self-host шрифтов

Главное смещение по сравнению с предыдущей неделей - команда вернулась к продуктовым фичам внутри платформы. Мульти-домен дал маркетинговую базу, теперь мы возвращаем долг по тем местам, которые годами откладывали: трекинг трафика, разделение режимов работы бота, нормальный редактор автоматизаций. Это менее эффектно, чем «один продукт, два домена», но даёт больше прироста выручки на единицу ресурса.

Лид теперь не из ниоткуда: атрибуция трафика и Маршрут

Проблема разорванного контекста между лендингом и CRM: слепой трафик, ручная фиксация заявок в WhatsApp, лиды без биографии

Главная фича недели - мы развернули внутри Botseller собственный пиксель сайта (мы называем его Botseller Tracker) и встроили его во все четыре наших публичных приложения: лендинг, блог, документацию и Academy. Технически это маленький JS-скрипт pixel.js, который грузится на каждой странице, ставит cookie на уровне второго домена (.botseller.ai), отправляет события page_view на эндпоинт /touchpoints/track в CRM и пишет каждое касание в новую таблицу customer_touchpoints_table. На стороне CRM это превращается в полноценный таймлайн касаний на одного посетителя: что зашёл из контекстной рекламы Яндекса, прочёл вот эту статью блога, перешёл на калькулятор, потом на страницу контактов, и только после этого оставил заявку.

Botseller Tracker как собственный пиксель экосистемы: триггер pixel.js, идентификация через cookie_domain, маршрутизация в touchpoints, синтез связи с lead_id

Зачем нам этот трекер, если есть Яндекс.Метрика и Google Analytics. Метрика про агрегаты: воронки, источники, средние конверсии. Когда вы продаёте SaaS со средним чеком от десятков тысяч рублей, агрегаты вам не помогают - вам нужна история конкретного человека, который сидит сейчас в карточке лида у менеджера. Метрика так не умеет: её события живут в её базе, а не у вас, и вы не можете показать менеджеру, что этот лид заходил восемь раз и читал статью про сравнение тарифов. Наш трекер ровно про это: каждое касание привязано к visitor_id, потом visitor_id связывается с lead_id, и менеджер видит биографию лида внутри CRM, а не в чужом аналитическом интерфейсе.

Сравнение Яндекс.Метрики и Botseller Tracker: агрегированная аналитика против индивидуальной биографии лида прямо в карточке CRM

Архитектурный разбор того, как мы устроили эту связку, я выложил отдельной статьёй - «Как Botseller отслеживает путь клиента». Там подробно про visitor_id-cookie, JSONB-хранилище касаний, дедупликацию событий, защиту от спама и UTM-парсинг. Здесь же опишу только инженерные подводные камни недели.

Четыре инженерные ловушки на пути к чистому трекингу: cross-subdomain cookies, real IP за nginx-прокси, SPA routing через History API, data hardening JSONB-базы

Первый - cross-subdomain cookies. Лендинг живёт на botseller.ai, блог - на botseller.ai/blog, документация - на botseller.ai/docs, академия - на botseller.ai/academy. На третьих доменах (cdn.botseller.ai, app.botseller.ai) у нас живут CDN и платформа. Cookie должен быть один на всю экосистему, иначе один и тот же посетитель будет считаться четырьмя разными людьми. Решили через явный cookie_domain=.botseller.ai при выставлении cookie - тогда он шарится между поддоменами. Звучит просто, но в реальности там пять мест в коде, где cookie могло выставиться без правильного domain, и все пять надо было найти и поправить.

Второй - real client IP за reverse-proxy. Botseller сидит за nginx, поэтому изначально все события /touchpoints/track приходили с IP nginx-контейнера, а не реального посетителя. Это убивает геолокацию и обнаружение ботов. Поправили: nginx прокидывает X-Forwarded-For, а CRM аккуратно читает первый IP из цепочки. Параллельно сделали HTTPS scheme detection через X-Forwarded-Proto - без него pixel.js за reverse-proxy формирует свои запросы как http://, и браузер их режет mixed-content политикой.

Третий - SPA routing tracking. У нас лендинг это Next.js, и переходы между страницами происходят без полной перезагрузки. Стандартный pixel.js видит только первый page_view - дальше браузер не перезагружает страницу, скрипт молчит. Перехватили History API (pushState, replaceState, popstate) - теперь каждый SPA-переход регистрируется как отдельный page_view с правильным page_title и page_path.

Четвёртый - production hardening. На втором дне работы мы поймали: один трафик-всплеск на статью в блоге дал неожиданный рост размера записей в attribution_data (там лежит JSONB с UTM, referrer, user-agent и прочим). Поставили жёсткий size cap на attribution_data плюс дедупликацию событий на стороне pixel.js, чтобы один и тот же page_view не отправлялся дважды при быстром клике. Скучно, но без этого через месяц у нас была бы база на 100+ ГБ из мусорных событий.

Пятый - связка visitor → lead. У нас есть случаи, когда один visitor оставляет несколько заявок (например, через несколько мессенджеров - сначала WhatsApp, потом Telegram, потом форму). Раньше второй и третий лиды от того же visitor получали странные привязки. Сделали два механизма: force_lead_id для multi-lead per visitor и автоматическое наследование lead_id для пост-лидовых page_view. Теперь история касаний правильно расщепляется на лиды.

Таб «Маршрут» в карточке лида: двухуровневая TrafficSourceCard с источником и UTM-метками плюс TouchpointTimeline с хронологическими касаниями

И на стороне CRM. В карточке лида появился новый таб «Маршрут» с двумя компонентами: TouchpointTimeline (хронологический список касаний с метаданными) и TrafficSourceCard (двухуровневый collapsible с разбором источника, кампании, ключевого слова). Двухуровневый - значит сверху одна короткая строка вроде «Яндекс. Платная реклама. Кампания: brand-search», а под ней по клику разворачивается детализация с tooltips. Это сделано специально для маркетолога, который не хочет видеть простыню UTM-параметров, но иногда хочет копнуть. И в настройках CRM появилась отдельная страница «Трекер сайта» с автоматической проверкой того, что pixel.js установлен и события доходят: можно вставить домен, нажать «Проверить установку» и сразу увидеть, работает ли.

Последняя ловушка, которая стоила нескольких часов отладки - правильное определение «прямого перехода». Раньше при навигации внутри лендинга (с главной на статью блога, например) мы получали referrer вида botseller.ai/, и наш трекер честно классифицировал это как «реферальный трафик с botseller.ai», что бессмыслица. Поправили: если referrer hostname совпадает с landing hostname - это «Прямой переход», а не реферал. Это та самая работа, которую видишь, только когда открываешь свою же статистику и задаёшься вопросом «почему 30% трафика помечено как пришедшее с самого себя».

Конструктор веб-форм: четыре итерации до production

Конструктор веб-форм Botseller v4: привязка select-полей к backend-enum-справочникам CRM устраняет грязные дубли «Авито/авито/AVITO» в источниках лидов

Параллельно с трекером мы доделали конструктор веб-форм - визуальный редактор, где можно собрать форму заявки, забрендировать её под свой сайт и встроить тремя способами: iframe, JS-сниппет или прямой редирект. До этой недели у нас был v1, на котором можно было собрать что-то техническое, но с UX «программист мечтал быть дизайнером». За неделю прошло четыре итерации: цветовая тема, заголовок, скругление, фон, redirect после submit, markdown-ссылки в подписях полей, позиция подписи (сверху/слева). На последней - v4 - мы привели форму в production-ready состояние: добавили rate-limit на сабмит, CORS-заголовки, тесты, trim входных данных, антиспам-фильтрацию.

Главная фича конструктора - привязка selects к backend enum’ам. У нас в CRM есть набор справочников: статусы лидов, источники, типы услуг, мессенджеры. Раньше при создании select-поля в форме оператор копировал значения вручную - и мы получали лиды с источником «Авито», «авито», «Avito» и «AVITO» в одной базе. Теперь поле select можно привязать к enum в backend - и значения подтянутся автоматически плюс будут оставаться синхронизированными при изменении enum’а на стороне платформы.

Бесшовный handoff из веб-формы в мессенджер: заявка на сайте, генерация lead_id в CRM, мгновенный редирект в WhatsApp с сохранением контекста

Вторая важная мелочь - редирект в мессенджер после submit с подстановкой {lead_id}. Юзкейс простой: посетитель оставляет заявку, мы создаём лид, генерируем уникальный URL вида https://wa.me/...?text=Hi,+I+left+a+request,+lead_id={lead_id} и редиректим туда. WhatsApp-чат открывается с предзаполненным сообщением, и через одну секунду менеджер уже видит обращение в CRM, привязанное к конкретному лиду. Это даёт два эффекта сразу: посетитель попадает в живой канал коммуникации (где конверсия в продажу выше, чем у формы), и менеджер видит контекст, а не «здравствуйте, расскажите подробнее».

И последнее - мы интегрировали свою же форму на botseller.ai/contacts. До этой недели у нас стояла самописная форма, которая улетала в WhatsApp-чат. Сейчас она же построена на конструкторе и улетает в CRM как полноценный лид с meta-атрибуцией от pixel.js. Поверх пришлось накатить три правки: расширить CSP (Content Security Policy) под Botseller Tracker, добавить fallback на дев-домен CRM на случай прод-инцидентов и поправить webform endpoint, чтобы корректно работала схема за nginx-прокси. Сейчас если читать эту статью с botseller.ai и зайти на страницу контактов - заявка уходит уже через свой собственный конструктор форм. Это маленькое eat your own dogfood, и оно много рассказывает про зрелость продукта: пока мы сами не пользуемся своими инструментами, нельзя продавать их клиентам.

Если вы теряете заявки на сайте и не можете понять, на каком этапе - проблема почти всегда не в форме, а в склейке между формой и CRM. Этот цикл мы наконец замкнули у себя.

Альтернативный режим: четыре слоя одной фичи

Смена парадигмы расписания бота: альтернативный режим как полноценная вторая воронка где днём работают сотрудники, а ночью бот выходит на смену и ловит заявки

Это одна из главных фич недели и фактически второй флагман после атрибуции трафика. Я выношу её отдельной большой главой, потому что у задачи специфичная архитектурная форма: четыре слоя стека, каждый со своим инженером и своими подводными камнями. Без любого из этих слоёв фича не работает, и собирали мы её всю неделю одной слаженной командой.

Сначала про мотивацию. Альтернативный режим - это запрос клиентов, который мы накопили за последние полгода и никак не могли запустить. Суть в одном предложении: в рабочее время сотрудники ведут чаты сами, а бот отдыхает. После рабочего дня (или в выходные, или в отпуск) бот включается и работает за людей. Это противоположно тому, что обычно думают про «расписание бота» - не «бот молчит ночью», а ровно наоборот: «бот молчит днём, когда люди в офисе, и работает ночью, когда людей нет». Юзкейс из реальных запросов: салон красоты с двумя администраторами, которые сами пишут клиентам с 10 до 20. Они не хотят, чтобы бот вмешивался и опережал их с автоответами. Но в 20:00 они уходят домой, и до 10:00 утра бот ловит ночные заявки сам - проводит квалификацию, отвечает на типовые вопросы, переводит горячие лиды в очередь на утро.

Раньше эту задачу клиенты решали костылём: ставили боту общую паузу на ночь и просто терпели, что днём он мешал операторам. Это было плохо для всех. Альтернативный режим - это нормальная форма решения: один бот, две воронки внутри, переключение по расписанию.

Четыре слоя альтернативного режима бота: хранилище расписаний, платформенный API с use_workhours, фронтенд с Tabs и Schedule Sheet, мессенджер-специфика

Архитектурно фича собрана из четырёх слоёв снизу вверх.

Слой 1. Хранилище и базовая бизнес-логика. В нижнем слое появились две новые таблицы: одна для рабочих часов (по дням недели) и одна для выходных дней (отдельные даты - праздники, отпуска). Это фундамент: без него вся остальная стройка провисает. Параллельно в этом же слое выросла бизнес-логика чтения расписания и расчёта текущего режима бота: now() сравнивается с расписанием, определяется «рабочее время / нерабочее время», и в зависимости от этого читаются либо основные настройки воронки, либо альтернативные. Эта работа была заложена в начале недели и стала базой для всего, что строилось выше. Без неё ни один из последующих слоёв не имеет смысла.

Слой 2. Платформенный API. На этом уровне мы прокинули три PR. Первый - флаг use_workhours в схемах пятнадцати разных типов ботов и поля альтернативного режима (weekend_*_dialog_cols, weekend_*_chatflow_id) в четырёх типах CRM-конфигов: наша CRM, AmoCRM, KommoCRM, Bitrix24. Второй - эндпоинты CRUD для расписания и выходных дней. Третий - эндпоинт copy-from-main, который копирует настройки из основной воронки в альтернативную, чтобы клиенту не пришлось пересобирать всю логику с нуля (типичный кейс: «у меня уже всё настроено в основной, хочу то же самое, но молча в рабочие часы»). Плюс нормализация расписания в UTC и отдельный фикс фильтрации ботов для альт-режима у одного из типов CRM.

Слой 3. Фронтенд и UX. На странице настройки воронки появились Tabs - «Основной режим / Альтернативный режим». Внутри Tabs всё, что в основном режиме - drag-and-drop статусов, привязка цепочек автоматизации, настройки stop/answer - работает в режиме mode-aware: если пользователь открыл альтернативную вкладку, все изменения пишутся в weekend_* поля, не трогая основные. Расписание (рабочие часы и выходные) задаётся через отдельный Schedule Sheet - выезжающий боковой экран с drag-and-drop по слотам времени и календарём для выходных. Активация фичи на воронке - один тумблер в настройках логики: если включён - применяется альтернативный режим в нерабочее время, если выключен - бот всегда работает по основной воронке.

Слой 4. Мессенджер-специфика. И отдельный, четвёртый, слой - адаптация под конкретные мессенджеры. Тут нашлась интересная история с мессенджером Макс. Это российский мессенджер для бизнеса, к которому мы недавно подключились через Umnico Widget. У него своя специфика чтения рабочих часов, и наш расчёт текущего режима бота для Макса работал криво: бот мог отвечать ночью, даже если воронка была в нерабочем режиме - то есть фактически фича для Макса не работала. Поправили, добавили в общий регресс-тест. Параллельно прошёл фикс лишнего refresh-токена при запросах в нижний слой данных и пара UX-улучшений в логах.

Главный архитектурный урок недели на этой задаче. У нас в платформенном API два независимых слоя описания одной и той же сущности: Pydantic-схемы для валидации входа/выхода и SQLAlchemy-модели для маппинга в нижний слой данных. Если добавить поле только в Pydantic - PATCH-запрос вернёт 200 OK, но GET того же ресурса покажет default-значение, потому что SQLAlchemy при декодировании ответа из нижнего слоя не видит этого поля и тихо его выкидывает. Никаких ошибок, никаких алертов - просто данные пропадают между фронтом и базой. На отладку этого ушло около четырёх часов в первый день. Урок: при добавлении поля в платформенное API нужно править ОБА слоя одновременно. Это уже записано в инженерный чек-лист и в ближайший спринт превратится в статический CI-чек, который на этапе билда не пропустит расхождение между Pydantic и SQLAlchemy.

Отдельно про защиту данных при включении и выключении. У клиентов с уже настроенным альтернативным режимом во внутренних таблицах накоплена ценная информация: расписание, выходные дни, привязка статусов, цепочки автоматизации. Если оператор случайно щёлкнет тумблер «Альт. режим» туда-сюда - данные ни в коем случае не должны пропасть. Поэтому деактивация работает максимально консервативно: тумблер просто переключает флаг use_workhours=false на всех ботах логики, а сами расписания, статусы и цепочки остаются нетронутыми. Повторная активация продолжает с тех же данных. Аналогично выключение конкретного статуса в альт-режиме переводит его в состояние stop (бот молчит), а не none (статус выпадает) - чтобы ни в одной точке настройки нельзя было случайно сломать клиенту тщательно собранную раскладку молчания. Это та невидимая работа, которую видишь, только когда тебе пишет клиент и говорит «у меня вчера всё работало, сегодня тумблер случайно выключил - и потерял настройки за полгода». Мы не хотим таких сообщений никогда.

Зачем это нужно. По нашим оценкам, минимум 30% клиентов работают в нишах с живыми операторами, которые не хотят, чтобы бот мешал им днём: салоны красоты, медицина, юристы, образовательные центры, B2B-консалтинг, агентства недвижимости. Все они и были авторами того запроса, который мы наконец закрыли. Это первая большая фича в этом квартале, целиком собранная по запросам клиентов, а не по нашему продуктовому видению - и именно поэтому я считаю её одной из главных фич недели. Когда пользовательская боль точно совпадает с тем, что вы выкатываете - это и есть зрелость продукта.

Автоматизации: condition, init_dialog и переезд на DAG

Эволюция редактора автоматизаций Botseller: переезд с линейной цепочки v1 на направленный ациклический граф v2 с condition-нодами и atomic-операциями с rollback

Третий крупный технический блок недели - редизайн редактора автоматизаций. У нас есть визуальный конструктор сценариев, где можно собрать пайплайн обработки лидов из блоков: «отправить сообщение», «изменить статус», «дождаться ответа», «вызвать webhook». До этой недели редактор был линейный: ноды соединялись друг с другом через одно поле next_node_id, никаких ветвлений, никаких параллельных веток. Если вам нужно было «если клиент написал «да», то сюда, если «нет» - туда» - это собиралось через костыль в sidebar-настройках, без визуального ветвления на канвасе.

На прошедшей неделе фронт догнал бэкенд по двум новым нодам, которые подготовили заранее: init_dialog_message (бот пишет первым - используется в холодных рассылках, где пайплайн начинается не с входящего сообщения, а с инициации) и condition (полноценное ветвление по условию: значение переменной, статус лида, наличие/отсутствие данных, контент сообщения). Бэкенд этих нод был готов ещё две недели назад, но фронт WIP отставал - потому что ветвление ломает линейную модель next_node_id.

Перевели редактор с линейной модели на DAG (directed acyclic graph). Конкретно: при condition-ноде ноды-наследники строятся не из её собственного next_node_id, а из массива data.handlers[i].next_node_id - по одной next-ноде на каждую ветку. Добавили обнаружение «осиротевших» нод (они визуально отмечаются красным баннером), атомарные операции добавления/удаления нод с rollback при ошибке, отдельный хук схемы condition-ноды (потому что она возвращается с бэка по другому формату, чем линейные ноды), нормализатор условий, валидацию хэндлеров.

Параллельно прошёл визуальный редизайн канваса. Появилась сетка из точек на фоне (dot-grid) - это банальный приём, но он сильно помогает глазу удерживать сетку расположения. Edges между нодами теперь рисуются как bezier-кривые с анимированным dash-flow (легкая анимация направления потока) - это снимает вопрос «куда идёт стрелка» с минимальной когнитивной нагрузкой. Стартовая и финальная ноды получили цветные таблетки (pills), action-нода - заголовок в фирменном цвете, condition-нода - визуально различимые порты ветвей. Плюс мы запустили cmdk-палитру (Cmd+K) для быстрого добавления нод без обращения в sidebar.

Честно признаюсь: я оценивал v1 редактора как «технически работает, но пользоваться больно». Слишком линейный, слишком много кликов, condition только в sidebar. Эталонные редакторы у конкурентов (ChatPlace, AmaSirai, BotHelp) выглядят и ведут себя кардинально иначе - с вложенными группами, visual condition с inline-редактированием правил, drag-and-drop ветвей. Мы туда ещё не дошли, v2 - не финиш, а первый честный шаг в эту сторону. Следующая итерация будет про вложенные condition-группы и inline-редактирование. Но текущая версия уже снимает главное ограничение прошлого - линейность пайплайна.

Clubator: биллинговая стабилизация под боевой нагрузкой

Параллельно с инженерией ядра у нас Clubator (наш движок клубов по подписке) на этой неделе вскрыл серию биллинговых багов. Я писал в журнале 143-й, что Clubator под нагрузкой начал ловить мелкие, но обидные баги в Followup-подсистеме. На этой неделе вылезла другая категория - биллинг с recurring-вебхуками от Prodamus (наш платёжный провайдер).

Главный баг: при recurring-платеже наш webhook-обработчик прибавлял +period_days к существующему access_until, не глядя на реальную дату следующего платежа от провайдера. Это значит, что если у клиента подписка была активна до 30 апреля, и 28 апреля прошёл досрочный recurring-платёж - его access_until улетал в 30 мая вместо 28 мая. Накапливаемая ошибка: за полгода активной подписки клиент получал лишние две-три недели. Звучит как подарок, но это разбалансирует биллинг и портит когортную аналитику. Поправили: теперь мы доверяем date_next_payment от Prodamus и пишем именно его, а не считаем сами.

Второй каскад: у нас были «осиротевшие» клиенты. Сценарий - клиент отписывается через Prodamus, но в нашей базе подписка остаётся активной, потому что webhook отписки не доходит (типичная история с вебхуками за прокси). Поправили: при пропавшей Prodamus-подписке сбрасываем has_access у клиента, при ошибке парсинга order_num - fallback на subscription_id, и считаем рассинхрон прямо по access_until без оглядки на статус бэкенда платёжного провайдера.

Третий: tz-сравнение в кроне kick_expired_users (который выкидывает истёкших клиентов из приватных чатов клуба) - часть клиентов с особыми локалями (KRIPTON, Дарья и другие) не выкидывались вовремя, потому что время истечения хранилось как naive datetime без UTC, а сравнение шло с now() в UTC. Привели к единому виду.

И финально: в админке Clubator появился отдельный статус «Расхождение» для клиентов, у которых наша база и Prodamus говорят разное про подписку. Плюс полная история платежей в карточке клиента: видно каждый recurring, каждый webhook, каждый sync. Это та видимость, которой у нас не было, и без которой биллинговые баги ловятся не в коде, а в Telegram-чате с клиентом, что неприемлемо.

Это нормальная цена за то, что мы продукт сначала проверяем на себе, прежде чем продавать наружу. Клуб у нас живой, операторы работают каждый день, баги вылезают сразу - и за неделю мы их закрываем. На клиентах такие проблемы могли бы спать месяцами и взорваться в самый неудобный момент.

Невидимая работа недели по стабилизации экосистемы: фикс биллинга Clubator с recurring-платежами Prodamus, mobile UX performance, безопасность и SEO с self-host шрифтов и 410 Gone

Лендинг и блог: новые статьи, перформанс, защита трафика

В блог уехали две новые большие статьи. Первая - подробный архитектурный разбор того, как мы устроили трекер сайта (TECH3). Вторая - расширение прошлой статьи про инженерные паттерны (TECH5) до 11 слайдов с реальной презентацией. Плюс выпустили эпизод 15 подкаста - аудиоверсия большой статьи про путь клиента и сквозную аналитику. Подкаст-серия теперь синхронизируется со статьями: новый текст - новый аудиоэпизод в течение нескольких дней.

Поверх этого - инфраструктурная работа на блоге и лендинге.

CSP (Content Security Policy) расширили под Botseller Tracker - теперь pixel.js может грузиться с обоих доменов CRM (продакшн и dev) для безопасных деплоев, плюс разрешили script-src и connect-src именно туда, куда нужно. Расширили CSP под Яндекс.Метрику Вебвизор (он подгружает дополнительные ресурсы при записи сессий) и под Яндекс.Карты в frame-ancestors, чтобы карты не блокировались при встраивании. Без этого расширения Tracker молча отказывал работать на проде - и эти ошибки видишь только в DevTools конкретного пользователя, что мешает диагностике.

Self-host Inter fonts - перенесли шрифты с Google Fonts на собственный CDN. Это снимает зависимость от внешнего домена (один из векторов 152-ФЗ-рисков) и убирает внешний DNS-lookup из критического пути загрузки. Цена - чуть больше работы по обновлению шрифтов вручную, но в эпоху, когда даже шрифты могут быть точкой отказа, это разумная страховка.

410 Gone расширили под WordPress-наследие. У нас на старом домене жил WordPress, после миграции на Next.js остались ~900 «зомби-URL», которые поисковики продолжали индексировать. Сначала мы возвращали 404, но Google любит 404 хранить в индексе как «soft 404» месяцами. 410 Gone - явный сигнал «это удалено, забудь» - выкидывает страницу из индекса в среднем втрое быстрее. На неделе расширили nginx-правила на еще одну порцию старых URL. И параллельно поставили X-Robots-Tag: noindex на эндпоинт /api/og - он генерирует OG-картинки для Telegram, в индексе ему делать нечего.

Mobile UX полировка: на мобильных лендинг страдал от трёх вещей - тяжёлое автовоспроизведение видео в hero-блоке (рваный скролл), не центрированный CRM preview (визуально ломался на узких экранах), нечитаемые названия каналов в hero. Прошлись по всем трём. Добавили постеры до hover на видео кейсов - сейчас на мобильных видео не запускается само, пока пользователь не активирует, и это разом починило jank скролла. Победили scroll-behavior на iOS Safari (отдельная боль - на ней smooth scroll иногда добавляет +200ms лага на каждый pageView). Получился ощутимо более лёгкий лендинг.

В чём главный итог 144-й недели?

Aha-moment 144-й недели Botseller: единая замкнутая экосистема от трафика и заявки через редирект в WhatsApp до сменности бот-ночью-человек-днём в карточке CRM

Если 142-я была про переход к экосистеме, 143-я - про географическое разделение на два домена, то 144-я - про возвращение к ядру. Мы взяли пять фундаментальных дыр в продукте и закрыли их одну за другой: трекинг трафика, сборку лидов через свой же конструктор, разделение режимов работы бота, нормальное ветвление в автоматизациях, видимость биллинга. Каждая из них по отдельности не выглядит эффектно. Все вместе они меняют то, какой продукт мы продаём.

До этой недели CRM и лендинг жили как два сообщающихся сосуда, между которыми постоянно подтекало. Менеджер не понимал, откуда лид. Маркетолог не видел, на каких статьях люди возвращаются. Бот работал круглосуточно и днём перебивал операторов, которые сами хотели вести своих клиентов. Автоматизации собирались через костыли, а биллинг расходился у клиентов с провайдером без видимости. После этой недели трафик трекается на всех четырёх приложениях, лиды приходят в CRM с полной биографией, бот работает по расписанию (днём отдыхает, пока сотрудники в чатах, ночью включается и ловит заявки сам) - то есть наконец встроился в режим работы реальной команды, а не диктует свой. В редакторе автоматизаций появилось полноценное ветвление, а биллинговые расхождения подсвечиваются в админке отдельным статусом.

Что дальше. На 145-й неделе план: довести редактор автоматизаций до вложенных condition-групп с inline-редактированием правил (чтобы не открывать sidebar для каждого условия), запустить первую большую партнёрскую кампанию через Botseller Tracker (с пиксельной атрибуцией каждого партнёра отдельно), и закрыть оставшиеся регрессии Pydantic-vs-SQLAlchemy в платформенном API через статический чек на CI. Плюс новая статья блога про то, как продаются SaaS со сквозной аналитикой и почему чужая Метрика - это не конкурент собственному трекеру, а дополнение к нему.

Приходите в Telegram-канал

Если этот формат отчётов вам полезен - подпишитесь на наш Telegram-канал @botseller_ai. Там я каждую неделю выкладываю эти журналы плюс короткие посты про продуктовые решения, эксперименты и инциденты в проде. Без рекламы, без воды - только то, что мы сами считаем важным.

Если хотите попробовать платформу - заходите на botseller.ai (международная версия) или botseller.ru (российская версия с поддержкой кириллического домена ботселлер.рф). При регистрации даём 500 ₽ в подарок - можно собрать ИИ-бота, поставить трекер на свой сайт и потрогать всё руками.

Если интересна не только платформа, но и бизнес вокруг неё - посмотрите партнёрскую программу и калькулятор дохода партнёра. Реферал, интегратор, IT-франшиза - три модели на любой профиль. Документация для старта - быстрый старт за пять шагов. До встречи на 145-й.

FAQ

Что такое атрибуция трафика и зачем она нужна в CRM, если есть Яндекс.Метрика?

Атрибуция трафика - это привязка каждого посещения сайта к конкретному человеку (visitor) и далее к конкретному лиду в CRM. Метрика делает это для агрегатной аналитики: сколько всего пришло, какая средняя конверсия, какие источники работают лучше. Но Метрика не знает ваших лидов в CRM и не показывает менеджеру, что конкретно этот клиент заходил восемь раз и читал статью про сравнение тарифов. Собственный трекер пишет историю касаний прямо в CRM, и менеджер видит биографию лида в карточке - не в чужом аналитическом интерфейсе. Это разные задачи, и они дополняют друг друга.

Что показывает таб «Маршрут» в карточке лида?

Это новый раздел в карточке, который собирает всю историю контактов клиента с вашим сайтом до момента создания лида и после. Сверху - двухуровневая TrafficSourceCard: одной строкой источник, кампания и ключевое слово, по клику разворачивается с UTM-метками и tooltips. Ниже - TouchpointTimeline: хронологический список касаний с временем, страницей, заголовком и метаданными. Маркетолог видит маршрут одним взглядом, менеджер открывает по клику нужное касание и понимает контекст разговора с клиентом.

Что значит «альтернативный режим» бота - это бот молчит или бот работает?

Это бот РАБОТАЕТ - тогда, когда сотрудники не работают. Семантика часто кажется обратной. Альтернативный режим включается в нерабочее время воронки: после рабочего дня, в выходные, в отпуск или в любое окно, которое клиент пометил как «нерабочее» в расписании. В это время бот берёт чаты на себя: ловит заявки, отвечает на типовые вопросы, ведёт квалификацию по своему сценарию. А в рабочее время, когда сотрудники в офисе, бот, наоборот, отдыхает: не вмешивается в чаты, чтобы операторы вели клиентов сами без перебивания. Поэтому альтернативный режим - это две разные воронки внутри одного бота с двумя разными раскладками статусов и двумя разными цепочками автоматизации. Расписание определяет, какая из них активна прямо сейчас.

В чём смысл переписывания редактора автоматизаций v2 вместо полировки v1?

Потому что v1 был линейный - ноды соединялись цепочкой через одно поле next_node_id, без визуального ветвления. Полировать линейный редактор бессмысленно: главное ограничение продукта (нельзя нормально собрать сценарий с ветвлением) останется. Мы перешли на DAG-модель: ветвление через condition-ноду с массивом handlers, каждый со своей next-нодой, плюс обнаружение «осиротевших» нод и атомарные операции с rollback. Это первый честный шаг в сторону редакторов уровня ChatPlace или BotHelp. До их уровня ещё несколько итераций - вложенные condition-группы, inline-редактирование правил, drag-and-drop ветвей - но без перехода на DAG никакие из этих фич нельзя было собрать.

Почему в Clubator биллинговые баги вылезли только сейчас, через несколько недель после релиза?

Потому что мы только-только начали активно пользоваться recurring-платежами. Когда клиенты платят разовыми покупками - 95% багов спят. Стоит подключить регулярные списания через Prodamus, накопить шесть месяцев истории и пройти через несколько досрочных recurring - и сразу вылезает накопление access_until и tz-сравнения. Это нормальная цена за то, что мы сначала проверяем продукт на себе. Все шесть багов закрыли за два дня, плюс добавили видимость в админке - теперь любое расхождение между нашей базой и Prodamus сразу подсвечивается отдельным статусом «Расхождение».

Чем веб-форма Botseller отличается от Tilda, Tally или Google Forms?

Тем, что она привязана к нашей же CRM как первоклассный гражданин. В Tilda или Tally вы собираете форму, она шлёт данные в почту или Google Sheet, и потом вы руками или через Zapier пробрасываете это в CRM - теряя по пути источник трафика, время визита, страницу, с которой пришла заявка. Наша форма работает в связке с Botseller Tracker: лид приходит в CRM сразу с биографией - откуда зашёл, какие страницы читал, какие касания были до. Плюс selects привязываются к enum’ам в backend (никаких рассинхронов справочников), а после submit можно сделать редирект в мессенджер с подстановкой {lead_id} - клиент попадает в WhatsApp, где менеджер уже видит его карточку.

Когда ждать вложенные condition-группы в редакторе автоматизаций?

Это план на 145-146-ю недели. Сейчас condition-нода работает в режиме «одно условие - одна ветка», и для составных условий («если статус = X И сумма > 100K») нужно собирать каскад из двух condition-нод. Это работает, но визуально шумно. Следующая итерация - вложенные группы условий с операторами AND/OR, inline-редактирование правил прямо на канвасе и drag-and-drop ветвей. Архитектурный фундамент (DAG, atomic ops, condition normalizer) уже готов на этой неделе, дальше - UX.