Legan Studio
Все статьи
~ 16 мин чтения

Бот для онлайн-записи: салоны, клиники, услуги

Разбираем архитектуру бота для онлайн-записи: расписание мастеров, слоты, оплата предоплаты, напоминания и интеграция с YCLIENTS/Altegio.

  • Telegram
  • услуги
  • сценарии
  • интеграции

Бот для онлайн-записи — один из самых очевидных и при этом самых недооценённых форматов в сфере услуг. Салоны, клиники, барбершопы, фитнес-студии, автосервисы и репетиторы обычно тратят часы операторов на запись по телефону, тогда как 60–80% клиентов готовы записаться сами в мессенджере. Главное — правильно собрать слоты, синхронизацию с системой записи и напоминания. Ниже — подробный разбор сценариев, интеграций и подводных камней архитектуры.

Универсальный сценарий записи

Минимальный путь клиента в боте выглядит так:

  1. Выбор услуги или категории (стрижка, маникюр, чистка зубов, диагностика двигателя).
  2. Выбор мастера/специалиста (или «любой свободный»).
  3. Выбор даты — календарь на 2–4 недели вперёд.
  4. Выбор времени — слоты подгружаются из расписания.
  5. Подтверждение контактных данных (имя, телефон).
  6. Предоплата или залог (опционально) — для борьбы с no-show.
  7. Подтверждение записи + добавление в календарь устройства.
  8. Напоминания → визит → запрос отзыва.

Максимум 5–6 шагов до подтверждения. Каждый дополнительный — минус 5–10% доходимости. Оптимизировать стоит с двух сторон: убирать необязательные экраны (например, повторный ввод имени, если клиент уже регистрировался) и сокращать клики (день и время — на одном экране, а не на двух).

Альтернативный поток: «к моему мастеру»

В индустрии красоты, медицине и репетиторстве у 60–80% клиентов есть «свой» специалист — они приходят именно к нему. Для них линейный путь «услуга → мастер» неудобен: клиент уже знает, кого хочет, и ему важно увидеть ближайшие слоты этого человека, а не выбирать заново.

Решение — параллельный вход в воронку «к моему мастеру»:

  1. Выбор специалиста из списка избранных или быстрого поиска.
  2. Календарь со слотами этого мастера на 2–4 недели.
  3. Выбор услуги из тех, что мастер выполняет.
  4. Подтверждение и предоплата.

Технически это тот же FSM, но с другим стартовым состоянием. Любимые мастера хранятся в профиле клиента (favorite_specialists: int[]) и автоматически добавляются после третьего визита к одному и тому же специалисту. На главном экране бота — две кнопки: «Записаться» (классический путь) и «К моему мастеру» (если есть избранные).

Mini App vs inline-кнопки для календаря

Календарь — самый «капризный» экран в воронке записи. Есть два пути его реализации, и выбор сильно влияет на UX и конверсию.

КритерийInline-кнопкиMini App (WebView)
Время до первого экрана200–500 мс1.5–3 с
Слотов на одном экране6–12 (ограничение клавиатуры)50+ с прокруткой
Длинные слоты (окрашивание, депиляция)Скучно листать страницыВидно весь день сразу
Drag-to-select интерваловНетДа
Оффлайн в метроРаботает (просто кнопки)Падает на загрузке
Сложность разработки2.5–3×
БрендированиеОграниченноеПолная свобода
Конверсия на простых услугах12–18%10–15%
Конверсия на сложных услугах6–9%11–14%

Практическое правило: если средняя длительность услуги 30–60 минут и слотов на день 8–12 — берите inline. Если услуги длятся 1.5–4 часа (окрашивание, татуировки, СПА), много мастеров и нужен «вид недели» — Mini App. Гибрид тоже работает: в боте быстрая запись через inline, в Mini App — глубокий выбор и перенос существующих записей.

Сравнение систем онлайн-записи

В большинстве сервисных бизнесов уже стоит специализированная CRM. Бот ходит в её API, а не строит параллельное расписание. Сравнение основных систем на российском рынке:

