Бот для онлайн-записи — один из самых очевидных и при этом самых недооценённых форматов в сфере услуг. Салоны, клиники, барбершопы, фитнес-студии, автосервисы и репетиторы обычно тратят часы операторов на запись по телефону, тогда как 60–80% клиентов готовы записаться сами в мессенджере. Главное — правильно собрать слоты, синхронизацию с системой записи и напоминания. Ниже — подробный разбор сценариев, интеграций и подводных камней архитектуры.
Универсальный сценарий записи
Минимальный путь клиента в боте выглядит так:
- Выбор услуги или категории (стрижка, маникюр, чистка зубов, диагностика двигателя).
- Выбор мастера/специалиста (или «любой свободный»).
- Выбор даты — календарь на 2–4 недели вперёд.
- Выбор времени — слоты подгружаются из расписания.
- Подтверждение контактных данных (имя, телефон).
- Предоплата или залог (опционально) — для борьбы с no-show.
- Подтверждение записи + добавление в календарь устройства.
- Напоминания → визит → запрос отзыва.
Максимум 5–6 шагов до подтверждения. Каждый дополнительный — минус 5–10% доходимости. Оптимизировать стоит с двух сторон: убирать необязательные экраны (например, повторный ввод имени, если клиент уже регистрировался) и сокращать клики (день и время — на одном экране, а не на двух).
Альтернативный поток: «к моему мастеру»
В индустрии красоты, медицине и репетиторстве у 60–80% клиентов есть «свой» специалист — они приходят именно к нему. Для них линейный путь «услуга → мастер» неудобен: клиент уже знает, кого хочет, и ему важно увидеть ближайшие слоты этого человека, а не выбирать заново.
Решение — параллельный вход в воронку «к моему мастеру»:
- Выбор специалиста из списка избранных или быстрого поиска.
- Календарь со слотами этого мастера на 2–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 интервалов | Нет | Да |
| Оффлайн в метро | Работает (просто кнопки) | Падает на загрузке |
| Сложность разработки | 1× | 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, а не строит параллельное расписание. Сравнение основных систем на российском рынке:
| Система | Ниша | API | Webhook | Лимиты | Комментарий |
|---|---|---|---|---|---|
| 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 + пуллинг
Самая нетривиальная часть архитектуры. Слоты в системе записи меняются постоянно: администратор вписывает запись по телефону, мастер отменил день, клиент перенёс через сайт. Бот должен видеть актуальные данные, иначе предложит уже занятый слот и получит ошибку при создании записи.
Надёжный паттерн — двухконтурная синхронизация:
- Webhook от системы записи на изменения (
appointment.created,appointment.cancelled,schedule.updated). Прилетает за 1–3 секунды, инвалидирует кэш слотов в Redis. - Периодический пуллинг раз в 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, и стоит писать вендору.
Защита от двойного бронирования
Два клиента кликают «забронировать» на один слот в одну секунду. Без защиты оба получают «успешно забронировано», а потом администратор разбирается, кому отказать. Решение — двухуровневая блокировка:
- Distributed lock в Redis на ключ
lock:slot:{specialist_id}:{datetime}с TTL 10–15 секунд. Захватывает первый запрос, второй получает «слот занят, попробуйте ещё раз». - 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-show | 18–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 часа после окончания визита бот отправляет короткий опрос:
- «Как прошёл визит?» — 5 эмодзи-кнопок (😡 / 😕 / 😐 / 🙂 / 😍).
- Если 4–5 — благодарим и просим оставить отзыв на Яндекс.Картах / 2GIS / Otzovik (с deeplink на нужную страницу).
- Если 1–3 — извиняемся, просим написать, что пошло не так, и эскалируем менеджеру в течение 1 часа.
Этот фильтр — критичен. Без него негативные отзывы попадают на публичные площадки и режут поток новых клиентов. С фильтром: 70–80% положительных уходят на карты, 80–90% негатива закрывается лично, и только 20–30% всё равно пишут публично — это нормальный фон.
Для NPS считаем долю «промоутеров» (4–5) минус долю «детракторов» (1–2). Хороший салон держит NPS 50+, отличный — 70+.
Аналитика для управляющего
Минимум, что должен видеть владелец/администратор в дашборде:
| Метрика | Хорошо | Тревога |
|---|---|---|
| Доля записей через бот | 40–60% | менее 20% |
| No-show rate | 3–6% | более 10% |
| Загруженность мастеров | 70–85% | менее 50% или более 95% |
| Средний чек | растёт МоМ | падает 2 месяца подряд |
| Retention 60 дней | 55–70% | менее 40% |
| NPS | 50+ | менее 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% — люди, которые уже хотели именно этот день, охотно соглашаются. Без листа ожидания освободившиеся слоты часто остаются пустыми до конца дня.