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

White-label SaaS-бот на платформе MAX: архитектура и монетизация

Как построить white-label SaaS-бот в MAX: мультитенантность, биллинг, кастомизация под клиента, изоляция данных, deployment-модели и стоимость.

  • MAX
  • SaaS
  • архитектура

White-label SaaS-бот — это когда вы один раз построили платформу, а потом продаёте её десяткам/сотням клиентов под их брендом, цветами, доменами и логикой. На платформе MAX от VK Tech это особенно интересный сегмент: 2026 год — взрывной рост ботов в РФ, ниши не насыщены, и студия с одним правильным SaaS-продуктом может за год набрать 50–200 клиентов с MRR 0.5–5 млн ₽. В этой статье — как спроектировать white-label SaaS-бот для MAX: мультитенантная архитектура, изоляция данных, биллинг, кастомизация без правки кода, deployment-модели (shared / dedicated / on-prem) и финансовая модель.

Что такое white-label SaaS-бот

СвойствоЧто значит
White-labelКлиент использует бот под своим брендом, без упоминания вас
SaaSПодписочная модель, без покупки лицензии
Multi-tenantОдин код, несколько изолированных тенантов
Self-serviceКлиент сам подключает свой токен бота, настраивает контент
БиллингАвтоматическое выставление счетов и блокировка при неоплате

Примеры ниш: бот для записи (салоны, клиники, барбершопы), бот-магазин для малого бизнеса, бот-каталог для производителей мебели, HR-бот для подбора курьеров, бот для микрорассылок.

Уровни мультитенантности

Три модели:

  1. Shared everything — один Postgres, общие таблицы с полем tenant_id. Дёшево, масштабируется хорошо, риск утечки между тенантами при ошибке в коде.
  2. Shared schema, separate row-level security — RLS на уровне Postgres, query автоматически фильтруется по tenant. Безопаснее.
  3. Schema-per-tenant — отдельная Postgres-схема на каждого. Изоляция полная, но миграции — на каждой схеме.
  4. DB-per-tenant — отдельная база. Полная изоляция, но дорого и сложно масштабировать.

Для SaaS-бота на старте — shared schema + tenant_id + RLS. Для крупных корпоративных клиентов с особыми требованиями — schema-per-tenant.

Модель данных

CREATE TABLE tenants (
    id BIGSERIAL PRIMARY KEY,
    slug TEXT UNIQUE NOT NULL,         -- "salon-vesna"
    title TEXT NOT NULL,
    bot_token TEXT,                    -- токен MAX-бота клиента
    plan TEXT DEFAULT 'free',
    status TEXT DEFAULT 'active',      -- active|suspended|trial
    trial_ends_at TIMESTAMPTZ,
    created_at TIMESTAMPTZ DEFAULT now(),
    settings JSONB DEFAULT '{}'        -- бренд, цвета, тексты
);

CREATE TABLE users (
    id BIGSERIAL PRIMARY KEY,
    tenant_id BIGINT NOT NULL REFERENCES tenants(id),
    messenger_user_id BIGINT NOT NULL,
    first_name TEXT,
    UNIQUE (tenant_id, messenger_user_id)
);

CREATE INDEX ix_users_tenant ON users(tenant_id);

-- Для каждой бизнес-таблицы — tenant_id
CREATE TABLE appointments (
    id BIGSERIAL PRIMARY KEY,
    tenant_id BIGINT NOT NULL REFERENCES tenants(id),
    user_id BIGINT REFERENCES users(id),
    -- ...
);

ALTER TABLE appointments ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON appointments
    FOR ALL USING (tenant_id = current_setting('app.tenant_id')::bigint);

В коде в начале каждого запроса:

async def with_tenant(conn, tenant_id: int):
    await conn.execute(f"SET LOCAL app.tenant_id = {tenant_id}")

После этого все запросы автоматически фильтруются — никакой риск SELECT * без tenant_id не приведёт к утечке.

Роутинг сообщений на правильный tenant

