CRM-интеграции
Подключение amoCRM/Kommo, Bitrix24, RetailCRM и OzmaCRM: авторизация, обнаружение полей, маппинг, предпросмотр, двусторонняя синхронизация без эха, приём вебхуков, действия из диалога и передача лида ИИ-агентом.
На этой странице
CRM-подключение связывает одного бота с одним аккаунтом CRM, чтобы профили клиентов, сделки и сводки диалогов передавались в обе стороны. Поддерживаются четыре провайдера: amoCRM и Kommo, Bitrix24, RetailCRM и OzmaCRM (ozma.io).
Все провайдеры работают за одним внутренним контрактом, поэтому авторизация, обнаружение полей, маппинг, разрешение конфликтов и подавление эха ведут себя одинаково независимо от CRM. Различается только формат обмена, и именно его эта страница описывает по каждому провайдеру.
Что доступно сейчас
Экран CRM находится в кабинете: Интеграции → CRM. Нужно право бота «Реакции». С этого экрана можно:
- подключить CRM через мастер, переименовать подключение, проверить его, переподключить и удалить;
- редактировать маппинг полей и сохранять его новой ревизией;
- прогнать предпросмотр маппинга на реальных профилях подписчиков и запустить первичную синхронизацию;
- следить за журналом синхронизации: операциями и их задачами по каждой записи, с повтором упавшей задачи;
- разбирать конфликты, которые политика маппинга оставила человеку;
- посмотреть адрес приёма вебхуков подключения и перевыпустить его.
Планировщик надёжной синхронизации работает: воркер задач просыпается каждые 5 секунд (пачки по 20 задач, аренда задачи 2 минуты), а сверка по опросу каждые 30 секунд (пачки по 20 просроченных курсоров). Включать что-либо отдельно не нужно.
Подключаются все четыре провайдера, но пути подключения и приёма событий у них разные:
| Провайдер | Как подключается | Как приходят входящие изменения |
|---|---|---|
| amoCRM / Kommo | OAuth, самостоятельно в мастере | исходящий вебхук amoCRM на выданный адрес, либо опрос |
| Bitrix24 | входящий вебхук, который вы создаёте у себя на портале; OAuth-путь пока закрыт | исходящий вебхук портала, либо опрос |
| RetailCRM | API-ключ, самостоятельно в мастере | вебхук RetailCRM с токеном MyBot в заголовке, либо опрос |
| OzmaCRM | OIDC-клиент плюс логин пользователя, самостоятельно в мастере | триггер, который вы ставите в OzmaDB сами, либо опрос |
Опрос работает у всех четырёх и ни от чего не зависит. Вебхук везде необязателен и лишь сокращает задержку.
Как достигается провайдер
Любой запрос к CRM, независимо от провайдера, проходит через один защищённый транспорт:
- Только HTTPS. Без user info в адресе, без своего порта и без прокси, унаследованного из окружения процесса.
- Хост заново разрешается перед каждым запросом, и соединение отклоняется, если любой из полученных адресов оказался loopback, приватным, link-local, multicast или адресом облачных метаданных. Редиректы обязаны остаться на том же хосте и проверяются точно так же.
- Тело ответа ограничено 1 МБ. Таймаут запроса 30 секунд, TLS-рукопожатия 10 секунд, заголовков ответа 15 секунд.
Retry-AfterиX-RateLimit-Resetучитываются как подсказки для повтора, но не более 5 минут. Лимиты частоты остаются лимитами самой CRM: дополнительной квоты вызовов на стороне платформы нет.- Ошибка провайдера сводится к короткому коду в нижнем регистре. HTTP 429, 5xx и сбои транспорта классифицируются как временные и повторяются, остальное терминально. Тела ответов, адреса запросов и текст ошибок провайдера никогда не попадают в логи, ошибки задач и аудит.
Три провайдера из четырёх можно направить только на закрытый набор хостов, выводимый из указанного идентификатора аккаунта:
- amoCRM и Kommo:
<аккаунт>.amocrm.ruили<аккаунт>.kommo.com. - Bitrix24:
<портал>.bitrix24.ruили<портал>.bitrix24.com. Конфиденциальный обмен токенами дополнительно идёт на единственный фиксированный сервер авторизацииoauth.bitrix.info. - RetailCRM:
<аккаунт>.retailcrm.ru.
OzmaCRM здесь исключение. Поскольку поддерживаются self-hosted инстансы, её базовый адрес задаёт оператор, а не выводится из разрешённого суффикса. Проверяется только форма (HTTPS, без user info, без порта, без пути, запроса и фрагмента); от обращения на внутренний адрес защищает описанный выше запрет приватного egress.
amoCRM и Kommo
Предварительные условия. Оператор платформы регистрирует одну
OAuth-интеграцию и задаёт AMOCRM_CLIENT_ID и AMOCRM_CLIENT_SECRET. Обе
переменные задаются вместе, а APP_BASE_URL должен быть абсолютным
HTTPS-origin, потому что redirect URI выводится из него как
<APP_BASE_URL>/api/integrations/crm/amocrm/callback. Пока это не настроено,
эндпоинты подключения отвечают 503.
Подключение. В кабинете это карточка amoCRM в мастере. По REST владелец бота отправляет хост аккаунта и переходит по возвращённому адресу:
curl -X POST "$BASE_URL/api/bots/$BOT_ID/crm/connections/amocrm/start" \
-H "Authorization: Bearer $PAT" \
-H "Content-Type: application/json" \
-d '{"account_key":"acme.amocrm.ru"}'
В ответе приходит redirect_url. Браузерное состояние одноразовое, живёт
5 минут и хранится в виде хеша, никогда в открытом виде. Стартов не больше трёх
в минуту на аккаунт. Отказ в согласии тоже расходует состояние, поэтому тот же
callback нельзя повторить с подделанным кодом.
Проверка при подключении. После обмена токенами аккаунт перечитывается
через GET /api/v4/account. Поддомен, который вернула amoCRM, обязан совпасть с
указанным ключом аккаунта, иначе подключение падает с несовпадением аккаунта, а
валюта аккаунта должна быть корректным кодом ISO 4217. Токены запечатываются
конвертным шифрованием с привязкой к подключению, боту, провайдеру,
нормализованному хосту аккаунта, версии ключа и ревизии кредов, поэтому шифртекст,
перенесённый в другое подключение или на другой разрешённый поддомен, не
откроется.
Возможности. Клиенты, лиды, сделки, диалоги, вебхуки, опрос и OAuth, плюс валюта аккаунта.
Поля и воронки. Кастомные поля обнаруживаются для контактов и лидов и
доступны как custom:<id поля>. Вычисляемое поле никогда не доступно на запись,
поле multiselect помечается как множественное, а неизвестный MyBot тип поля
предлагается только на чтение. Воронки и их статусы приходят из
GET /api/v4/leads/pipelines.
Запись. Контакты и лиды пишутся с request_id, равным ключу
идемпотентности записи (не длиннее 255 символов), и ответ обязан вернуть ровно
одну запись с тем же ключом, иначе эффект считается неоднозначным.
Диалоги. Выгружаются обычным примечанием к контакту или лиду. Сводка и каждое сообщение на входе ограничены 24 000 символами, сохраняются только последние 50 сообщений, а склеенный текст обрезается до 6 000 символов.
Вебхуки. amoCRM и Kommo не подписывают тела вебхуков, поэтому
аутентификацией служит сам непрозрачный адрес приёма, который MyBot выдаёт при
создании подключения. Сверх этого адаптер принимает JSON или form-encoded не
больше 256 КБ, отклоняет дубли ключей JSON, хвостовые данные и вложенность
глубже 64 уровней и требует ровно одно событие сущности на доставку. Если в
теле объявлен account[subdomain], он обязан совпасть с подключённым аккаунтом.
Принимаются события добавления и изменения контакта и добавления, изменения и
смены статуса лида. Затем запись перечитывается через API, и её updated_at не
должен быть старше события, так что авторитетное состояние никогда не берётся из
тела вебхука.
Опрос. Контакты листаются по возрастанию updated_at, по 50 на страницу, с
зашифрованным курсором.
Bitrix24
У Bitrix24 два пути подключения, и работает пока один.
Входящий вебхук: рабочий путь. Вы создаёте у себя на портале интеграцию «Приложения → Вебхуки → Входящий вебхук» и передаёте её тройку: домен портала, member id и код вебхука со страницы интеграции. Это карточка Bitrix24 в мастере или прямой запрос:
curl -X POST "$BASE_URL/api/bots/$BOT_ID/crm/connections/bitrix24" \
-H "Authorization: Bearer $PAT" \
-H "Content-Type: application/json" \
-d '{"name":"Портал","account_key":"acme.bitrix24.ru","member_id":"…","webhook_token":"…"}'
Портал, member id и токен запечатываются вместе, а REST-вызовы адресуются как
/rest/<member id>/<токен>/<метод>.json. Refresh-токена в этом режиме нет,
поэтому подключение сообщает, что OAuth недоступен. Так как кред живёт прямо в
пути запроса, ошибки транспорта всегда сводятся к стабильному коду и никогда не
цитируют адрес.
OAuth: пока закрыт. POST /api/bots/{botID}/crm/connections/bitrix24/start
отвечает 503: платформа читает только AMOCRM_CLIENT_ID и
AMOCRM_CLIENT_SECRET, отдельного OAuth-приложения Bitrix24 у неё нет. В
мастере кнопка «Через OAuth» помечена недоступной, а форма входящего вебхука
рядом остаётся рабочей. Описание режима ниже читайте как справку.
Режим OAuth (когда приложение появится). Согласие даётся на
https://<портал>/oauth/authorize/, обмен токенами идёт на фиксированный сервер
oauth.bitrix.info. В ответе с токеном должен быть домен oauth.bitrix.info и
клиентский эндпоинт ровно https://<портал>/rest/, иначе подключение
отклоняется как несовпадение портала. Возвращённый member_id обязателен и
сохраняется: позже им аутентифицируются вебхуки.
Приём вебхуков требует токена приложения. Доставки Bitrix24 проверяются по
application_token, и на развёртывании без BITRIX24_APPLICATION_TOKEN каждая
из них отклоняется до чтения тела. Оба пути подключения это знают: ответ на
создание подключения несёт предупреждение inbound_warning, а кабинет
показывает его отдельным блоком, чтобы вы не настраивали на портале адрес,
который заведомо отвергнет все доставки. Опрос при этом работает.
Минимум прав. Выдавайте только те CRM-права, которые нужны вашему маппингу.
Адаптер вызывает profile, crm.contact.fields, crm.lead.fields,
crm.deal.fields, crm.dealcategory.list, crm.status.list,
crm.contact.list, методы crm.{contact,lead,deal}.{add,update,get} и
crm.timeline.comment.add. Больше ничего не используется.
Возможности. Клиенты, лиды, сделки, диалоги, вебхуки, опрос и идемпотентные внешние ключи. Валюту аккаунта Bitrix24 не сообщает, поэтому денежный маппинг обязан явно указать валюту ISO.
Поля и воронки. Поля контакта, лида и сделки читаются с портала с
сохранением флагов «только чтение» и множественности. Воронки здесь это
категории сделок, а статусы каждой категории читаются из crm.status.list с
фильтром DEAL_STAGE_<id категории>.
Контакты или лиды. По умолчанию клиент пишется контактом. Настройка
{"customer_entity":"lead"} в конфигурации подключения переключает запись на
лиды.
Запись. crm.<сущность>.add и crm.<сущность>.update вызываются с
REGISTER_SONET_EVENT=N, чтобы синхронизация не засоряла ленту активности.
Создание новой записи требует замапленного поля внешнего ключа, доступного на
запись: без него запись отклоняется, а не создаётся без возможности связать её.
Диалоги. Добавляются комментарием в таймлайн контакта, лида или сделки с
префиксом [mybot:<ключ идемпотентности>], чтобы повторную доставку было видно
в таймлайне. Склеенный текст обрезается до 32 000 символов.
Вебхуки. application_token и member_id сравниваются за постоянное время,
а auth.domain обязан совпасть с подключённым порталом. Тела принимаются только
в JSON, до 256 КБ, без хвостовых данных. Обрабатываются события
ONCRMCONTACTADD/UPDATE/DELETE, ONCRMLEADADD/UPDATE/DELETE и
ONCRMDEALADD/UPDATE/DELETE. Любое событие, кроме удаления, перечитывает
запись через crm.<сущность>.get, и возвращённый ID обязан совпасть с событием.
Опрос. crm.contact.list с сортировкой по DATE_MODIFY и ID, с
продолжением от зашифрованной контрольной точки.
RetailCRM
Предварительные условия. API-ключ. OAuth у RetailCRM здесь нет: и авторизация, и обновление отвечают явным кодом «не поддерживается», а ключ остаётся единственным кредом.
Подключение. Карточка RetailCRM в мастере или напрямую:
curl -X POST "$BASE_URL/api/bots/$BOT_ID/crm/connections/retailcrm" \
-H "Authorization: Bearer $PAT" \
-H "Content-Type: application/json" \
-d '{"name":"Магазин","account_key":"acme.retailcrm.ru","api_key":"…"}'
Поле name необязательно и по умолчанию равно хосту аккаунта. В ответе
безопасная сводка подключения, адрес приёма вебхуков и токен вебхука, но не
API-ключ.
Минимум прав. При подключении адаптер читает /api/credentials и справочники
sites, stores и order-methods; каждый из трёх должен вернуть хотя бы одну
пригодную запись. Выгрузка диалогов дополнительно требует права
customer_write (на старых аккаунтах кред /api/v5/customers/notes/create).
Без него подключение сообщает, что диалоги недоступны, вместо того чтобы упасть
в момент выгрузки.
Возможности. Клиенты, сделки, заказы, вебхуки, опрос и идемпотентные внешние ключи. Диалоги зависят от права выше.
Поля и воронки. Кастомные поля читаются из /api/v5/custom-fields отдельно
для сущностей клиента и заказа и доступны как custom:<код>. Поле доступно на
запись, только если RetailCRM пометила его editable. Обнаружение читает по 250
полей за раз, не больше 100 страниц и 10 000 полей. Статусы заказов группируются
по сайтам, и каждый сайт становится отдельной воронкой.
Запись. Клиенты и заказы создаются и правятся через /api/v5/customers/...
и /api/v5/orders/... формами; API-ключ добавляется на сервере и никогда не
попадает в маппинг. Локальный идентификатор MyBot пишется в externalId. Если
идентификатор, вернувшийся из RetailCRM, не совпал со связанной записью, эффект
помечается неоднозначным, а связь не переставляется молча.
Диалоги. Выгружаются примечанием клиента, текст ограничен 2 000 символами.
Вебхуки. RetailCRM не подписывает колбэки, поэтому MyBot выдаёт собственный
токен при создании подключения и показывает его один раз. Вставьте его в
заголовок X-Mybot-Webhook-Token в шаблоне вебхука самой RetailCRM: доставки
обязаны нести этот заголовок, и он сравнивается за постоянное время. Тела
ограничены 64 КБ, тип обязан быть customer или order, событие create,
update или delete, и любое событие, кроме удаления, перечитывает запись по
ID, потому что шаблонное тело несёт только идентичность.
Опрос. /api/v5/customers/history и /api/v5/orders/history, по 50 записей
на страницу, с продолжением по sinceId или startDate.
OzmaCRM
OzmaCRM это ozma.io, low-code CRM/ERP на базе OzmaDB. У неё нет фиксированной схемы CRM, поэтому, в отличие от трёх других провайдеров, это вы сообщаете MyBot, какие сущности использовать, а не наоборот.
Предварительные условия.
- Базовый адрес инстанса. Облачные инстансы выглядят как
https://<аккаунт>.api.ozma.org, self-hosted это любой HTTPS-origin, которым вы управляете. - Client id и client secret OIDC, а не только логин. Аутентификация идёт по OIDC resource-owner password grant, и запрос отправляется с клиентскими кредами вашего арендатора.
- Логин и пароль пользователя OzmaCRM, от имени которого будет работать MyBot. Дайте ему минимальную роль, которой хватает на чтение и запись ваших сущностей.
- Базовый адрес OIDC и realm для self-hosted инстанса. Для облака realm по
умолчанию
defaultнаhttps://account.ozma.io, эндпоинт токена/auth/realms/<realm>/protocol/openid-connect/token. - Журнальная сущность
usr.mybot_sync_marks, описанная ниже. Создайте её до подключения: без неё проверка подключения падает.
Refresh-токен обязателен. Срок действия access-токена проверяется локально: как
только он истёк, адаптер падает с token_expired и подключение
перезапечатывается до следующей отправки, вместо обновления токена посреди
записи.
Выбор сущностей. Подключение несёт небольшую конфигурацию:
customer_entity: схема и сущность с клиентами (обязательно).deal_entity: схема и сущность со сделками (обязательно).conversation_entity: необязательно, сущность для журнала диалогов.deal_status_field: колонка сделки, домен которой становится воронкой.updated_at_field: колонка времени изменения, по умолчаниюupdated_at.
Каждое имя схемы, сущности и поля должно состоять из строчных ASCII-букв, цифр или подчёркиваний, не начинаться с цифры и быть не длиннее 64 символов. Это не косметика: имена подставляются в FunQL, поэтому белый список здесь работает границей защиты от инъекций.
Журнал синхронизации. OzmaCRM единственный провайдер, где причинные метки MyBot не могут жить на ваших собственных записях. Вместо этого они идут в отдельную журнальную сущность, которую вы создаёте сами: MyBot никогда не разворачивает и не заменяет схему, потому что загрузка макета перезаписала бы ваши данные. Создайте её до включения синхронизации:
create entity usr.mybot_sync_marks with fields:
entity_kind string not null -- customer | lead | deal | order | conversation
remote_id string -- nullable: пусто для первой метки новой записи
marker string not null -- ключ идемпотентности эффекта
direction string not null -- outbound | inbound
created_at datetime not null
Проверка подключения смотрит /api/check_access, затем этот журнал, затем все
настроенные сущности. Отсутствующий или несовместимый журнал приводит к отказу с
missing_sync_marks_entity и возвращает определение выше дословно;
синхронизация не включается, пока журнала нет.
Поля и воронки. Доступные на запись колонки настроенных сущностей
обнаруживаются из их entity info (id пропускается). int и double
превращаются в целое, bool в булево, date и datetime во время, а
string, reference, enum и uuid в строку; колонка-массив берёт свой
подтип и помечается множественной. Неизвестный MyBot тип колонки пропускается и
остаётся виден через overflow в предпросмотре маппинга. Так как в OzmaDB нет
понятия воронки, домен deal_status_field становится одной синтетической
воронкой.
Запись. Каждая запись это один POST /api/entities/transaction с двумя
операциями: сначала сама запись, затем вставка в журнал. Обе фиксируются вместе,
поэтому метка не может существовать без изменения, которое она описывает. Если
транзакция упала, в ошибке сказано, какая из двух операций не прошла.
Колонки, которые MyBot пишет сам, фиксированы, поэтому ваши сущности обязаны их принимать:
- Клиент:
name, плюсphone,emailиtags, когда они есть, плюс все замапленные колонки, которые обнаружение пометило доступными на запись. - Сделка:
name,status,amount_minor,currency,customer(id связанного клиента) иtags. Записи сделки нужны статус и трёхбуквенная валюта. - Диалог:
customerилиdeal(id владельца),summaryиcreated_at.
Замапленное значение, чья колонка не помечена доступной на запись, отбрасывается до запроса, поэтому ревизия маппинга не может протащить запись в колонку, которую никто не предлагал.
Диалоги. Сводка и каждое сообщение ограничены 16 384 байтами, сохраняются последние 50 сообщений, а склеенный текст обрезается до 32 000 байт по границе корректного UTF-8.
Опрос. Изменения читаются keyset-запросом FunQL с сортировкой по вашей
колонке updated_at и id, по 200 строк на страницу, с продолжением строго
после последней возвращённой строки, поэтому перекрывающиеся окна опроса не
могут пропустить или продублировать запись. Каждая строка левым соединением
связывается с журналом, чтобы отличить входящее изменение от эха собственной
последней записи MyBot.
Триггер вместо ожидания опроса. Своих вебхуков у OzmaDB нет, поэтому
реальное время здесь даёт триггер, который вы ставите сами. При создании
подключения MyBot выдаёт адрес приёма и секрет X-Mybot-Trigger-Secret;
установите в интерфейсе OzmaDB или FunApp AFTER INSERT/UPDATE триггер на своих
сущностях клиентов и сделок, который шлёт на этот адрес маленькое событие:
export default async function handler(args, ctx) {
await OzmaDB.enqueueHttpRequest({
url: '<адрес приёма>',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Mybot-Trigger-Secret': '<секрет>'
},
body: JSON.stringify({
entity_kind: 'customer',
remote_id: args.id,
changed_at: new Date().toISOString()
}),
maxRetries: 10,
retryBaseDelayMs: 2000
})
}
entity_kind принимает customer или deal. Используется именно
enqueueHttpRequest, то есть исходящий ящик с доставкой не менее одного раза, а
не синхронный вызов: медленный или недоступный MyBot не должен блокировать вашу
собственную транзакцию. Повторная доставка нормальна и безопасна, потому что
дубли отсеиваются по идентификатору события. Тело ограничено 64 КБ, а всё
подключение продолжает работать на опросе, если триггер вы не поставите.
MyBot никогда не устанавливает этот триггер за вас и не выдаёт его текст через API: загрузка макета в OzmaDB заменяет схемы целиком и уничтожила бы ваши данные.
Обнаружение полей и маппинг
Маппинг это неизменяемая ревизия. Правка маппинга создаёт новую ревизию и не переосмысляет уже запущенную задачу, поэтому сегодняшнее изменение не может переписать смысл записи, поставленной в очередь раньше.
Каждое сопоставление поля называет:
- локальный путь:
profile.id,profile.name,profile.first_name,profile.last_name,profile.phone,profile.email,profile.tagsили любое другое свойство профиля какprofile.<свойство>; а для сделокdeal.id,deal.customer_id,deal.name,deal.pipeline,deal.status,deal.amount_minor(илиdeal.value_minor),deal.currencyлибо любое другое свойство сделки какdeal.<свойство>; - удалённый ключ из списка обнаруженных полей;
- сущность: клиент, лид, сделка или заказ;
- направление: исходящее (MyBot → CRM) или входящее (CRM → MyBot);
- преобразование;
- и обязательно ли значение.
Преобразования
identity, string, integer, boolean, timestamp, datetime_rfc3339,
phone_e164, email_normalized, enum_map, tags и
money_minor_currency. (minor_units остаётся для маппингов, созданных до того,
как у денег появилась явная валюта.)
Они декларативные и никогда не являются кодом клиента:
email_normalizedобрезает пробелы и приводит к нижнему регистру, требует формы адреса и отклоняет всё длиннее 320 символов.phone_e164принимает пробелы, дефисы и скобки как разделители и выдаёт номер с+длиной от 9 до 16 символов, не начинающийся с+0.stringограничивает значение 10 000 символами.tagsтребует строк, обрезает пробелы, отклоняет пустые теги и теги длиннее 128 символов, а также убирает дубли.enum_mapотображает только по закрытому списку, который вы задаёте. На входе список инвертируется, и если два локальных значения ведут в одно удалённое, сопоставление неоднозначно и удалённое значение уходит в overflow.money_minor_currencyвыдаёт знаковые минорные единицы плюс валюту ISO 4217.
Чему должен удовлетворять маппинг
Сохранение ревизии падает, если локальный путь неизвестен, удалённого ключа нет
у этой сущности, исходящее сопоставление целится в поле, помеченное провайдером
только на чтение, у enum_map нет значений, у денежного маппинга нет валюты ISO
в верхнем регистре, два сопоставления пишут в одно и то же удалённое назначение в
одном направлении для одной сущности, два входящих сопоставления пишут в одно
локальное назначение или преобразование не связывает локальный и удалённый типы.
Тегам нужны обе стороны, а триггер обязан назвать известное событие, свою воронку
и один из её статусов.
Теги: только входящие
Метки MyBot никогда не уезжают в CRM тегами. В профиле подписчика хранятся
идентификаторы меток, а не человекочитаемые названия, с которыми сравнивает
сопоставление тегов, поэтому отправка их наружу записала бы в карточку клиента
непрозрачные строки вида 7f3c1a2e-…. Исходящий список тегов намеренно остаётся
пустым.
Входящие теги работают штатно: сопоставление тегов преобразует и нормализует
удалённые теги в локальные, а незамапленный удалённый тег уходит в overflow.
Если нужно показать метку MyBot в CRM, замапьте её как обычное свойство профиля
через enum_map.
Overflow
Каждый маппинг объявляет одно назначение для overflow, поле или примечание плюс ключ, и оно обязательно. Ничто, что не удалось сопоставить, не выбрасывается. Значение, не прошедшее преобразование, отсутствующее обязательное значение, локальное свойство без сопоставления, незамапленный тег, незамапленное удалённое свойство и незамапленный удалённый тег записываются в overflow вместе с исходным значением и причиной и уходят в это назначение.
Политика конфликтов
mybot_wins: удалённое изменение никогда не перезаписывает локально изменённый профиль.crm_wins: удалённое изменение применяется.newest_wins: побеждает более поздняя отметка времени; при точном совпадении изменение уходит на ручной разбор.manual: каждое конфликтующее изменение уходит на ручной разбор.
Профиль, в котором ещё не зафиксировано локальное изменение, принимает удалённое при любой политике. Независимо от политики удалённое изменение, которое не новее последнего уже записанного удалённого изменения по связи, игнорируется.
Предпросмотр маппинга
До любой записи маппинг можно спроецировать на реальные записи. Предпросмотр детерминирован, не зависит от провайдера и потому не может вызвать удалённую запись: он лишь показывает, что было бы отправлено.
Он берёт не больше 20 профилей подписчиков, показывает каждое замапленное поле с исходным значением, преобразованным значением, предупреждением и ошибкой, и перечисляет overflow, который дал бы этот маппинг. Рядом с выборкой он сообщает число затронутых записей, помечая его точным, когда запрос по своим записям полон, или оценочным, когда доступна только ограниченная оценка.
Список сделок в выборке всегда пуст, и в предпросмотре, и в первичной синхронизации. Своего объекта «сделка» у MyBot до подключения CRM нет, поэтому наружу выгружать нечего, кроме профилей подписчиков. Сделки движутся в обратную сторону: они приходят из CRM и становятся событиями клиента и целями (см. раздел «Сделки, деньги и атрибуция»). Поля сделки в редакторе маппинга по-прежнему настраиваются и используются для входящих изменений и для сделок, которые вы отправляете из диалога.
Первичная синхронизация запускается отдельным запросом и требует явного
подтверждения, ключа идемпотентности и номера ревизии маппинга, который сейчас
на экране: устаревшая ревизия отвечает 409.
Исходящие записи, связи и идемпотентность
Каждая запись несёт стабильный ключ идемпотентности по записи и приводит к сохранённой связи, паре вашего локального идентификатора и id записи в CRM вместе с удалённой версией и последними локальной, удалённой и синхронизированной отметками времени. Именно связь, а не догадка, превращает повторную запись в обновление.
Каждый провайдер закрепляет ключ в своём родном механизме: request_id у amoCRM
и Kommo, поле внешнего ключа и метка в комментарии у Bitrix24, externalId у
RetailCRM и транзакционная строка журнала у OzmaCRM.
Приём вебхуков и модели доверия
Входящие изменения приходят в MyBot либо из курсора опроса, либо вебхуком от провайдера. На стороне вебхуков есть четыре отдельных маршрута приёма, по одному на провайдера, и у каждого своя модель доверия:
| Маршрут | Чем аутентифицируется доставка |
|---|---|
POST /hooks/crm/{routeKey}/amocrm | в теле ничего не подписано: секрет это сам непрозрачный маршрут, плюс account[subdomain] обязан совпасть с подключённым аккаунтом |
POST /hooks/crm/{routeKey}/bitrix24 | application_token и member_id сравниваются за постоянное время, а auth.domain обязан совпасть с подключённым порталом |
POST /hooks/crm/{routeKey}/retailcrm | заголовок X-Mybot-Webhook-Token, выданный MyBot, сравнивается за постоянное время |
POST /hooks/crm/{routeKey}/ozmacrm | заголовок X-Mybot-Trigger-Secret, выданный MyBot, сравнивается за постоянное время |
Ключ маршрута случаен для каждого подключения и хранится только в виде хеша, а
провайдер закрепляется в момент регистрации: отправка тела amoCRM на маршрут
RetailCRM или на неподключённое подключение это не отдельная ошибка, а тот же
самый 404, что и неизвестный маршрут. Доставка, попавшая в нужный маршрут, но
не прошедшая проверку адаптера, получает один общий 400. Ни один из ответов не
сообщает, существует ли маршрут или подключение. Любое тело ограничено 256 КиБ, а
на чтение даётся 10 секунд.
Как получить адрес. Полный адрес приёма показывается один раз, в момент
создания подключения, вместе с токеном RetailCRM или секретом триггера OzmaCRM.
Скопируйте его сразу: он нигде не хранится в открытом виде и повторно не
отобразится. Если подключение создано давно и адрес утерян, откройте карточку
подключения и нажмите «Перевыпустить адрес приёма» (нужно право «Секреты
интеграций») либо вызовите
POST /api/bots/{botID}/crm/connections/{connectionID}/webhook-route. Прежний
адрес перестаёт работать сразу, поэтому обновите настройки на стороне CRM.
Если в развёртывании не настроен публичный адрес сервера, кабинет так и скажет: адрес приёма показать нельзя, и это пробел в конфигурации платформы.
Входящие изменения и подавление эха
Входящее изменение приходит либо из проверенного вебхука, либо из курсора опроса, и оба пути сходятся в одной нормализации.
Общая проблема обоих путей это эхо: MyBot пишет в CRM, CRM сообщает об изменении обратно, и наивная интеграция зацикленно применяет собственную запись. Подавление здесь работает по эффекту и одноразово:
- Исходящая запись ставит метку на удалённой записи и фиксирует её как ожидаемую для этой связи.
- Входящее изменение подавляется, только если несёт метку, которая всё ещё ожидается для ровно этого подключения и удалённой записи, не старше 24 часов и наблюдалась не позже последнего известного удалённого изменения связи.
- После этого метка расходуется.
Практическое следствие важно: более позднее изменение с тем же значением считается настоящей правкой человека. Если кто-то откроет CRM и поправит поле, которое MyBot только что записал, пусть даже вернёт то же значение, эта правка не будет проглочена. Метка, которая так и не вернулась эхом, просто истекает по своему сроку, поэтому потерянный вебхук не может навсегда заблокировать входящую синхронизацию связи.
Стабильный идентификатор связи для этого намеренно не используется. Он постоянен всю жизнь связи, так что подавление по нему проглатывало бы и все последующие правки человека.
Как только изменение пережило подавление и политику конфликтов, замапленные значения становятся аудируемыми изменениями свойств профиля, каждое со своим происхождением: подключение, связь, провайдер, удалённый id, удалённая версия и время изменения. Пустое значение в необязательном входящем поле очищает локальное свойство, в обязательном уходит в overflow.
Повторная сверка
Сверка по опросу сама продвигает просроченные курсоры каждые 30 секунд, но подключение можно заставить догнать состояние немедленно:
curl -X POST "$BASE_URL/api/bots/$BOT_ID/crm/connections/$CONNECTION_ID/reconcile" \
-H "Authorization: Bearer $PAT"
Успешный проход отвечает 202 и отчётом о том, какие ресурсы удалось опросить, а
какие нет. Ответы различаются намеренно, чтобы мёртвое подключение не выглядело
здоровым: 409, если сверка по этому подключению уже идёт; 422, если
подключение вообще нельзя опрашивать и ему, скорее всего, нужна переавторизация;
502, если упал опрос каждого ресурса.
Используйте сверку после сбоя у провайдера, после доставки, которая, по вашему мнению, потерялась, или когда правки на стороне CRM явно не видно в MyBot. Повторять безопасно: уже применённое изменение опознаётся по связи и ключу идемпотентности.
Кнопки для этого эндпоинта в кабинете пока нет. Он доступен только по API, со
скоупом crm:write и правом бота «Реакции».
Действия из диалога
Оператор, ведущий переписку, может посмотреть на CRM-сторону конкретного человека и отправить его туда вручную. В диалоге это меню CRM с действиями «Синхронизировать сейчас» и «Экспортировать переписку», а в карточке подписчика вкладка CRM со статусом связи и последней синхронизацией. Всем действиям нужно право бота «Диалоги».
По REST это три маршрута:
GET /api/bots/{botID}/subscribers/{subscriberID}/crm/status(subscribers:read): связан ли подписчик, с каким подключением и удалённой записью, когда синхронизировался последний раз и чем закончилась последняя задача.POST /api/bots/{botID}/subscribers/{subscriberID}/crm/sync(subscribers:write): отправить подписчика сейчас. В теле указываетсяconnection_id, а также может быть необязательнаяdeal(имя, воронка, статус,value_minor,currency, дополнительные свойства), которую создадут или обновят вместе с клиентом. Меню в диалоге отправляет только клиента, без сделки, потому что выбирать воронку и статус оператору там негде. Подписчик проецируется ровно так же, как его проецирует фоновая синхронизация, поэтому ручная отправка никогда не разойдётся с плановой.POST /api/bots/{botID}/subscribers/{subscriberID}/crm/export-conversation(subscribers:write): записать переписку в CRM примечанием. В теле толькоconnection_id; что именно уезжает, описано ниже.
Каждое действие возвращает тот же объект надёжной операции, что показывает журнал синхронизации, поэтому ручное действие видно в списке операций подключения наравне с остальными.
Что содержит выгрузка переписки
Выгрузка это урезанная и отредактированная выдержка, а не стенограмма. До того как что-либо покинет MyBot:
- Сохраняются не больше 50 последних сообщений. Более старая переписка обрезается, а не выгружается постранично.
- Каждое сообщение обрезается до 4 000 символов, и сводка тоже.
- Вложение превращается в буквальный текст
[attachment], без имени файла, без ссылки, без MIME-типа и размера. CRM узнаёт, что что-то было приложено, и ничего больше. - Авторы сводятся к двум ролям,
customerиoperator. Кто именно из операторов написал строку, наружу не уезжает. - Пустые сообщения отбрасываются.
Сверху накладываются лимиты провайдера: amoCRM держит склеенный текст в 6 000 символов, RetailCRM в 2 000, Bitrix24 в 32 000, OzmaCRM в 32 000 байт.
Приватные заметки операторов не уезжают никогда. Это не фильтр, который можно неправильно настроить: путь выгрузки читает обычную ленту сообщений диалога и не имеет ни ветки кода, ни поля запроса, которые вели бы к таблице приватных заметок. Там просто нечего случайно приложить. Что такое приватная заметка, см. Поддержка.
Передача квалифицированного лида ИИ-агентом
ИИ-агент может сам передать квалифицированного лида в CRM через один
закрытый инструмент, crm.sync_qualified_lead. Это инструмент с эффектом записи,
поэтому он подчиняется обычным правилам подтверждения и идемпотентности агента.
Безопасным для модели его делают две границы:
- Подключение должно быть явно разрешено этому агенту. Инструмент видит только те id подключений, что перечислены в политике инструментов ИИ-агента этого бота. Модель, назвавшая любое другое подключение, в том числе реальное и принадлежащее тому же боту, получает отказ до того, как что-то выполнится.
- Факты ограничены полями, которые уже настроены в маппинге. Модель может
приложить не больше 20 фактов вида
{key, value}(ключ до 128 символов, значение до 1 000). Каждый ключ обязан быть локальным путём, который текущая ревизия маппинга уже пишет наружу в поле клиента, сделки или заказа. Неизвестный ключ отклоняет весь вызов: и молчаливый отброс, и молчаливый приём отдали бы модели решение о том, что попадёт в вашу CRM.
Переписку модель не подставляет. Флаг export_conversation просит сервер
приложить её, и сервер берёт её той же ограниченной и отредактированной выгрузкой,
что описана выше: подписчик, бот и креды определяются на сервере.
Инструмент возвращает id операции и её статус, больше ничего.
Сделки, деньги и атрибуция
Деньги всегда представлены знаковой 64-битной минорной суммой плюс валютой ISO 4217, никогда числом с плавающей точкой. В полезной нагрузке провайдера едет только минорная сумма, валюта остаётся в канонической записи сделки. На входе денежный маппинг сохраняет минорную сумму и предупреждает, что валюта зафиксирована маппингом: если валюта может меняться, замапьте её отдельным полем.
Изменение сделки порождает событие клиента с именем crm.deal_created,
crm.deal_updated, crm.deal_won или crm.deal_lost с локальным id, id
клиента, воронкой, статусом и, когда они есть, минорной суммой и валютой.
Выигранная сделка дополнительно даёт цель crm.deal_won для
атрибуции с той же суммой и валютой.
Приватность и маскирование
- Креды вообще нельзя сериализовать: тип кредов отказывается превращаться в JSON, поэтому токен не может случайно утечь через ответ, строку лога или полезную нагрузку задачи.
- Креды и курсоры зашифрованы на диске и привязаны к одному подключению, боту, провайдеру, аккаунту, версии ключа и ревизии кредов. Шифртекст, перенесённый в другое место, просто не откроется.
- Чтение подключения отдаёт только безопасную сводку: id, бот, провайдер, имя, ключ аккаунта, статус, возможности, ревизия маппинга, код последней ошибки и отметки времени. Шифртекста кредов и материала маршрута вебхука в ней нет.
- Перевыпуск маршрута пишется в аудит самим фактом ротации: ни новый ключ, ни его хеш в аудит не попадают.
- Метаданные запросов и ответов, которые хранятся для разбора проблем,
маскируются: метод, схема, хост и путь, код статуса и подсказка для повтора.
Любой заголовок, в имени которого есть
authorization,token,secret,api-key,cookieилиpassword, хранится как[redacted]. - Выгрузки переписки это ограниченные выдержки с обезличенными ролями, а приватные заметки операторов исключены из них структурно.
- Входящие изменения профиля аудируются вместе с происхождением, поэтому любое значение, записанное CRM в профиль, прослеживается до подключения и удалённой записи, откуда оно пришло.
- Связи с CRM это отдельная категория хранения со сроком 730 дней; они попадают в выгрузку и удаление данных наравне с остальными категориями.
Состояние подключения, переподключение и отключение
Один бот может держать одно подключение на пару «провайдер + ключ аккаунта».
Подключение создаётся в состоянии connected; набор состояний также допускает
pending, expired, disabled и failed вместе со стабильным кодом последней
ошибки.
Кнопка проверки подключения записывает failed только по терминальному ответу
провайдера. Таймаут, 502 или сбой транспорта ничего не доказывают, поэтому во
время аварии у провайдера одно нажатие не выводит подключение из очереди задач и
опроса.
Переподключение атомарно проворачивает запечатанные креды относительно ожидаемой ревизии, поэтому два одновременных переподключения не могут переплестись и оставить наполовину провёрнутое подключение. Так как печать привязана к ревизии, прежний шифртекст перестаёт открываться в момент фиксации нового.
Чтобы отключиться безопасно:
- Сначала отзовите кред на стороне CRM: доступ OAuth-приложения для amoCRM и Kommo, входящий вебхук для Bitrix24, API-ключ для RetailCRM, OIDC-клиент для OzmaCRM.
- Уберите входящий путь, если настраивали его: подписку на вебхук в CRM или триггер в OzmaDB.
- Удалите подключение: кнопкой «Удалить» на карточке подключения или
запросом
DELETE /api/bots/{botID}/crm/connections/{connectionID}(crm:write). Удаление подключения уносит с собой его ревизии маппинга, связи и курсоры и ничего не меняет внутри вашей CRM.
Журнальная сущность OzmaCRM принадлежит вам: её безопасно оставить и безопасно удалить, когда ни одно подключение не использует этот инстанс.
API
Все маршруты ниже находятся под /api/bots/{botID}/crm/…, если не указано иное,
и всем дополнительно нужно право бота «Реакции». Схемы запросов и ответов
описаны в Справочнике API.
Подключения
| Эндпоинт | Скоуп | Что делает |
|---|---|---|
GET /connections | crm:read | список подключений (безопасные сводки) |
GET /connections/{connectionID} | crm:read | сводка одного подключения |
POST /connections/{provider}/start | crm:write | начать браузерный OAuth-сценарий, возвращает redirect_url. Работает у amoCRM; bitrix24 отвечает 503, пока приложение не настроено |
GET /api/integrations/crm/{provider}/callback | : | цель браузерного редиректа; аутентификацией служит одноразовое состояние, токен не нужен |
POST /connections/bitrix24 | crm:write | подключить Bitrix24 по входящему вебхуку портала |
POST /connections/retailcrm | crm:write | подключить RetailCRM по API-ключу |
POST /connections/ozmacrm | crm:write | подключить OzmaCRM по OIDC-кредам, логину и конфигурации сущностей |
POST /connections/{connectionID}/webhook-route | crm:write | перевыпустить адрес приёма вебхуков; нужно право «Секреты интеграций» |
PATCH /connections/{connectionID} | crm:write | переименовать |
DELETE /connections/{connectionID} | crm:write | отключить и удалить маппинги, связи и курсоры |
POST /connections/{connectionID}/test | crm:write | заново прогнать проверки связи и предварительных условий |
Маппинг и предпросмотр
| Эндпоинт | Скоуп | Что делает |
|---|---|---|
GET /connections/{connectionID}/fields | crm:read | обнаруженные удалённые поля |
GET /connections/{connectionID}/pipelines | crm:read | воронки и их статусы |
GET /connections/{connectionID}/mapping | crm:read | текущая ревизия маппинга |
PUT /connections/{connectionID}/mapping | crm:write | сохранить новую неизменяемую ревизию |
POST /connections/{connectionID}/preview | crm:read | спроецировать маппинг на реальные профили; намеренно чтение, удалённой записи не происходит |
Синхронизация
| Эндпоинт | Скоуп | Что делает |
|---|---|---|
POST /connections/{connectionID}/initial-sync | crm:write | выгрузить профили подписчиков наружу; нужны подтверждение, ключ идемпотентности и текущая ревизия маппинга |
POST /connections/{connectionID}/reconcile | crm:write | принудительная догоняющая сверка |
GET /operations | crm:read | операции, новейшие первыми, keyset-пагинация |
GET /operations/{operationID} | crm:read | одна операция и её задачи |
POST /operations/{operationID}/cancel | crm:write | отменить выполняющуюся операцию |
POST /jobs/{jobID}/retry | crm:write | повторить одну упавшую задачу |
GET /conflicts | crm:read | задачи, ждущие ручного разбора |
POST /conflicts/{jobID}/resolve | crm:write | разрешить один конфликт |
Действия из диалога (subscribers:read / subscribers:write, право бота
«Диалоги», см. раздел «Действия из диалога» выше) это
GET|POST /api/bots/{botID}/subscribers/{subscriberID}/crm/status|sync|export-conversation.
В Справочнике API их пока нет.
Инструменты MCP
По MCP опубликованы шесть инструментов только для чтения, всем
нужен crm:read:
list_crm_connections: подключения бота с провайдером, статусом и возможностями.get_crm_connection: безопасная сводка одного подключения.get_crm_mapping: текущая ревизия маппинга подключения.list_crm_operations: последние операции синхронизации, новейшие первыми, при желании в рамках одного подключения.get_crm_operation: одна операция с её задачами (без сырой полезной нагрузки запроса задачи).list_crm_conflicts: задачи, ждущие ручного разбора конфликта.
Ни один из них никогда не возвращает креды. Инструментов записи для CRM нет намеренно: подключение, синхронизация, повтор и разрешение конфликта остаются только в REST, поэтому агент может наблюдать за CRM-интеграцией, но не может менять её поведение.
Известные ограничения
Записаны здесь, чтобы никто не открывал их заново как баги:
- OAuth для Bitrix24 недоступен:
connections/bitrix24/startотвечает503, потому что OAuth-приложения Bitrix24 на платформе нет. Подключайтесь через входящий вебхук портала. - Приём вебхуков Bitrix24 требует
BITRIX24_APPLICATION_TOKENна стороне развёртывания; без него доставки отклоняются, и остаётся опрос. - Метки MyBot не уезжают в CRM тегами; входящее сопоставление тегов работает.
- В предпросмотре и первичной синхронизации список сделок всегда пуст: своего объекта «сделка» у MyBot нет.
- У OzmaCRM нет собственных вебхуков: реальное время даёт только триггер, который вы ставите сами, а его текст MyBot через API не выдаёт.
- Адрес приёма показывается один раз: потерянный адрес можно только перевыпустить, восстановить прежний нельзя.
- У повторной сверки нет кнопки в кабинете, только API.
- Действий CRM по подписчику нет в Справочнике API, хотя в кабинете они есть.
Смежные страницы
- Интеграции: другие внешние сервисы, которые можно подключить к боту.
- Источники и ключи: REST- и OzmaDB-коннекторы для прямого чтения и записи данных.
- Веб-запросы и вебхуки: входящий и исходящий HTTP.
- Люди и профили: профили, которые читает и пишет маппинг CRM.
- Поддержка: переписка, из которой собирается выгрузка, и приватные заметки.
- ИИ-агент: агент, который может передать квалифицированного лида.
- MCP: шесть инструментов CRM только для чтения.
- Аналитика: куда попадает выигранная сделка как цель.