-
Notifications
You must be signed in to change notification settings - Fork 5
CRM
Интеграция с amoCRM v4 API: OAuth2 аутентификация, синхронизация контактов, лидов и задач, автоматическое создание сделок.

Интеграция с amoCRM v4 API через OAuth2 с автоматическим обновлением токенов. Система позволяет:
- Двусторонняя синхронизация: импорт контактов и лидов из amoCRM → экспорт данных из бота в CRM
- Автоматическое создание сделок: при обращении в Telegram бот или виджет
- Webhook-интеграция: получение событий от amoCRM (обновление контактов, сделок, задач)
- Управление воронками: выбор pipeline и статуса для новых лидов
- Docker/VPN proxy: поддержка прокси для работы из контейнера
Управление интеграцией через CrmView.vue с 4 вкладками: Settings (настройки), Kanban (доска сделок), Deals (таблица сделок), Inbox (сообщения).
Визуальная доска сделок в стиле Trello/Kanban (CrmKanban.vue):
| Функция | Описание |
|---|---|
| Выбор воронки | Выпадающий список всех pipelines из amoCRM |
| Колонки по статусам | Горизонтальные колонки — каждая соответствует статусу воронки |
| Drag & Drop | Перетаскивание сделок между статусами через vuedraggable@next (SortableJS) |
| Изменяемая ширина колонок | Перетаскивание разделителя между колонками, ширина сохраняется в localStorage |
| Сворачивание колонок | Кнопка ChevronLeft в заголовке сворачивает колонку в узкую вертикальную полоску (48px) с цветной точкой, счётчиком и вертикальным названием. Клик по свёрнутой полоске раскрывает обратно. Состояние сохраняется в localStorage (crm-kanban-collapsed). Разделители ресайза скрываются между свёрнутыми колонками |
| Сброс ширины | Кнопка ↺ сбрасывает все колонки до ширины по умолчанию (288px) |
| Горизонтальная прокрутка | Drag-to-scroll по горизонтали |
| Ленивая загрузка | Карточки подгружаются порциями по 30 шт., IntersectionObserver автоматически загружает ещё при прокрутке |
| Неразобранные сделки | Автоматически подгружаются в первую колонку воронки (статус «Неразобранное») |
| Авто-обновление | Каждые 30 секунд |
| Карточка сделки | Название, сумма, ответственный, контакт |
Табличный вид всех сделок (CrmDeals.vue):
| Функция | Описание |
|---|---|
| Поиск | Фильтрация сделок по названию |
| Пагинация | Кнопки «назад/вперёд» в тулбаре (между поиском и кнопкой «Создать») |
| Детали сделки | Модальное окно: контакты, примечания, теги |
| Создание сделки | Диалог с полями: название, сумма, воронка, статус, контакт |
Unified inbox с двумя подвкладками (CrmInbox.vue):
Все клиентские диалоги с ИИ-ботами, сгруппированные по источнику:
| Функция | Описание |
|---|---|
| Группировка по источнику | Сессии разделены на сворачиваемые группы: Telegram, WhatsApp, Widget |
| Сворачиваемые группы | Chevron-toggle (▶/▼) на заголовке группы, со счётчиком сессий |
| Поиск | Фильтрация сессий по названию и тексту последнего сообщения (по всем группам) |
| Иконки источника | Каждый источник отображается своей иконкой: Send (Telegram), MessageCircle (WhatsApp), Globe (Widget) |
| Просмотр переписки | Выбор сессии → сообщения с markdown-рендерингом (marked + DOMPurify) |
| Ответ через стриминг | Ввод текста → chatApi.streamMessage() → streaming AI reply |
| Авто-обновление | Каждые 15 сек — список сессий, каждые 10 сек — сообщения |
Данные: используется
GET /admin/chat/sessions?group_by=source(группы: telegram, whatsapp, widget; admin-чаты исключены).
Мессенджер-интерфейс для переписки с контактами через Amojo API (CrmInboxAmoCRM.vue). Видна только если настроены Amojo credentials.
| Функция | Описание |
|---|---|
| Список контактов | Поиск контактов из amoCRM |
| Разрешение чата | Выбор контакта → автоматический запрос getContactChats() → получение UUID чата |
| История сообщений | Загрузка переписки из Amojo API |
| Отправка сообщений | Ввод текста → POST /admin/crm/chats/{chat_id}/messages
|
| Авто-обновление | Каждые 10 секунд |
Требования: для amoCRM Inbox необходимо настроить Amojo credentials (Amojo Scope ID, Channel Secret) во вкладке Settings.
| Индикатор | Описание |
|---|---|
| 🟢 Подключено | OAuth2 токены активны, аккаунт доступен |
| 🔴 Не подключено | Нет токенов или истекли |
| 🟡 Ошибка | Ошибка при последнем запросе |
| Поле | Описание |
|---|---|
| Subdomain | Поддомен amoCRM (например, mycompany для mycompany.amocrm.ru) |
| Client ID | OAuth2 Client ID из настроек интеграции |
| Client Secret | OAuth2 Client Secret |
| Redirect URI | URI для OAuth callback (автозаполняется: https://yourdomain.com/admin/crm/callback) |
| Sync Contacts | Включить синхронизацию контактов |
| Sync Leads | Включить синхронизацию лидов |
| Sync Tasks | Включить синхронизацию задач |
| Auto Create Lead | Автоматически создавать лид при первом обращении |
| Lead Pipeline ID | ID воронки для новых лидов |
| Lead Status ID | ID статуса для новых лидов |
- Зайдите в Настройки → Интеграции → Создать интеграцию
- Выберите тип Private app (для приватного доступа) или OAuth2 (для публичной интеграции)
- Скопируйте Client ID и Client Secret
- Укажите Redirect URI:
https://yourdomain.com/admin/crm/callback - Выберите права доступа (scope):
crm(контакты, сделки, задачи)
- Вставьте Subdomain, Client ID и Client Secret в форму
- Нажмите "Получить URL авторизации" → появится кнопка "Подключить amoCRM"
- Кликните на кнопку → откроется popup с OAuth-страницей amoCRM
- Подтвердите доступ в amoCRM
- После редиректа система получит токены и отобразит статус "Подключено"
Для приватных интеграций amoCRM код авторизации (authorization_code) получается из настроек интеграции:
- Создайте приватную интеграцию в amoCRM
- Скопируйте код авторизации из раздела "Получить код"
- Используйте этот код вместо OAuth2 flow
- Система автоматически обменяет код на токены
После подключения отображается:
| Поле | Описание |
|---|---|
| Account Name | Название аккаунта |
| Subdomain | Поддомен |
| User Name | Имя пользователя |
| User Email | Email пользователя |
| Кнопка | Действие |
|---|---|
| Синхронизировать | Запустить синхронизацию контактов/лидов |
| Отключить | Отозвать токены OAuth2 |
История операций синхронизации:
| Поле | Описание |
|---|---|
| Timestamp | Дата и время |
| Operation | Тип операции (sync_contacts, sync_leads, create_lead) |
| Status | Статус (success, error) |
| Details | Детали (количество синхронизированных записей, ошибки) |
| Метрика | Описание |
|---|---|
| Contacts Count | Количество контактов в amoCRM |
| Leads Count | Количество сделок |
| Last Sync Time | Время последней успешной синхронизации |
Синхронизация данных amoCRM в RAG-коллекцию для поиска по сделкам через LLM. Позволяет ИИ-секретарю отвечать на вопросы про конкретные сделки, клиентов, суммы и менеджеров.
-
Сбор данных —
POST /admin/crm/dataset-syncзагружает все воронки, сделки, контакты и пользователей из amoCRM -
Обогащение — контакты дополняются телефонами/email (
get_contacts_by_ids()), ответственные менеджеры резолвятся в имена (get_users()) -
Генерация markdown — по каждой воронке создаётся
crm-pipeline-{id}.md+ общая сводкаcrm-summary.md -
Индексация — файлы пишутся в
data/crm-dataset/, создаётся коллекция "amoCRM", BM25/embedding индекс обновляется
Каждая сделка записывается как секция с полными данными для поиска:
## Сделка #8847291: Разработка сайта — Переговоры (100 000 ₽)
- **ID сделки**: 8847291
- **Сумма**: 100 000 ₽ (100000)
- **Контакт**: Иванов Пётр | тел: +7 (999) 123-45-67 (79991234567) | email: ivanov@mail.ru
- **Тип продукта**: Корпоративный сайт
- **Источник**: Яндекс Директ
- **Теги**: VIP, срочно
- **Создана**: 15.01.2026
- **Обновлена**: 18.02.2026
- **Ответственный**: Алексей Сидоров| Проблема | Решение |
|---|---|
Форматированная цена 100 000 не матчит запрос 100000
|
Цена дублируется: 100 000 ₽ (100000)
|
Телефон +7 (999) 123-45-67 фрагментируется токенизатором |
Digits-only дубль: (79991234567)
|
responsible_user_id — числовой ID |
Резолвится в имя менеджера через get_users()
|
| Кастомные поля (товар, источник) игнорировались |
custom_fields_values извлекаются в markdown |
| ID сделки не записывался | Заголовок ## Сделка #8847291: + строка **ID сделки**
|
| Метод | Endpoint | Описание |
|---|---|---|
| POST | /admin/crm/dataset-sync |
Полная синхронизация: воронки + сделки + контакты + пользователи → markdown → RAG |
| GET | /admin/crm/dataset-status |
Статус: количество документов, секций, дата последней синхронизации |
| DELETE | /admin/crm/dataset |
Очистить датасет (только admin) |
{
"status": "ok",
"pipelines": 3,
"leads_total": 150,
"contacts_enriched": 89,
"users_resolved": 5,
"files_written": 4,
"files_removed": 4,
"collection_id": 2,
"synced_at": "19.02.2026 14:30"
}app/routers/amocrm.py (crm_dataset_sync)
├── get_pipelines() → воронки + статусы
├── get_all_leads_paginated() → все сделки (250/стр)
├── get_contacts_by_ids() → контакты с телефонами/email (50/батч)
├── get_users() → пользователи → {id: имя}
└── app/services/crm_dataset_service.py
├── build_pipeline_document() → markdown по воронке
└── build_summary_document() → сводка + статистика
Файлы:
data/crm-dataset/crm-pipeline-*.md,data/crm-dataset/crm-summary.mdКоллекция: slugamocrm,base_dir="data/crm-dataset"
Если Docker-контейнер не может напрямую достучаться до amoCRM (например, VPN настроен на хосте), используйте прокси:
python scripts/amocrm_proxy.pyПрокси запустится на порту 8888 и пробросит запросы к amoCRM.
Добавьте в .env:
AMOCRM_PROXY=http://172.17.0.1:8888Где 172.17.0.1 — IP хост-машины из Docker-сети.
Endpoint для получения событий от amoCRM: POST /webhooks/amocrm
- Перейдите в Настройки → Интеграции → Вебхуки
- Укажите URL:
https://yourdomain.com/webhooks/amocrm - Выберите события:
add,update,deleteдля контактов, сделок, задач - Сохраните
| Событие | Описание |
|---|---|
contacts:add |
Создан контакт |
contacts:update |
Обновлён контакт |
leads:add |
Создана сделка |
leads:update |
Обновлена сделка |
leads:status |
Изменён статус сделки |
tasks:add |
Создана задача |
tasks:update |
Обновлена задача |
Если включено Auto Create Lead, при первом обращении пользователя из Telegram бота или виджета:
- Система проверяет, существует ли контакт с этим Telegram User ID
- Если нет — создаёт контакт
- Создаёт сделку (лид) в указанной воронке (
Lead Pipeline ID) и статусе (Lead Status ID) - Связывает сделку с контактом
| Метод | Endpoint | Описание |
|---|---|---|
| GET | /admin/crm/config |
Получить конфигурацию CRM |
| PUT | /admin/crm/config |
Обновить конфигурацию |
| GET | /admin/crm/auth-url |
Получить URL для OAuth2 авторизации |
| GET | /admin/crm/callback |
OAuth2 callback (обмен кода на токены) |
| GET | /admin/crm/status |
Статус подключения (токены активны?) |
| POST | /admin/crm/disconnect |
Отключить amoCRM (отозвать токены) |
| GET | /admin/crm/contacts |
Список контактов из amoCRM |
| GET | /admin/crm/leads |
Список сделок из amoCRM |
| GET | /admin/crm/pipelines |
Список воронок и статусов |
| POST | /admin/crm/sync |
Запустить синхронизацию |
| GET | /admin/crm/sync/log |
Получить лог синхронизации |
| GET | /admin/crm/account |
Информация об аккаунте |
| GET | /admin/crm/leads/{id} |
Детали сделки |
| PATCH | /admin/crm/leads/{id} |
Обновить сделку (статус, pipeline) |
| GET | /admin/crm/leads/by-pipeline/{pipeline_id} |
Сделки по воронке (для Kanban) |
| GET | /admin/crm/events |
Лента событий |
| GET | /admin/crm/contacts/{id}/chats |
Чаты контакта (Amojo) |
| GET | /admin/crm/chats/{chat_id}/history |
История сообщений чата (Amojo) |
| POST | /admin/crm/chats/{chat_id}/messages |
Отправить сообщение в чат (Amojo) |
| POST | /admin/crm/dataset-sync |
Синхронизация данных в RAG-коллекцию |
| GET | /admin/crm/dataset-status |
Статус CRM-датасета |
| DELETE | /admin/crm/dataset |
Очистить CRM-датасет (admin only) |
GET /admin/crm/contacts?limit=50&offset=0
Authorization: Bearer <jwt_token>{
"_embedded": {
"contacts": [
{
"id": 12345,
"name": "Иван Иванов",
"custom_fields_values": [
{
"field_code": "PHONE",
"values": [{"value": "+79001234567"}]
},
{
"field_code": "EMAIL",
"values": [{"value": "ivan@example.com"}]
}
]
}
]
}
}Общая ошибка API amoCRM:
raise AmoCRMAPIError(f"API error: {response.status_code}")Истёк токен доступа (401):
raise AmoCRMTokenExpired("Access token expired")Система автоматически обновляет токен при получении 401 и повторяет запрос.
При превышении лимита запросов (429 Too Many Requests):
- Максимум повторов: 3 (
MAX_429_RETRIES) - Задержка между повторами: 1.5 секунды (
RETRY_DELAY_SECONDS) - Система использует экспоненциальный backoff
if response.status_code == 429 and retry_count < MAX_429_RETRIES:
await asyncio.sleep(RETRY_DELAY_SECONDS * (retry_count + 1))
return await self._request(...)Для снижения нагрузки на amoCRM API (лимит ~7 req/s) используется Redis-кэширование ответов. Kanban-доска обновляется каждые 30 секунд, при этом каждый рефреш вызывает GET /leads/by-pipeline/{id}, который пагинирует все сделки (250/стр). Без кэша это создаёт большое количество запросов к amoCRM.
| Эндпоинт | TTL | Ключ кэша |
|---|---|---|
GET /pipelines |
300с (5 мин) | amocrm:pipelines:{subdomain} |
GET /leads/by-pipeline/{id} |
60с (1 мин) | amocrm:pipeline_leads:{subdomain}:{pipeline_id} |
GET /leads/unsorted |
30с | amocrm:unsorted:{subdomain}:{page}:{limit} |
GET /leads/{id} |
120с (2 мин) | amocrm:lead:{subdomain}:{lead_id} |
GET /leads(поиск с query) не кэшируется — динамические запросы, низкая частота.
Кэш автоматически сбрасывается при мутациях:
| Мутация | Что инвалидируется |
|---|---|
PATCH /leads/{id} |
amocrm:lead:*:{lead_id} + amocrm:pipeline_leads:* + amocrm:unsorted:*
|
POST /leads |
amocrm:pipeline_leads:* + amocrm:unsorted:*
|
POST /webhooks/amocrm (events: leads[*]) |
amocrm:pipeline_leads:* + amocrm:unsorted:* + amocrm:lead:*
|
POST /disconnect |
amocrm:* (все ключи) |
Redis опционален. Если Redis недоступен — все эндпоинты работают напрямую с amoCRM API, как и до внедрения кэша. При восстановлении Redis кэширование возобновляется автоматически.
# Посмотреть ключи кэша
redis-cli keys "amocrm:*"
# Очистить весь CRM-кэш
redis-cli keys "amocrm:*" | xargs redis-cli delapp/routers/amocrm.py
→ app/services/amocrm_service.py (AmoCRMService)
→ AmoCRMService (modules/crm/service.py)
→ AmoCRMRepository (db/repositories/amocrm.py)
→ AmoCRMConfig, AmoCRMToken (modules/crm/models.py)
Чистый async HTTP-клиент без прямого DB-доступа. Методы:
-
get_contacts(limit, offset)— список контактов -
get_contacts_by_ids(contact_ids, batch_size=50)— батч-запрос контактов по ID с телефонами/email -
get_leads(limit, offset)— список сделок -
get_all_leads_paginated()— все сделки (250/стр, пагинация до конца) -
get_users()— все пользователи аккаунта →{id: имя} -
create_lead(name, price, pipeline_id, status_id, contact_id)— создать сделку -
get_pipelines()— список воронок и статусов -
refresh_tokens()— обновить токены при 401 -
exchange_code(code)— обменauthorization_codeна токены
Доменный сервис в modules/crm/service.py (ранее AsyncAmoCRMManager в db/integration.py) для работы с конфигурацией и токенами в БД:
-
get_config()— получить конфигурацию -
save_config(config)— сохранить конфигурацию -
get_tokens()— получить токены -
save_tokens(tokens)— сохранить токены -
delete_tokens()— удалить токены (отключение)
Если установлена переменная окружения AMOCRM_PROXY, все запросы проксируются:
proxies = {"http://": os.getenv("AMOCRM_PROXY"), "https://": os.getenv("AMOCRM_PROXY")}
async with httpx.AsyncClient(proxies=proxies) as client:
response = await client.get(url)- Admin — полный доступ к CRM-интеграции
-
User — только своя конфигурация (по
owner_id) - Guest — только чтение