Один процесс может обслуживать сразу всех тенантов. Идея — у каждого клиента свой токен MAX-бота, но webhook у всех — один общий вида /webhook/<tenant_slug>. Slug извлекаем из URL:

@app.post("/webhook/{tenant_slug}")
async def on_webhook(tenant_slug: str, request: Request, x_secret: str | None = Header(None)):
    tenant = await get_tenant_by_slug(tenant_slug)
    if not tenant or tenant.status != "active":
        raise HTTPException(404)
    if not hmac.compare_digest(tenant.webhook_secret, x_secret or ""):
        raise HTTPException(401)
    update = await request.json()
    async with db.acquire() as conn:
        await with_tenant(conn, tenant.id)
        await dispatch(tenant, update, conn)
    return {"ok": True}

В MAX-кабинете каждого клиента webhook регистрируется на свой URL https://saas.example.ru/webhook/salon-vesna.

Кабинет клиента

Веб-приложение, где клиент:

  • подключает токен своего MAX-бота;
  • настраивает бренд, цвета, тексты;
  • управляет содержимым (услуги, расписание, прайс);
  • видит метрики и историю;
  • управляет подпиской и платежами.

Авторизация по email + password или SSO. Доступ к данным — только своего тенанта (через RLS).

Onboarding нового клиента

1. Регистрация в кабинете → email/телефон → trial 14 дней.
2. Создаётся tenant, slug, webhook_secret.
3. Клиент создаёт бота в MAX (через @MasterBot или аналог), копирует токен.
4. Вставляет токен в кабинет.
5. Платформа делает setWebhook на https://saas.example.ru/webhook/{slug}.
6. Клиент заполняет начальный контент (услуги/прайс) или импортирует CSV.
7. Готово, бот работает под брендом клиента.

Цель — первый запуск работающего бота за 5–10 минут без участия техподдержки.

Кастомизация без правки кода

Бренд, цвета, тексты — JSONB в tenants.settings:

{
  "brand": {
    "name": "Salon Vesna",
    "logo_url": "https://cdn.../logo.png",
    "color": "#5B8DFF"
  },
  "messages": {
    "greeting": "Здравствуйте! Это бот {brand_name}.",
    "booking_confirmed": "✅ Запись на {date} подтверждена",
    "reminder_24h": "Напоминаем, завтра в {time} вы записаны на {service}"
  },
  "features": {
    "loyalty_program": true,
    "online_payment": false,
    "reviews": true
  }
}

В коде:

def t(tenant: Tenant, key: str, **kw) -> str:
    template = tenant.settings.get("messages", {}).get(key, DEFAULT_MESSAGES[key])
    return template.format(brand_name=tenant.settings["brand"]["name"], **kw)

await bot.send_message(chat_id, t(tenant, "booking_confirmed", date="15 марта", time="14:00", service="стрижка"))

Для глубокой кастомизации (новые типы услуг, нестандартные сценарии) — feature flags + редактируемые шаблоны.

Тарифы и биллинг

Типичный SaaS-pricing для бот-платформы:

ТарифЦена/месОграничения
Free / Trial014 дней, до 100 пользователей
Start1 990 ₽до 1 000 активных, базовые функции
Pro4 990 ₽до 10 000, доп. функции, рассылки
Business14 990 ₽до 50 000, white-label, API
Enterpriseот 50 000 ₽безлимит, dedicated, SLA, кастомизация

Биллинг — через ЮKassa / Тинькофф Эквайринг, рекуррентные платежи. При неоплате — суспенд с graceful degrade:

  • день 1 — push с напоминанием;
  • день 3 — баннер в кабинете;
  • день 7 — отключение исходящих сообщений;
  • день 30 — полный suspend, данные сохраняются 90 дней.
async def cron_billing():
    overdue = await db.fetch_all("""
        SELECT * FROM tenants 
        WHERE status='active' 
          AND next_billing_at < now() - interval '7 days'
    """)
    for t in overdue:
        await suspend(t.id)
        await notify_owner(t.id, "Подписка приостановлена. Оплатите для возобновления работы.")