СистемаНишаAPIWebhookЛимитыКомментарий
YCLIENTSСалоны красоты, барбершопы, фитнесREST JSONДа200 запросов/минСтандарт индустрии красоты, богатое API
AltegioБьюти, медицина (бывший YCLIENTS Pro)REST JSONДа200/минРасширенная версия YCLIENTS, лучше для сетей
DikidiБьюти, бюджетный сегментRESTЧастично60/минДешевле, но API беднее, webhooks нестабильны
SonlineУниверсальный сервисRESTДа100/минУдобен для малого бизнеса, ограничен в кастомизации
EnterMediaМедицинские клиникиSOAP + RESTДа60/минСильный медицинский контур, ДМС, страховые
1С:МедицинаКрупные клиники, сетиOData / HTTP-сервисыЧерез очередьЗависит от сервераТяжёлая интеграция, нужен опытный 1С-разработчик
RenovatioСтоматологияRESTДа120/минЗаточен под зубные карты и план лечения
IDENTСтоматологияRESTДа100/минКонкурент Renovatio, лучше с рентгеном и DICOM

Универсальное правило: всегда смотрите, что уже стоит у клиента, и подключайтесь к этому. Перевод бизнеса на новую систему записи ради бота — почти всегда плохая идея и съедает 3–6 месяцев на миграцию данных и обучение администраторов.

Синхронизация слотов: webhook + пуллинг

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

Надёжный паттерн — двухконтурная синхронизация:

  1. Webhook от системы записи на изменения (appointment.created, appointment.cancelled, schedule.updated). Прилетает за 1–3 секунды, инвалидирует кэш слотов в Redis.
  2. Периодический пуллинг раз в 5–15 минут — догоняет пропущенные webhook (сеть упала, очередь захлебнулась, у вендора был сбой).

Без пуллинга периодически случается «фантомная запись»: webhook не дошёл, бот показал занятый слот как свободный, клиент кликнул — и получил ошибку «время занято». Без webhook слоты обновляются с задержкой 5–15 минут, и в выходной день популярного мастера это превращается в ад.

# Псевдокод обработки webhook от YCLIENTS
async def handle_yclients_webhook(payload: dict):
    event_type = payload.get("status")
    company_id = payload["data"]["company_id"]
    staff_id = payload["data"]["staff_id"]
    appointment_date = payload["data"]["date"]

    # Идемпотентность: один и тот же webhook может прийти 2-3 раза
    event_id = payload.get("event_id") or hashlib.sha256(
        json.dumps(payload, sort_keys=True).encode()
    ).hexdigest()

    if await redis.exists(f"webhook_seen:{event_id}"):
        return {"ok": True, "duplicate": True}
    await redis.setex(f"webhook_seen:{event_id}", 86400, "1")

    # Инвалидируем кэш слотов конкретного мастера на эту дату
    cache_key = f"slots:{company_id}:{staff_id}:{appointment_date}"
    await redis.delete(cache_key)

    # Если в этот слот была запись через бот - уведомляем клиента
    if event_type == "cancelled":
        booking = await db.fetchrow(
            "SELECT user_id FROM bookings WHERE external_id=$1",
            payload["data"]["id"],
        )
        if booking:
            await notify_user_cancelled(booking["user_id"])

    return {"ok": True}

Параллельно крутится cron-задача, которая раз в 10 минут запрашивает все слоты на ближайшие 14 дней по каждому филиалу и сравнивает с кэшем. Расхождения логируются — если они стабильно появляются, у вас проблема с webhook, и стоит писать вендору.

Защита от двойного бронирования

Два клиента кликают «забронировать» на один слот в одну секунду. Без защиты оба получают «успешно забронировано», а потом администратор разбирается, кому отказать. Решение — двухуровневая блокировка:

  1. Distributed lock в Redis на ключ lock:slot:{specialist_id}:{datetime} с TTL 10–15 секунд. Захватывает первый запрос, второй получает «слот занят, попробуйте ещё раз».
  2. Optimistic locking в БДINSERT ... ON CONFLICT DO NOTHING по уникальному индексу (specialist_id, start_time). Если апдейт затронул 0 строк — слот уже занят.
