UTM Tracking
1. Summary
Goal: Система отслеживания маркетинговых кампаний — позволяет создавать короткие ссылки с UTM-параметрами, отслеживать переходы и конверсии, анализировать эффективность каналов привлечения.
User Value: Маркетолог получает точную аналитику по каждому источнику трафика: сколько кликов, сколько регистраций, какая конверсия. Красивые короткие ссылки (goloot.online/influencer/code) вместо длинных Telegram URL.
2. Business Logic
UTM Parameters
- Required
- Optional
Обязательные параметры:
| Параметр | Описание | Пример |
|---|---|---|
source | Источник трафика | telegram, youtube, vk |
medium | Способ размещения | cpc, social, influencer, banner |
campaign | Название кампании | winter_sale, new_users_2024 |
Ограничения: 1-30 символов (source/medium), 1-50 символов (campaign)
Опциональные параметры:
| Параметр | Описание | Пример |
|---|---|---|
term | Ключевое слово (для поиска) | rust skins |
content | Вариант объявления (A/B тест) | banner_v2 |
adType | Тип рекламы | video_horizontal, stories, banner |
influencer | Имя инфлюенсера | @blogger_name |
Ограничения:
| Параметр | Лимит |
|---|---|
term | 1-40 символов |
content | 1-40 символов |
adType | 1-30 символов |
influencer | 1-25 символов |
Short Link Format
Формат короткой ссылки: goloot.online/{influencer}/{code}
Внутреннее хранение: {influencer}_{code} (с underscore)
Отображение: {influencer}/{code} (со slash)
- Базовый код из первых символов параметров:
tel_vid_winter - При коллизии: добавляется счётчик
tel_vid_winter_1,tel_vid_winter_2 - При переполнении (>999): fallback на случайный 6-символьный код
Click Tracking Flow
Существуют два endpoint для UTM shortlink redirect:
- Redirect Service (
goloot.online/:shortCode) — основной production путь. ИнкрементируетlinkClicksCountв БД (fire-and-forget UPDATE). - Backend public route (
/utm/:influencer/:codeвpublic-utm.routes.ts) — чистый редирект без записи в БД ("NO DATABASE WRITES"). Клики трекаются позже через/authendpoint при открытии Mini App.
linkClicksCount инкрементируется только в redirect-service.
| Метрика | Поле | Источник |
|---|---|---|
| Link Clicks | linkClicksCount | redirect-service (переход по ссылке) |
| Bot Opens | clicksCount | Auth API (открытие Mini App) |
| Conversions | conversions | ActivationRewardService (завершение онбординга) |
CTR = conversions / clicksCount * 100% (Конверсии / Открытия бота)
InviteSession (Activation Domain)
UTM и Referrals используют сервисы из домена activation:
InviteSessionService— создание и поиск сессийActivationRewardService— активация и распределение наград
Модель InviteSession:
type: InviteType— UTM или REFERRALtelegramId— ID пользователя до регистрацииmetadata: Json— UTM параметры или referralCodestate— PENDING → ACTIVATED / EXPIRED
InviteSession — это общий механизм для трекинга любых "приглашений". UTM и Referrals — два варианта использования. Подробнее см. Activation.
Conversion Tracking
Конверсия считается когда:
- Пользователь перешёл по UTM-ссылке (зафиксировано в InviteSession)
- Пользователь завершил регистрацию (onboarding completed)
- Система устанавливает
UTMTracking.converted = true
Три метрики (воронка):
utm_campaigns.linkClicksCount— переходы по короткой ссылке (redirect-service)utm_campaigns.clicksCount— открытия Mini App через UTM (bot opens, InviteSession)utm_campaigns.conversions— завершённые регистрации (onboarding completed)
Формула CTR: CTR = conversions / clicksCount * 100% (Конверсии / Открытия бота)
Promo Code Integration
UTM-кампания может быть связана с промокодом (promoCodeId). Это позволяет:
- Отслеживать активации промокода от пользователей, пришедших по UTM
- Видеть на графике аналитики две линии: общие активации и активации от UTM
- Сравнивать эффективность разных кампаний по промокоду
Метрики промокода:
totalPromoRedemptions— все активации привязанного промокодаutmPromoRedemptions— активации от пользователей с UTM-визитом за последние 7 дней- На графике: фиолетовая линия (всего) и розовая линия (от UTM)
Активация промокода атрибутируется к UTM-кампании если пользователь посетил Mini App по любой UTM-ссылке в течение 7 дней до активации. Связь устанавливается через source+medium+campaign match.
Admin UI (Promo Code UX):
| Элемент | Описание |
|---|---|
| Колонка "Промокод" в таблице | Зелёный badge с кодом или прочерк "—" для кампаний без промокода |
| Секция "Промокод" в деталях кампании | Всегда отображается: badge или "Не привязан" (курсив) |
| Кнопка "+" в форме создания | Рядом с полем выбора промокода — открывает мини-диалог быстрого создания нового промокода (код + тип награды + количество) |
Normalization
UTM параметры автоматически нормализуются для консистентности:
| Ввод | Результат |
|---|---|
youtube, YOUTUBE, ютуб | YouTube |
tiktok, тикток | TikTok |
vk, вконтакте | VK |
shorts, reels | video_vertical |
Protection
| Действие | Rate Limit | Auth | Validation |
|---|---|---|---|
| Redirect (public) | shortlinks (100/min) | none | — |
| Create campaign | critical (5/min) | admin | UTMParametersSchema |
| List campaigns | admin (~50/min) | admin | UTMLinksQuerySchema |
| Toggle/Delete | critical (5/min) | admin | — |
| Analytics | analytics (30/min) | admin | AnalyticsQuerySchema |
| Tracking | tracking (60/min) | telegram | UTMTrackingSchema |
| Emergency | emergency (1/min) | — | При подозрительной активности |
Rate Limit Configs
Определены в backend/src/domains/utm/routes/middleware/utm-rate-limiting.middleware.ts:
| Config | Лимит | Назначение |
|---|---|---|
criticalRateLimitConfig | 5/min | Создание/изменение кампаний |
trackingRateLimitConfig | 60/min | Трекинг активности |
analyticsRateLimitConfig | 30/min | Аналитика |
shortlinksRateLimitConfig | 100/min | Публичные редиректы |
adminRateLimitConfig | ~50/min (GENERAL_REQUESTS_PER_MINUTE / 2) | Админские операции |
emergencyRateLimitConfig | 1/min | Экстренная защита |
См. Security Matrix для полного обзора защит.
Edge Cases
| Ситуация | UI поведение | Код |
|---|---|---|
| ❌ Кампания не найдена | Redirect на /start (fallback) | — |
| ❌ Кампания неактивна | Redirect на /start (fallback) | — |
| ❌ Short link истёк | Redirect на /start (fallback) | — |
| ❌ Дублирующая комбинация UTM | "Кампания с такими параметрами уже существует" | DUPLICATE |
| ❌ Заблокированное значение | "Значение заблокировано" | BLOCKED_VALUE |
| ✅ Успешный редирект | 302 → Telegram | — |
3. ADR (Architectural Decisions)
ADR 1: Lightweight Redirect Architecture (v4.0.0 → v5.0.0)
Проблема: Redirect сервис изначально не записывал клики в БД ("чистый редирект"). Это привело к тому, что clicksCount инкрементировался только при открытии Mini App — без разделения "переход по ссылке" и "открытие бота".
Решение (v5.0.0): Redirect сервис записывает только linkClicksCount (атомарный инкремент) — лёгкая запись без JOIN. Основная нагрузка (создание InviteSession, user lookup) по-прежнему в Auth API.
Альтернативы (отклонены):
- Async запись через очередь — усложняет архитектуру
- Redis counter + batch flush — дополнительная инфраструктура
Последствия: Redirect остаётся быстрым (один простой UPDATE). linkClicksCount точно считает переходы по ссылке независимо от открытий Mini App.
ADR 2: Агрегированные счётчики в utm_campaigns
Проблема: Подсчёт кликов через COUNT(*) в utm_tracking дорогой при большом объёме данных.
Решение: Денормализация: clicksCount и conversions хранятся в utm_campaigns и инкрементируются атомарно.
Альтернативы (отклонены):
- Материализованные представления — сложнее поддерживать
- Кэширование — inconsistency при инвалидации
Последствия: O(1) для получения статистики кампании. Trade-off: нужно инкрементировать при каждом событии.
ADR 3: Unique constraint (source, medium, campaign)
Проблема: Две кампании с одинаковыми UTM параметрами создают путаницу в аналитике.
Решение: Unique constraint на комбинацию (source, medium, campaign).
Альтернативы (отклонены):
- Разрешить дубликаты с разными ID — невозможно определить attribution
- Добавить timestamp в constraint — переусложнение
Последствия: Одна комбинация UTM = одна кампания. При необходимости новой кампании нужно изменить один из параметров.
ADR 4: Soft delete для UTM значений
Проблема: Удалённого инфлюенсера/источник можно создать заново, обойдя блокировку.
Решение: Таблица utm_deleted_values хранит заблокированные значения. При создании кампании проверяется отсутствие в этой таблице.
Последствия: Заблокированный инфлюенсер не сможет создать новую кампанию с тем же именем. Restore возможен через админку.
ADR 5: Разделение метрик кликов (v5.0.0)
Проблема: UTMCampaign.clicksCount инкрементировался дважды за один пользовательский путь:
- В
redirect-service— при переходе по короткой ссылке - В
invite-session.service— при создании InviteSession
Один пользователь = 2 "клика". Это двойной счёт.
Решение: Три отдельные метрики с чёткой семантикой:
| Метрика | Поле | Инкрементируется в |
|---|---|---|
| Link Clicks | linkClicksCount (новое) | redirect-service (переход по ссылке) |
| Bot Opens | clicksCount (существующее) | invite-session.service (открытие Mini App) |
| Conversions | conversions (существующее) | activation-reward.service (онбординг завершён) |
CTR считается от Bot Opens: CTR = conversions / clicksCount * 100%
Ключевые решения:
- Имя
clicksCountсохранено для обратной совместимости (семантика "bot opens") linkClicksCountстартует с 0 для всех существующих кампаний- Старые данные не мигрируются (исторические данные несущественны)
Альтернативы (отклонены):
- Переименовать
clicksCount→botOpensCount— нарушило бы backward compatibility API - Объединить в одну метрику с флагом — усложняет запросы аналитики
Последствия: Маркетологи видят полную воронку: Link Clicks → Bot Opens → Conversions. Диагностируется drop-off на каждом шаге.
4. Architecture
Services Overview
Key Components
| Компонент | Путь | Описание |
|---|---|---|
| UTMService | backend/src/domains/utm/services/utm.service.ts | Facade, делегирует к специализированным сервисам |
| UTMCampaignService | backend/src/domains/utm/services/utm-campaign.service.ts | CRUD кампаний |
| UTMShortLinksService | backend/src/domains/utm/services/utm-short-links.service.ts | Управление короткими ссылками |
| UTMAnalyticsService | backend/src/domains/utm/services/utm-analytics.service.ts | Аналитика промокодов для UTM |
| UTMValidator | backend/src/domains/utm/services/utils/utm-validator.ts | Валидация параметров |
| UTMNormalizer | backend/src/domains/utm/services/utils/utm-normalizer.ts | Нормализация значений |
| UTMAnalyticsUtils | backend/src/domains/utm/services/utils/utm-analytics-utils.ts | Расчёт трендов |
| UTMHashGenerator | backend/src/domains/utm/services/utils/utm-hash-generator.ts | Генерация уникальных shortCode |
| UTMUserService | backend/src/domains/utm/services/utils/utm-user.service.ts | Утилиты для работы с пользователями |
| InviteSessionService | backend/src/domains/activation/services/invite-session.service.ts | Создание UTM/Referral сессий |
| ActivationRewardService | backend/src/domains/activation/services/activation-reward.service.ts | Активация сессий и награды |
| Admin Controllers | backend/src/domains/utm/controllers/ | admin-utm-campaign, admin-utm-links, admin-utm-sources, admin-utm-values, admin-utm-analytics, admin-utm-autocomplete |
| Public Routes | backend/src/domains/utm/routes/public-utm.routes.ts | Redirect endpoint |
| Admin Routes | backend/src/domains/utm/routes/admin-utms.routes.ts | Admin API |
| Rate Limits | backend/src/domains/utm/routes/middleware/utm-rate-limiting.middleware.ts | UTM-специфичные rate limit конфиги |
| UTMFormFields | admin/src/components/utm/UTMGenerator/components/UTMFormFields.tsx | Поля формы UTM-генератора (источник, канал, тип, промокод + кнопка быстрого создания) |
| UTMTable | admin/src/components/utm/UTMTable.tsx | Таблица UTM-кампаний (включает колонку промокода) |
| UTMDetailsModal | admin/src/components/utm/UTMDetailsModal.tsx | Модалка деталей кампании (всегда показывает секцию промокода) |
5. Database Schema
Models
| Модель | Описание | Ключевые поля |
|---|---|---|
| UTMCampaign | Маркетинговая кампания | source, medium, campaign, shortCode, linkClicksCount, clicksCount, conversions, isActive, title, description, comment, term, content, adType, influencer, promoCodeId, imageUrl |
| ShortLink | Короткая ссылка | campaignId, shortCode, destinationUrl, redirectType, title, description, imageUrl, expiresAt |
| UTMTracking | Визит пользователя | source, medium, campaign, term, content, referrer, ip, userAgent, landingPage, userId, converted, conversionDate |
| UTMDeletedValue | Заблокированные значения | field, value, deletedBy, reason |
Relationships
Indexes
| Таблица | Index | Назначение |
|---|---|---|
| UTMCampaign | (source, medium, campaign) | UNIQUE — одна кампания на комбинацию |
| UTMCampaign | shortCode | Быстрый поиск для redirect |
| UTMCampaign | isActive | Фильтрация активных |
| UTMCampaign | influencer | Поиск кампаний инфлюенсера |
| UTMCampaign | campaignGroupId | Группировка кампаний |
| UTMCampaign | linkClicksCount | Сортировка по кликам по ссылке |
| UTMCampaign | clicksCount | Сортировка по открытиям бота |
| UTMCampaign | conversions | Сортировка по конверсиям |
| UTMCampaign | startDate | Фильтрация по дате начала |
| UTMCampaign | promoCodeId | Быстрый поиск кампаний по промокоду |
| ShortLink | shortCode | Быстрый redirect |
| ShortLink | campaignId | Поиск ссылок кампании |
| ShortLink | isActive | Фильтрация активных |
| ShortLink | expiresAt | Cron для деактивации |
| UTMTracking | source | Группировка аналитики по источнику |
| UTMTracking | campaign | Группировка аналитики по кампании |
| UTMTracking | userId | Конверсии пользователя |
| UTMTracking | converted | Фильтрация конвертированных |
| UTMTracking | createdAt | Аналитика по временным диапазонам |
| UTMDeletedValue | (field, value) | UNIQUE — проверка при создании |
6. API Endpoints
- Public
- Admin: Campaigns
- Admin: Links
- Admin: Analytics
- Admin: Autocomplete
- Admin: Sources
- Admin: Values
| Метод | Эндпоинт | Описание | Docs |
|---|---|---|---|
| GET | /utm/:influencer/:code | Redirect на Telegram | → |
Response: 302 Redirect → t.me/goloot_bot?start={shortCode}
| Метод | Эндпоинт | Описание |
|---|---|---|
| POST | /admin/utm/links | Создать кампанию |
| GET | /admin/utm/campaigns | Список кампаний |
| GET | /admin/utm/campaigns/by-influencer/:influencer | Кампании по инфлюенсеру |
| GET | /admin/utm/campaigns/:id | Детали кампании |
| DELETE | /admin/utm/campaigns/:id | Удалить кампанию |
| PATCH | /admin/utm/campaigns/:id/toggle-status | Вкл/выкл статус кампании |
| POST | /admin/utm/short-link | Создать короткую ссылку |
| GET | /admin/utm/short-link/:id/stats | Статистика короткой ссылки |
| GET | /admin/utm/examples | Примеры UTM ссылок |
| Метод | Эндпоинт | Описание |
|---|---|---|
| GET | /admin/utm/links | Список UTM ссылок (с пагинацией, фильтрами) |
| GET | /admin/utm/links/stats | Сводная статистика ссылок |
| PATCH | /admin/utm/links/:linkId/comment | Обновить комментарий |
| PATCH | /admin/utm/links/:linkId/toggle | Переключить статус ссылки |
| DELETE | /admin/utm/links/:linkId | Удалить ссылку |
| Метод | Эндпоинт | Описание |
|---|---|---|
| GET | /admin/utm/constants | UTM константы |
| GET | /admin/utm/stats | Сводная статистика |
| GET | /admin/utm/export | Экспорт статистики (CSV/JSON) |
| GET | /admin/utm/detailed-stats | Детальная аналитика (+ promo data, charts) |
Каскадная фильтрация:
| Метод | Эндпоинт | Описание |
|---|---|---|
| GET | /admin/utm/influencers-autocomplete | Инфлюенсеры для автодополнения |
| GET | /admin/utm/campaigns-by-influencer | Кампании по инфлюенсеру |
| GET | /admin/utm/sources-by-influencer-campaign | Источники по инфлюенсеру+кампании |
| GET | /admin/utm/mediums-by-campaign | Каналы по кампании |
| GET | /admin/utm/mediums-by-influencer-campaign-source | Каналы по полной цепочке |
| GET | /admin/utm/adtypes-by-campaign | Типы рекламы по кампании |
| GET | /admin/utm/adtypes-by-influencer-campaign-source-medium | Типы рекламы по полной цепочке |
Независимые фильтры:
| Метод | Эндпоинт | Описание |
|---|---|---|
| GET | /admin/utm/all-campaigns | Все кампании |
| GET | /admin/utm/all-sources | Все источники |
| GET | /admin/utm/all-mediums | Все каналы |
| GET | /admin/utm/all-adtypes | Все типы рекламы |
| Метод | Эндпоинт | Описание |
|---|---|---|
| GET | /admin/utm/influencers | Список инфлюенсеров |
| GET | /admin/utm/influencers/autocomplete | Автодополнение инфлюенсеров |
| GET | /admin/utm/autocomplete/sources | Автодополнение источников |
| GET | /admin/utm/autocomplete/mediums | Автодополнение каналов |
| GET | /admin/utm/autocomplete/ad-types | Автодополнение типов рекламы |
| GET | /admin/utm/autocomplete | Универсальное автодополнение (по field query param) |
| GET | /admin/utm/fields/:field/autocomplete | Автодополнение по URL-параметру поля |
| GET | /admin/utm/campaign | Alias: автодополнение кампаний |
| GET | /admin/utm/source | Alias: автодополнение источников |
| GET | /admin/utm/medium | Alias: автодополнение каналов |
| GET | /admin/utm/adType | Alias: автодополнение типов рекламы |
Каскадные фильтры:
| Метод | Эндпоинт | Описание |
|---|---|---|
| GET | /admin/utm/sources/by-influencer-campaign | Источники по инфлюенсеру+кампании |
| GET | /admin/utm/mediums/by-campaign | Каналы по кампании |
| GET | /admin/utm/ad-types/by-campaign | Типы рекламы по кампании |
Независимые фильтры:
| Метод | Эндпоинт | Описание |
|---|---|---|
| GET | /admin/utm/independent/all-sources | Все источники |
| GET | /admin/utm/independent/all-mediums | Все каналы |
| GET | /admin/utm/independent/all-adtypes | Все типы рекламы |
| GET | /admin/utm/independent/all-campaigns | Все кампании |
| Метод | Эндпоинт | Описание |
|---|---|---|
| GET | /admin/utm/sources | Уникальные источники (с опциональной статистикой) |
| GET | /admin/utm/mediums | Уникальные каналы (с опциональной статистикой) |
| GET | /admin/utm/ad-types | Уникальные типы контента (с опциональной статистикой) |
| Метод | Эндпоинт | Описание |
|---|---|---|
| GET | /admin/utm/values/deleted | Заблокированные значения |
| GET | /admin/utm/values/stats | Статистика удалённых значений |
| DELETE | /admin/utm/values/delete | Заблокировать значение (body: field, value, reason) |
| POST | /admin/utm/values/restore | Восстановить значение (body: field, value) |
7. Related
- Activation — домен InviteSession: создание и активация сессий
- Promo Codes — промокоды атрибутируются к UTM-кампаниям
- Referrals — реферальная система (также использует activation domain)
- Glossary — термины: UTM, ShortLink, Conversion, InviteSession
- Security Matrix — защиты UTM эндпоинтов
UTM и Referrals используют общие компоненты из activation домена:
InviteSessionService— создание и поиск сессийActivationRewardService— активация и наградыInviteSessionмодель — общая модель в Prisma schema