Лимиты и квоты

Каждый тариф — свои лимиты. Реализация на уровне rate limit:

TIER_LIMITS = {
    "start":    {"users": 1000,  "broadcasts_per_month": 4},
    "pro":      {"users": 10000, "broadcasts_per_month": 20},
    "business": {"users": 50000, "broadcasts_per_month": 100},
}

async def check_users_limit(tenant: Tenant) -> bool:
    cur = await db.fetch_val("SELECT count(*) FROM users WHERE tenant_id = $1", tenant.id)
    limit = TIER_LIMITS[tenant.plan]["users"]
    return cur < limit

Превышение → push «Лимит превышен, обновите тариф» + блокировка роста.

Deployment-модели

МодельКомуСтоимость
Shared SaaSмалый бизнесминимум, окупается с 50+ клиентов
Dedicated SaaSсредний бизнесотдельный namespace в k8s, +50–100%
On-prementerprise, банкиразовый платёж + поддержка

On-prem имеет смысл при ARR 1.5+ млн ₽/клиент или жёстких требованиях по информационной безопасности.

Изоляция инцидентов

Один тенант не должен валить других. Меры:

  • rate limit на уровне tenant_id (отдельный bucket для каждого);
  • лимит ресурсов в k8s по namespace (если Dedicated);
  • circuit breaker на исходящие API (если у одного клиента сломалась интеграция CRM);
  • алерты в Prometheus с label tenant_id для мониторинга «кто шумит».

Финансовая модель

ПараметрЗначение
Средний чек (ARPU)4 000 ₽/мес
Количество клиентов на 12 мес100–200
MRR400 тыс. – 800 тыс. ₽
Churn в месяц3–7%
LTVARPU / churn = 13–33 ARPU = 50–130 тыс. ₽
CAC5–15 тыс. ₽
LTV/CAC5–15 (хорошая модель)

Стоимость инфры на 100 клиентов: 30–80 тыс. ₽/мес (k8s 50–100K MAU суммарно). Маржа в зрелом состоянии — 60–80%.

Common pitfalls

  1. Один общий webhook без HMAC по тенанту — атакующий шлёт обновления чужого бота.
  2. Шеринг данных через JOIN без tenant_id — катастрофическая утечка.
  3. Один Redis для rate limit без префикса tenant_id — клиент A blocks клиента B.
  4. Миграции без проверки на больших тенантах — на 1000 строках работает, на 1М — лежит.
  5. Бесплатный trial без CC — масс-регистрация ботов и спам.
  6. «Кастомизация» через прямой доступ к БД — невозможно мигрировать, обновлять.

Итого

White-label SaaS-бот на MAX строится по схеме: shared schema с tenant_id и RLS (или schema-per-tenant для крупняка), один процесс обслуживает всех тенантов, webhook вида /webhook/{slug} с HMAC по секрету тенанта, кабинет клиента с self-service onboarding (5–10 минут до запуска), кастомизация через JSONB settings (бренд, тексты, feature flags), биллинг через ЮKassa с рекуррентами и graceful degrade при неоплате, лимиты по тарифу, изоляция через per-tenant rate limit и Prometheus labels. Финансовая модель: ARPU 4 тыс. ₽, 100–200 клиентов за год = MRR 0.4–0.8 млн ₽, маржа 60–80% при инфре 30–80 тыс. ₽/мес. Срок MVP — 8–14 недель и 3–5 млн ₽; время до окупаемости — 6–14 месяцев в зависимости от ниши и каналов привлечения.

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

Какую модель мультитенантности выбрать?

Для большинства SaaS-ботов на старте — shared schema с tenant_id во всех таблицах + Row Level Security в Postgres. Это даёт хорошую изоляцию (RLS защищает от случайных утечек при ошибке в SQL), легко масштабируется и не требует миграций на каждой схеме. Schema-per-tenant имеет смысл для enterprise-клиентов с особыми требованиями. DB-per-tenant — только для очень крупных или регулируемых индустрий (банки, медицина), потому что управлять сотней баз данных дорого и сложно.