async def book_slot(user_id: int, specialist_id: int, start: datetime):
    lock_key = f"lock:slot:{specialist_id}:{start.isoformat()}"
    lock = await redis.set(lock_key, str(user_id), nx=True, ex=15)
    if not lock:
        raise SlotTaken("Слот только что занят другим клиентом")

    try:
        # Уникальный индекс гарантирует атомарность на уровне БД
        result = await db.execute(
            """
            INSERT INTO bookings (user_id, specialist_id, start_time, status)
            VALUES ($1, $2, $3, 'pending')
            ON CONFLICT (specialist_id, start_time) DO NOTHING
            RETURNING id
            """,
            user_id, specialist_id, start,
        )
        if not result:
            raise SlotTaken("Слот занят")

        # Создаём запись во внешней системе записи
        external_id = await yclients.create_appointment(...)
        await db.execute(
            "UPDATE bookings SET external_id=$1, status='confirmed' WHERE id=$2",
            external_id, result["id"],
        )
    finally:
        await redis.delete(lock_key)

Без обоих уровней система ломается на пиках: блокировка в Redis спасает от 99% коллизий, уникальный индекс — от оставшегося 1% (например, при сбое Redis или гонке между двумя инстансами бота).

Перенос и отмена записи

Перенос — самый частый запрос после самой записи. Хороший бот закрывает его без звонка администратору. Правила, которые стоит зашить:

  • Перенос за 24 часа и более — бесплатно, любое доступное время.
  • Перенос за 2–24 часа — бесплатно, но только в тот же день или на ближайшие 48 часов (чтобы окно не висело пустым).
  • Перенос менее чем за 2 часа — только через администратора, бот просит позвонить.
  • Отмена за 24 часа — полный возврат предоплаты.
  • Отмена менее чем за 24 часа — удержание 50% или вся предоплата (в зависимости от политики салона).
  • No-show — удержание 100%, плюс автоматическая пометка клиента как «ненадёжный» (после 2–3 раз — требование 100% предоплаты).

Все правила хранятся в конфиге салона, не зашиваются в код — у разных бизнесов разная политика. Лист ожидания (см. ниже) запускается автоматически при отмене за 24+ часов.

Напоминания: 24ч / 2ч / 30мин

Стандартная цепочка для записи:

  • Мгновенное подтверждение при создании записи (карточка с датой, временем, мастером, адресом, картой).
  • Напоминание за 24 часа — с кнопками «Подтверждаю», «Перенести», «Отменить».
  • Напоминание за 2 часа — мягкое, с адресом и картой, без CTA.
  • Напоминание за 30 минут — для бизнесов с пробками вокруг (центр города, ТРЦ).
  • Через 2–4 часа после визита — запрос отзыва.
  • Через 14–60 дней — напоминание о повторной записи (зависит от услуги: маникюр — 21 день, стрижка — 30 дней, чистка зубов — 180 дней).

Все интервалы настраиваемы в админке салона. Кнопка «Отменить» в напоминании — в один клик, без подтверждений и капчи: не пытайтесь удержать клиента, который уже решил не приходить, — он просто проигнорирует напоминание, и вы получите no-show вместо отмены за сутки.

МетрикаБез напоминанийТолько за 24чПолная цепочка
Доходимость70–78%82–88%88–94%
Отмены за 24+ часов5–8%10–14%14–18%
No-show18–25%8–12%3–6%
Возврат на повторный визит30–40%35–45%50–65%

Парадокс: с агрессивными напоминаниями растёт количество отмен, но падает no-show — а отмена за сутки гораздо лучше для бизнеса, потому что слот можно перепродать через лист ожидания.

Лист ожидания

Если все слоты на нужную дату заняты, клиент попадает в очередь. При освобождении слота (отмена, перенос, мастер открыл дополнительное окно) — бот автоматически рассылает уведомление первым N клиентам из листа с кнопкой «Забронировать».

Реализация:

CREATE TABLE waiting_list (
    id           BIGSERIAL PRIMARY KEY,
    user_id      BIGINT NOT NULL,
    company_id   INTEGER NOT NULL,
    specialist_id INTEGER,           -- NULL = любой мастер
    service_id   INTEGER NOT NULL,
    date_from    DATE NOT NULL,
    date_to      DATE NOT NULL,
    time_from    TIME,                -- опционально: только утром / только вечером
    time_to      TIME,
    created_at   TIMESTAMPTZ DEFAULT now(),
    notified_at  TIMESTAMPTZ,
    status       TEXT DEFAULT 'active' -- active | notified | converted | expired
);
CREATE INDEX ON waiting_list (company_id, service_id, status) WHERE status = 'active';

