CRM-интеграции
CRM-подключение связывает одного бота с одним аккаунтом CRM, чтобы профили клиентов, сделки и сводки диалогов передавались в обе стороны. Поддерживаются четыре провайдера: amoCRM и Kommo, Bitrix24, RetailCRM и OzmaCRM (ozma.io).
Все провайдеры работают за одним внутренним контрактом, поэтому авторизация, обнаружение полей, маппинг, разрешение конфликтов и подавление эха ведут себя одинаково независимо от CRM. Различается только формат обмена – именно его эта страница описывает по каждому провайдеру.
Что доступно сейчас
Экран CRM находится в кабинете: Интеграции → CRM. Нужно право бота «Реакции». С этого экрана можно:
- подключить CRM через мастер, переименовать подключение, проверить его, переподключить и удалить;
- редактировать маппинг полей и сохранять его новой ревизией;
- прогнать предпросмотр маппинга на реальных профилях подписчиков и запустить первичную синхронизацию;
- следить за журналом синхронизации – операциями и их задачами по каждой записи, с повтором упавшей задачи;
- разбирать конфликты, которые политика маппинга оставила человеку.
Планировщик надёжной синхронизации работает: воркер задач просыпается каждые 5 секунд (пачки по 20 задач, аренда задачи 2 минуты), а сверка по опросу – каждые 30 секунд (пачки по 20 просроченных курсоров). Включать что-либо отдельно не нужно.
Доступность провайдеров различается, и это стоит прочитать внимательно:
| Провайдер | Как подключается | Входящие изменения на практике |
|---|---|---|
| amoCRM / Kommo | OAuth, самостоятельно в мастере | опрос – см. раздел «Приём вебхуков и модели доверия» |
| RetailCRM | API-ключ, самостоятельно в мастере | опрос |
| OzmaCRM | OIDC-клиент плюс логин пользователя, самостоятельно в мастере | только опрос |
| Bitrix24 | подключить нельзя – эндпоинт подключения отвечает 503 | – |
Адаптер Bitrix24 реализован полностью и проходит тот же контрактный набор тестов, что и остальные, но OAuth-приложение для него на платформе не настроено, поэтому подключаться не к чему. В мастере его карточка помечена как недоступная, а не даёт заполнить форму, которая всё равно упадёт.
Как достигается провайдер
Любой запрос к 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 не подписывают тела вебхуков, поэтому
аутентификацией служит сам непрозрачный маршрут приёма. Сверх этого адаптер
принимает JSON или form-encoded не больше 256 КБ, отклоняет дубли ключей JSON,
хвостовые данные и вложенность глубже 64 уровней и требует ровно одно событие
сущности на доставку. Если в теле объявлен account[subdomain], он обязан
совпасть с подключённым аккаунтом. Принимаются события добавления и изменения
контакта и добавления, изменения и смены статуса лида. Затем запись
перечитывается через API, и её updated_at не должен быть старше события, так
что авторитетное состояние никогда не берётся из тела вебхука.
Опрос. Контакты листаются по возрастанию updated_at, по 50 на страницу, с
зашифрованным курсором.
Bitrix24
Сегодня Bitrix24 подключить нельзя. POST /api/bots/{botID}/crm/connections/bitrix24/start
отвечает 503: OAuth-приложения Bitrix24 на платформе нет, а маршрута с
API-ключом, как у RetailCRM, для него не существует. Дальше описан готовый и
покрытый тестами адаптер, до которого пока нельзя добраться, – читайте это как
справку, а не как инструкцию по настройке.
Предварительные условия. Либо локальное OAuth-приложение на портале, либо входящий вебхук. Одно подключение использует что-то одно, никогда оба сразу.
Режим OAuth. Согласие даётся на https://<портал>/oauth/authorize/, обмен
токенами идёт на фиксированный сервер oauth.bitrix.info. В ответе с токеном
должен быть домен oauth.bitrix.info и клиентский эндпоинт ровно
https://<портал>/rest/, иначе подключение отклоняется как несовпадение
портала. Возвращённый member_id обязателен и сохраняется: позже им
аутентифицируются вебхуки.
Режим входящего вебхука. Портал, member id и токен вебхука запечатываются
вместе, а REST-вызовы адресуются как
/rest/<member id>/<токен>/<метод>.json. В этом режиме refresh-токена нет,
поэтому подключение сообщает, что OAuth недоступен. Так как кред живёт прямо в
пути запроса, ошибки транспорта всегда сводятся к стабильному коду, а не цитируют
адрес.
Минимум прав. Выдавайте только те 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:<ключ идемпотентности>], чтобы повторную доставку было видно
в таймлайне.
Вебхуки. 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/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 (от 16 до 256 печатных символов) и который вы
вставляете в шаблон вебхука самой RetailCRM; тогда доставки обязаны нести его в
заголовке X-Mybot-Webhook-Token, и он сравнивается за постоянное время.
Подключение без такого токена отвечает webhook_unsupported, и единственным
входящим транспортом остаётся опрос – ровно это и сообщают возможности
подключения. В теле самостоятельного подключения выше поля для этого токена
пока нет, поэтому созданное вами подключение работает только на опросе. Тела
ограничены 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 нет, и маршрута приёма для OzmaCRM в MyBot сегодня тоже нет. Поэтому подключение OzmaCRM получает входящие изменения только по циклу опроса; триггера, который можно было бы установить, пока не существует.
Обнаружение полей и маппинг
Маппинг – это неизменяемая ревизия. Правка маппинга создаёт новую ревизию и не переосмысляет уже запущенную задачу, поэтому сегодняшнее изменение не может переписать смысл записи, поставленной в очередь раньше.
Каждое сопоставление поля называет:
- локальный путь –
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 и становятся событиями клиента и целями (см. раздел «Сделки, деньги и атрибуция»). Поля сделки в редакторе маппинга по-прежнему настраиваются и используются для входящих изменений и для сделок, которые вы отправляете из диалога.
Исходящие записи, связи и идемпотентность
Каждая запись несёт стабильный ключ идемпотентности по записи и приводит к сохранённой связи – паре вашего локального идентификатора и 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, сравнивается за постоянное время; подключение без сохранённого токена отвечает webhook_unsupported |
Ключ маршрута случаен для каждого подключения и хранится только в виде хеша, а провайдер закрепляется в момент регистрации: отправка тела amoCRM на маршрут RetailCRM или на неподключённое подключение – это не отдельная ошибка, а тот же самый 404, что и неизвестный маршрут. Доставка, попавшая в нужный маршрут, но не прошедшая проверку адаптера, получает один общий 400. Ни один из ответов не сообщает, существует ли маршрут или подключение. Любое тело ограничено 256 КиБ, а на чтение даётся 10 секунд.
Сегодня эти маршруты ещё не самостоятельные. Адрес маршрута для подключения генерируется при его создании, но не показывается в кабинете и не возвращается ни одним API, а для токена вебхука RetailCRM нет поля в теле подключения. На практике это значит, что входящие изменения у всех провайдеров приходят по циклу опроса. Модели доверия выше – это то, чему доставка должна будет удовлетворять, когда адрес маршрута начнут выдавать; читайте раздел как справку, а не как чек-лист настройки.
Входящие изменения и подавление эха
Входящее изменение приходит либо из проверенного вебхука, либо из курсора опроса, и оба пути сходятся в одной нормализации.
Общая проблема обоих путей – эхо: 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"
Эндпоинт отвечает {"status":"reconciling"} и перечитывает ленту изменений
провайдера от сохранённого курсора. Используйте его после сбоя у провайдера,
после доставки, которая, по вашему мнению, потерялась, или когда правки на
стороне CRM явно не видно в MyBot. Повторять безопасно: уже применённое изменение
опознаётся по связи и ключу идемпотентности, а не применяется дважды.
Кнопки для этого эндпоинта в кабинете пока нет. Он доступен только по API, со
скоупом crm:write и правом бота «Реакции».
Действия из диалога
Оператор, ведущий переписку, может посмотреть на CRM-сторону конкретного человека и отправить его туда вручную. Эти три действия существуют только по REST – вкладки CRM в операторском инбоксе пока нет. Всем трём нужно право бота «Диалоги».
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, 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 вместе со стабильным кодом последней
ошибки.
Переподключение атомарно проворачивает запечатанные креды относительно ожидаемой ревизии, поэтому два одновременных переподключения не могут переплестись и оставить наполовину провёрнутое подключение. Так как печать привязана к ревизии, прежний шифртекст перестаёт открываться в момент фиксации нового.
Чтобы отключиться безопасно:
- Сначала отзовите кред на стороне CRM – доступ OAuth-приложения для amoCRM и Kommo, API-ключ для RetailCRM, OIDC-клиент для OzmaCRM.
- Уберите входящий путь, если настраивали его: подписку на вебхук в CRM.
- Удалите подключение – кнопкой «Удалить» на карточке подключения или
запросом
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, остальные провайдеры отвечают 503 |
GET /api/integrations/crm/{provider}/callback | – | цель браузерного редиректа; аутентификацией служит одноразовое состояние, токен не нужен |
POST /connections/retailcrm | crm:write | подключить RetailCRM по API-ключу |
POST /connections/ozmacrm | crm:write | подключить OzmaCRM по OIDC-кредам, логину и конфигурации сущностей |
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-интеграцией, но не может менять её поведение.
Известные ограничения
Записаны здесь, чтобы никто не открывал их заново как баги:
- Bitrix24 подключить нельзя – эндпоинт подключения отвечает
503. - Метки MyBot не уезжают в CRM тегами; входящее сопоставление тегов работает.
- В предпросмотре и первичной синхронизации список сделок всегда пуст – своего объекта «сделка» у MyBot нет.
- Приём вебхуков пока не самостоятельный: адрес маршрута подключения нигде не выдаётся, поэтому по факту все провайдеры работают на опросе, а токен вебхука RetailCRM нельзя задать в теле подключения.
- У OzmaCRM нет триггера реального времени – только опрос.
- У повторной сверки нет кнопки в кабинете, только API.
- У действий CRM по подписчику нет интерфейса; они доступны только по API и отсутствуют в Справочнике API.
Смежные страницы
- Интеграции – другие внешние сервисы, которые можно подключить к боту.
- Источники и ключи – REST- и OzmaDB-коннекторы для прямого чтения и записи данных.
- Веб-запросы и вебхуки – входящий и исходящий HTTP.
- Люди и профили – профили, которые читает и пишет маппинг CRM.
- Поддержка – переписка, из которой собирается выгрузка, и приватные заметки.
- ИИ-агент – агент, который может передать квалифицированного лида.
- MCP – шесть инструментов CRM только для чтения.
- Аналитика – куда попадает выигранная сделка как цель.