Как роутить webhook MAX на правильного тенанта?

У каждого клиента свой токен бота, но webhook у всех на общем сервере вида https://saas.example.ru/webhook/\{tenant_slug\}. При регистрации бота в MAX вы вызываете setWebhook с этим URL и уникальным secret_token для тенанта. На входящем запросе извлекаете slug из URL, находите тенант, проверяете подпись secret_token (hmac.compare_digest), устанавливаете SET LOCAL app.tenant_id и обрабатываете update. Это позволяет одному процессу обслуживать сотни тенантов без отдельных деплоев.

Как кастомизировать бота под бренд клиента без правки кода?

Все кастомизируемые элементы (название, цвета, тексты, feature flags) храните в tenants.settings JSONB. Шаблоны сообщений с placeholder'ами вида {brand_name}, {service}, {time}. Клиент в кабинете редактирует тексты через UI, бот подставляет их при отправке. Для продвинутой кастомизации — feature flags ({features: {loyalty: true, payments: false}}) включают/отключают модули. Глубокую кастомизацию (новые типы сценариев) делайте через расширения на уровне платформы, а не правкой кода для одного клиента.

Как настроить биллинг и блокировку при неоплате?

Рекуррентные платежи через ЮKassa или Тинькофф Эквайринг (auto-payment с привязанной карты). Cron раз в день проверяет next_billing_at, пытается списать, при успехе — продлевает; при неудаче — последовательность graceful degrade: день 1 push, день 3 баннер в кабинете, день 7 блокировка исходящих сообщений (бот всё ещё принимает, но не отвечает), день 30 полный suspend, данные сохраняются 90 дней. Это даёт клиенту шанс восстановить подписку без потери данных и снижает churn от технических причин.

Какие тарифы установить для SaaS-бота?

Типичная сетка: Free trial 14 дней, Start 1 990 ₽ (до 1K пользователей, базовые функции), Pro 4 990 ₽ (до 10K, расширенные функции и рассылки), Business 14 990 ₽ (до 50K, white-label, API), Enterprise от 50 000 ₽ (безлимит, dedicated, SLA, кастомизация). Конкретные числа зависят от ниши: для малого бизнеса (запись в салоны) median 2–4 тыс. ₽; для среднего (онлайн-курсы, маркетплейсы) — 5–15 тыс. ₽. Гайдлайн: ARPU должен быть в 5–15 раз выше CAC, иначе unit-экономика плохая.

Как защитить тенантов друг от друга?

Несколько слоёв: RLS в Postgres гарантирует, что SQL не может прочесть чужие данные даже при ошибке в коде; rate limit в Redis с префиксом tenant_id (один шумный клиент не блокирует других); circuit breaker на исходящие API (поломанная интеграция CRM одного клиента не валит весь сервис); ресурсные лимиты в k8s по namespace для Dedicated; алерты в Prometheus с label tenant_id для быстрого выявления «кто шумит». Регулярные пентесты на multi-tenant escape — обязательно при наличии корпоративных клиентов.

Сколько занимает разработка и какова окупаемость?

MVP white-label SaaS-бота с одним сценарием (например, «бот для записи в салоны»), кабинетом клиента, биллингом, кастомизацией бренда — 8–14 недель и 3–5 млн ₽. Полная платформа с 2–3 сценариями, расширенными настройками, мобильным кабинетом, API, маркетплейсом дополнений — 6–9 месяцев и 8–15 млн ₽. Точка безубыточности при ARPU 4 тыс. ₽ и инфраструктуре 50 тыс. ₽/мес — около 30 платных клиентов. Реалистичная цель за год — 100–200 клиентов и MRR 0.4–0.8 млн ₽. Окупаемость инвестиций — 12–24 месяца при сильном продакт-маркет-фите.