При отмене слота запускается матчер: ищет в waiting_list записи, у которых date_from <= cancelled_date <= date_to и (specialist_id IS NULL OR specialist_id = cancelled_specialist). Уведомляются первые 3–5 клиентов одновременно — кто первый кликнул «Забронировать», тот и получил слот (с distributed lock, см. выше).

Конверсия из листа ожидания обычно 25–40%: люди, которые уже хотели именно этот день, охотно соглашаются. Это бесплатный канал возврата выручки от no-show и отмен.

Предоплата и залог

Один из главных pain-point салонов — клиенты, которые не пришли. Бот решает это через:

  • Частичную предоплату (15–30% от стоимости услуги).
  • Удержание оплаты через ЮKassa, Telegram Payments или СБП.
  • Автоматический возврат при отмене за 24+ часов.
  • Удержание при late cancel или no-show.
  • Первая запись — обычно без предоплаты (барьер входа), повторная — уже с залогом 500–1000 ₽.

После внедрения предоплаты no-show обычно снижается с 15–25% до 3–5%, что для салона на 10 кресел = 80–150 тыс. ₽ дополнительной выручки в месяц.

Telegram Payments удобен тем, что клиенту не нужно покидать чат: оплата проходит в нативном окне за 3–4 клика. Минус — комиссия 2.5–3.5% (зависит от провайдера) и ограниченный список банков. ЮKassa дешевле (1.5–2.5%) и поддерживает СБП с 0.4–0.7%, но требует перехода на внешнюю страницу.

Программа лояльности

Лояльность, привязанная к записям, работает в 2–3 раза лучше, чем абстрактные «баллы за активность». Базовый набор механик:

  • Кешбэк баллами — 5–10% от суммы визита, можно потратить на следующий (но не более 30% корзины).
  • Абонементы — пакет «10 стрижек со скидкой 15%» с TTL 6–12 месяцев. Бот отслеживает оставшиеся визиты и напоминает за 2–3 до конца.
  • Скидка на первый визит — 10–20% или фикс. ₽ за регистрацию в боте; одноразово, проверяется по phone_hash.
  • Реферальная программа — приглашающий получает 500 ₽ на счёт после первого визита приглашённого, приглашённый — скидку 10%.
  • День рождения — скидка 15% в течение 7 дней до и после; повышает retention на 8–12%.
  • Статусы — Bronze/Silver/Gold по сумме за 12 месяцев, с растущими привилегиями (приоритетная запись, расширенный список ожидания, бесплатные апгрейды).

Все механики хранятся в loyalty_rules и применяются движком при расчёте стоимости визита. Не зашивайте их в код — управляющий салона должен включать/выключать без разработчика.

Отзывы и работа с негативом

Через 2–4 часа после окончания визита бот отправляет короткий опрос:

  1. «Как прошёл визит?» — 5 эмодзи-кнопок (😡 / 😕 / 😐 / 🙂 / 😍).
  2. Если 4–5 — благодарим и просим оставить отзыв на Яндекс.Картах / 2GIS / Otzovik (с deeplink на нужную страницу).
  3. Если 1–3 — извиняемся, просим написать, что пошло не так, и эскалируем менеджеру в течение 1 часа.

Этот фильтр — критичен. Без него негативные отзывы попадают на публичные площадки и режут поток новых клиентов. С фильтром: 70–80% положительных уходят на карты, 80–90% негатива закрывается лично, и только 20–30% всё равно пишут публично — это нормальный фон.

Для NPS считаем долю «промоутеров» (4–5) минус долю «детракторов» (1–2). Хороший салон держит NPS 50+, отличный — 70+.

Аналитика для управляющего

Минимум, что должен видеть владелец/администратор в дашборде:

МетрикаХорошоТревога
Доля записей через бот40–60%менее 20%
No-show rate3–6%более 10%
Загруженность мастеров70–85%менее 50% или более 95%
Средний чекрастёт МоМпадает 2 месяца подряд
Retention 60 дней55–70%менее 40%
NPS50+менее 30
Recovery rate (лист ожидания)25–40%менее 15%
Конверсия первой записи30–50%менее 20%

Дашборд в Metabase или Power BI поверх БД бота даёт владельцу салона картину в реальном времени. Главное — не путать загруженность с прибылью: 95% загрузка часто означает, что мастер устал, качество падает, а отзывы — следом.

Сеть филиалов и мульти-локационность

Когда у бизнеса 2+ филиала (или сеть из 20+ точек), архитектура усложняется:

  • Выбор адреса — первый шаг воронки, после геолокации или ручного ввода. По умолчанию — ближайший к клиенту.
  • Разные расписания — каждый филиал в своём часовом поясе (актуально для сетей в РФ от Калининграда до Владивостока).
  • Разный прайс — одна услуга может стоить 1500 ₽ в Туле и 4000 ₽ в Москве; цена тянется по (service_id, branch_id).
  • Разный набор услуг — не все процедуры доступны во всех филиалах (например, лазерная косметология есть только в 3 точках из 12).
  • Перевод записи между филиалами — отдельный сценарий с предупреждением «другой адрес, другие цены».
  • Единая программа лояльности — баллы зарабатываются и тратятся в любом филиале сети, это сильно увеличивает retention.

В Telegram-боте филиал часто хранится в user_session.preferred_branch после первого выбора и предлагается по умолчанию, но всегда можно сменить через меню. Для сетей с 50+ точками — поиск по городу и метро в Mini App, с inline-кнопок неудобно.

Чек-лист готовности бота записи к запуску

  • Воронка укладывается в 5–6 шагов до подтверждения.
  • Есть параллельный путь «к моему мастеру» для повторных клиентов.
  • Слоты синхронизируются через webhook + пуллинг (двухконтурно).
  • Двойное бронирование защищено distributed lock + уникальным индексом.
  • Перенос и отмена работают без звонка администратору.
  • Цепочка напоминаний 24ч / 2ч / 30мин включается из админки.
  • Лист ожидания автоматически уведомляет при освобождении слота.
  • Предоплата через ЮKassa/Telegram Payments снижает no-show до 5%.
  • Программа лояльности с абонементами и реферальной механикой.
  • Опрос NPS с фильтрацией: позитив на карты, негатив — менеджеру.
  • Дашборд с загруженностью, no-show, retention, NPS.
  • Мульти-филиальная поддержка с автоматическим выбором ближайшего.
  • Все правила (отмены, скидки, напоминания) выносятся в конфиг.
  • Часовые пояса хранятся в UTC, отображаются локально.
  • Контакты валидируются (телефон по маске страны).

Итого

Бот для онлайн-записи — это связка короткой воронки выбора слота, двухконтурной синхронизации с системой записи (YCLIENTS, Altegio, Dikidi, 1С:Медицина), защиты от двойного бронирования, предоплаты для борьбы с no-show, цепочки напоминаний, листа ожидания и программы лояльности. Реалистичные сроки запуска — 3–6 недель, бюджет — 150 000–500 000 ₽ в зависимости от глубины интеграций и числа филиалов. Окупается обычно на 2–3 месяце за счёт снижения no-show, автоматизации операторских часов и роста retention через напоминания о повторной записи.

Частые вопросы

Как должен выглядеть сценарий онлайн-записи в Telegram-боте?

Минимальный путь клиента из 5–6 шагов. Выбор услуги или категории (стрижка, маникюр, чистка зубов). Выбор мастера/специалиста или «любой свободный». Выбор даты и времени — слоты подгружаются из расписания. Подтверждение контактных данных (имя, телефон). Предоплата (опционально) для борьбы с no-show. Подтверждение записи и добавление в календарь. Максимум 5–6 шагов. Каждый дополнительный — минус 5–10% доходимости. Для повторных клиентов имеет смысл сделать параллельный путь «к моему мастеру»: сначала специалист, потом его свободные слоты, потом услуга — это привычнее тем, у кого есть свой мастер.

Когда брать Mini App для календаря, а когда обычные inline-кнопки?

Inline-кнопки выигрывают на простых услугах со средней длительностью 30–60 минут и 8–12 слотами на день: они быстрее загружаются (200–500 мс против 1.5–3 с), работают в плохой связи и не требуют отдельного фронтенда. Mini App выигрывает на длинных услугах (окрашивание, татуировки, СПА), когда нужно показать весь день сразу, фасетные фильтры по мастерам и параметрам, drag-to-select интервалов или собственное брендирование интерфейса. Гибрид часто оптимален: быстрая запись через inline, перенос и глубокий выбор — через Mini App.

Какие системы онлайн-записи можно интегрировать с ботом?

Самые популярные на российском рынке. YCLIENTS — стандарт индустрии красоты, REST JSON API с webhooks, лимит 200 запросов/мин. Altegio — расширенная версия YCLIENTS для сетей и медицины. Dikidi — бюджетный сегмент, REST с нестабильными webhook. Sonline — универсальный сервис для малого бизнеса. EnterMedia — медицинские клиники, SOAP плюс REST. 1С:Медицина — крупные клиники и сети, OData плюс HTTP-сервисы, тяжёлая интеграция. Renovatio и IDENT — стоматология. Универсальное правило: интегрируйтесь с тем, что уже стоит у клиента, перевод бизнеса на новую систему ради бота почти всегда плохая идея и съедает 3–6 месяцев.

Как технически синхронизировать слоты между ботом и системой записи?

Двухконтурно: webhook плюс периодический пуллинг. Webhook от системы записи (YCLIENTS, Altegio) на события appointment.created, appointment.cancelled, schedule.updated прилетает за 1–3 секунды и инвалидирует кэш слотов в Redis. Параллельно cron каждые 5–15 минут запрашивает все слоты на ближайшие 14 дней и сравнивает с кэшем — это страховка от пропущенных webhook. Без пуллинга периодически случается «фантомная запись»: webhook не дошёл, бот показал занятый слот свободным. Без webhook слоты обновляются с задержкой и в выходной день популярного мастера это превращается в ад. Webhook должны быть идемпотентными — один и тот же event_id может прилететь 2–3 раза.

Как защититься от двойного бронирования одного слота?

Двухуровневой блокировкой. Сначала distributed lock в Redis на ключ slot:specialist_id:datetime с TTL 10–15 секунд — захватывает первый запрос, второй получает «слот только что занят». Поверх этого optimistic locking в БД — INSERT ON CONFLICT DO NOTHING по уникальному индексу specialist_id плюс start_time. Если апдейт затронул 0 строк — слот занят, откатываем транзакцию. Без обоих уровней система ломается на пиках: блокировка в Redis спасает от 99% коллизий, уникальный индекс — от оставшегося 1% при сбое Redis или гонке между двумя инстансами бота.

Какая цепочка напоминаний оптимальна для бота записи?

Стандартная: подтверждение в момент записи плюс напоминания за 24 часа, за 2 часа и за 30 минут (последнее — для бизнесов в центре города или ТРЦ с пробками). Через 2–4 часа после визита — запрос отзыва, через 14–60 дней — напоминание о повторной записи (интервал зависит от услуги: маникюр 21 день, стрижка 30 дней, чистка зубов 180 дней). Все интервалы настраиваемые в админке. Кнопка «Отменить» — в один клик, без капчи: попытка удержать решившего не приходить даёт no-show вместо отмены за сутки. Без напоминаний доходимость 70–78%, с полной цепочкой — 88–94%. Парадоксально: агрессивные напоминания увеличивают долю отмен, но снижают no-show — а отмена за сутки гораздо лучше для бизнеса, потому что слот можно перепродать через лист ожидания.

Зачем нужен лист ожидания и как он работает?

Лист ожидания — бесплатный канал возврата выручки от no-show и отмен. Если все слоты на нужную дату заняты, клиент попадает в очередь с указанием филиала, мастера (опционально), услуги, диапазона дат и времени. При освобождении слота (отмена, перенос, мастер открыл дополнительное окно) бот автоматически рассылает уведомление первым 3–5 клиентам из листа с кнопкой «Забронировать»: кто первый кликнул, тот и получил (с distributed lock против гонок). Конверсия из листа ожидания обычно 25–40% — люди, которые уже хотели именно этот день, охотно соглашаются. Без листа ожидания освободившиеся слоты часто остаются пустыми до конца дня.