Привет! Меня зовут Алексей Никитин, руководитель отдела автоматизации направления внедрений в Битрикс24.

REST Битрикс24 можно удобно использовать из Python — для интеграций, миграций, аналитики и служебных скриптов. 

Разбираем библиотеку a24wh: шесть функций, которые упрощают запросы, пагинацию, batch-вызовы и обработку ошибок.

Когда говорят про Битрикс24, рядом почти автоматически всплывает PHP. Это нормально: сам продукт исторически вырос рядом с этой экосистемой. Но при этом для внешних интеграций REST Битрикс24 не обязывает писать доработки именно на PHP. 

Если задача находится снаружи продукта, задачи примерно такие:

  • Выгрузить сделки.

  • Перенести данные между порталами.

  • Собрать аналитику.

  • Написать служебный микросервис.

  • Сделать разовую миграцию.

Во всех этих сценариях Python чувствует себя более чем уверенно.

За время работы я пришёл к простой мысли: сам REST не проблема. Проблема начинается вокруг него: собрать запрос, обработать ошибку, не утонуть в пагинации и не переписывать одну и ту же обвязку в каждом новом скрипте. Постепенно у меня накопился набор рабочих helper-функций.

Я довёл свои наработки до состояния полноценной библиотеки и 12 апреля 2026 года выложил в PyPI.

Что такое библиотека a24wh и зачем она нужна

a24wh не пытается быть большим фреймворком. Это маленькая, понятная и минималистичная библиотека для работы со входящими вебхуками Битрикс24 из Python. Её главная задача простая: взять REST-метод из документации, передать параметры и без лишней боли получить реальные данные портала.

На сегодня актуальная версия библиотеки — a24wh 0.1.2, и ставится она одной командой:

pip install a24wh

Почему Python хорошо подходит для внешних доработок Битрикс24

REST хорош тем, что ему всё равно, на каком языке написан клиент. Если система умеет принимать HTTP-запросы и отдавать JSON, дальше уже можно выбирать инструмент под задачу.

В случае внешних доработок Python мне нравится по нескольким причинам:

  1. На нём быстро писать прикладные скрипты.

  2. Он отлично подходит для миграций и промежуточных обработчиков.

  3. Удобно брать для анализа данных, особенно вместе с pandas.

  4. Python хорошо встраивается в сервисы на Django и Flask.

  5. Одинаково удобно делать и маленькую разовую утилиту, и долгоживущий сервис.

Если нужно не залезать внутрь ядра продукта, а что-то получить из Битрикс24, преобразовать и пересчитать, Python хорошо подходит для таких прикладных задач.

Типовой пример: выгрузили сделки или элементы смарт-процесса, собрали из них DataFrame, посчитали суммы и потери и отдали результат в отчёт, файл или любой другой портал. Именно для таких задач удобен тонкий слой над REST API: он берет на себя запросы, пагинацию и обработку ошибок, но оставляет структуру методов Битрикс24 понятной разработчику.

Как быстро стартовать со входящих вебхуков

Если вы только начинаете работать с REST Битрикс24, входящий вебхук почти всегда самый быстрый путь к первому результату.

На стороне портала это выглядит просто. Что нужно сделать:

  1. Открыть раздел для разработчиков.

  2. Создать входящий вебхук для вызова REST API.

  3. Выбрать нужные права.

  4. Получить URL вебхука.

Хук выглядит так:

https://example.bitrix24.ru/rest/1/xxxxxxxxxxxxxxxx/

Обратите внимание на три вещи.

Скоупы лучше выдавать ровно под задачу. Практическое правило простое: чем уже вебхук заточен под конкретную роль, тем лучше и для безопасности, и для поддержки.

Не надо включать права, если не знаете точно, что они нужны. Если работаете только с CRM, обычно достаточно crm. Если хотите слать технические сообщения в чат, нужно поставить im.

Вебхук не обходит права пользователя. Для интеграций это очень здоровая логика безопасности.

Вебхук работает в контексте прав того пользователя, от имени которого он создан. Если пользователь не должен видеть определенные сделки, лиды или элементы, вебхук тоже не будет давать к ним доступ. 

Секрет вебхука не надо светить в коде. URL вебхука содержит чувствительную часть. Поэтому хранить его лучше в переменных окружения или в защищённой конфигурации.

Шесть главных функций

Долгое время у меня была не библиотека, а скорее набор функций. Отдельные модули переезжали из проекта в проект, копировались из старых интеграций или дорабатывались уже по месту использования.

Такой подход работает, пока вы один и весь контекст умещается у вас в голове. Но чем больше задач и чем чаще вы возвращаетесь к одним и тем же сценариям, тем сильнее повторяющуюся механику хочется упаковать. Так выросла a24wh.

Мне хотелось сохранить две вещи:

  1. Минимализм.

  2. Достаточность для большинства повседневных задач. 

Сейчас публичный API библиотеки состоит из шести функций:

  • request24 — основной помощник для классического REST Битрикс24.

  • req24 — похожий helper для REST v3.

  • save_json сохраняет ответ в JSON.

  • batch_list массово читает списочные методы через batch.

  • batch_by_params запускает один метод с разными наборами параметров.

  • to_chat отправляет служебное сообщение в чат портала.

Главная идея здесь прямолинейная: библиотека не прячет REST Битрикс24, а делает его использование короче, стабильнее и предсказуемее.

1. request24 — основной метод

Главный повседневный сценарий. Берём метод из документации, собираем параметры и вызываем REST. Например, нужно получить сделку:

# подключаем модуль для работы с переменными окружения
import os
from dotenv import load_dotenv
load_dotenv()

# импортируем функцию для вызова классических REST-методов Битрикс24
from a24wh import request24

# получаем URL входящего вебхука из переменной окружения
webhook = os.getenv("A24WH_DEFAULT_WEBHOOK_URL")

# запрашиваем сделку по ее идентификатору
response = request24(
    # указываем метод получения сделки
    method="crm.deal.get",

    # передаем идентификатор сделки
    parameters={"id": 42},

    # передаем URL входящего вебхука
    webhook_url=webhook,
)

# проверяем, произошла ли ошибка при выполнении запроса
if "error_result" in response:
    # выводим сообщение об ошибке
    print("Ошибка:", response["error_message"])
else:
    # выводим данные полученной сделки
    print(response["result"])

Что тут хорошо:

  • Имя метода совпадает с документацией Битрикс24.

  • Параметры передаются обычным словарём Python.

  • На выходе всегда dict.

  • Для ошибок есть единый и понятный контракт.

По сути, это и есть главное обещание библиотеки: минимум лишнего кода между вами и реальным ответом портала.

Что приходит на выходе: единый контракт ответа

Очень удобно, когда библиотека стандартизирует не только вызов, но и то, как вы потом работаете с результатом.

У a24wh запросные функции всегда возвращают dict.

Успешный случай выглядит так:

{"result": ...}

Ошибка выглядит так:

{
    "error_result": True,
    "error_type": "http_error",
    "error_code": "500",
    "error_message": "Bitrix24 returned HTTP 500.",
    "method": "crm.deal.get",
    "portal": "example.bitrix24.ru",
    "status_code": 500,
}

Поэтому в прикладном коде достаточно проверить наличие error_result:

if "error_result" in response:
    print(response["error_type"])
    print(response["error_code"])
    print(response["error_message"])
else:
    print(response["result"])

Если после ошибки продолжать работу нельзя, можно вызвать исключение и сразу остановить скрипт:

if "error_result" in response:
    raise RuntimeError(
        f"{response['method']} failed: "
        f"{response['error_type']} / {response['error_code']} / {response['error_message']}"
    )

Это особенно удобно в автоматизации, потому что не надо каждый раз вспоминать, что вернёт конкретная ветка внутренней обвязки. Вы заранее знаете, как библиотека сообщает об ошибке.

2. req24 — отдельный helper для REST 3.0 

В Битрикс24 постепенно появляется REST 3.0. Методы новой версии вызываются по другому адресу: после /rest/ в URL добавляется сегмент /api/. Для таких методов в библиотеке есть отдельная функция req24:

# подключаем модуль для работы с переменными окружения
import os
from dotenv import load_dotenv
load_dotenv()
# импортируем функцию для вызова методов REST 3.0
from a24wh import req24

# получаем URL входящего вебхука из переменной окружения
webhook = os.getenv("A24WH_DEFAULT_WEBHOOK_URL")

# вызываем метод REST 3.0 для получения задачи
response = req24(
    # указываем метод из документации Битрикс24
    method="tasks.task.get",

    # передаем параметры запроса
    parameters={
        # указываем идентификатор задачи
        "id": 42,

        # выбираем дополнительные поля ответственного
        "select": [
            "responsible.name",
            "responsible.email",
        ],
    },

    # передаем URL входящего вебхука
    webhook_url=webhook,
)

# проверяем, вернула ли библиотека ошибку
if "error_result" in response:
    # выводим понятное сообщение об ошибке
    print("Ошибка:", response["error_message"])
else:
    # получаем задачу из ответа Битрикс24
    task = response["result"]["item"]

    # выводим название задачи
    print(task["title"])

    # выводим данные ответственного
    print(task.get("responsible"))

В примере мы получаем задачу и сразу запрашиваем имя и почту ответственного. REST 3.0 позволяет обращаться к полям связанных объектов через точку, поэтому для этих данных не приходится отдельно вызывать метод получения пользователя.

По способу использования req24 похожа на request24: передаём имя метода, словарь параметров и URL вебхука, а затем проверяем наличие error_result. Разница находится внутри библиотеки. req24 формирует адрес REST 3.0, отправляет JSON и разбирает новый формат ошибок.

Выбирать функцию нужно по документации конкретного метода:

  • request24(...) — классический REST Битрикс24

  • req24(...) — REST 3.0 

Если метод относится к REST 3.0, в документации для него будет указан адрес с /rest/api/.

3. save_json: почему полезно сохранять сырой JSON

Один из самых недооценённых приёмов в интеграционной работе — сохранить исходный ответ до того, как вы начнёте выбирать из него нужные поля и преобразовывать данные под свою задачу.

Когда вы впервые вызываете новый метод, особенно на сложной CRM-сущности, очень полезно увидеть исходный ответ Битрикс24 как он есть: какие поля пришли, как они вложены и какие значения вернул конкретный портал. Для этого в библиотеке есть save_json:

# импортируем функции для вызова REST-методов и сохранения ответа в JSON
from a24wh import request24, save_json

# запрашиваем список сделок
response = request24(
    # указываем метод получения списка сделок
    method="crm.deal.list",

    # передаем параметры запроса
    parameters={
        # сортируем сделки по идентификатору
        "order": {"ID": "ASC"},

        # выбираем сделки из воронки с идентификатором 0
        "filter": {"=CATEGORY_ID": 0},

        # указываем поля, которые нужно получить
        "select": ["ID", "TITLE", "STAGE_ID"],
    },

    # передаем URL входящего вебхука
    webhook_url=webhook,

    # автоматически получаем все страницы списка
    all_iters=True,
)

# сохраняем ответ в JSON-файл
file_path = save_json(
    response,
    file_title="crm.deal.list.debug",
)

# выводим путь к сохраненному файлу
print(file_path)

В реальной жизни это дает сразу несколько преимуществ:

  • JSON удобно читать глазами.

  • По нему проще понять вложенность и реальные поля.

  • Его можно приложить к задаче или техразбору.

  • Его можно отдавать ИИ-агентам как фактический материал для анализа.

Последний пункт сейчас особенно практичен. Если уж использовать ИИ в инженерной работе, то лучше кормить его не выдуманной схемой, а живым ответом с конкретного портала.

4. batch_list(...) — Как забирать много данных

На маленьких объемах хватает обычного request24. Но как только дело доходит до сотен и тысяч записей, нужны более удобные режимы.

В a24wh для этого есть два основных маршрута.

Первый вариант

Это самый простой способ сказать библиотеке: “собери мне все страницы списка”.

# запрашиваем список сделок
response = request24(
    # указываем метод получения списка сделок
    method="crm.deal.list",

    # передаем параметры запроса
    parameters={
        # сортируем сделки по идентификатору
        "order": {"ID": "ASC"},

        # выбираем сделки из воронки с идентификатором 16
        "filter": {"=CATEGORY_ID": 16},

        # получаем только идентификаторы сделок
        "select": ["ID"],
    },

    # передаем URL входящего вебхука
    webhook_url=webhook,

    # автоматически получаем все страницы списка
    all_iters=True,
)

Такой режим хорош, когда данных немного, хочется быстро получить всё без отдельной логики пагинации и сам списочный метод не слишком тяжёлый для портала. Для повседневных рабочих сценариев это приемлемый баланс между простотой и удобством.

Второй вариант 

Если списочные данные нужно забирать интенсивнее, можно использовать batch_list.

# импортируем функцию для получения данных через batch-запросы
from a24wh import batch_list

# запрашиваем список сделок через batch
response = batch_list(
    # указываем метод получения списка сделок
    method="crm.deal.list",

    # передаем параметры запроса
    parameters={
        # сортируем сделки по идентификатору
        "order": {"ID": "ASC"},

        # выбираем сделки из воронки с идентификатором 14
        "filter": {"=CATEGORY_ID": 14},

        # указываем поля, которые нужно получить
        "select": ["ID", "TITLE", "STAGE_ID", "CONTACT_ID"],
    },

    # передаем URL входящего вебхука
    webhook_url=webhook,
)

Этот подход удобен там, где хочется читать не “по одной странице за раз”, а более собранно, через batch-запросы.

5. batch_by_params — практический шаблон. Сначала ID, потом детали

Особенно полезно в рабочей практике.

Иногда выборка большого набора объектов сразу со многими полями в select оказывается для сервера тяжелее, чем сначала получить только ID, а потом уже пакетно добрать детали.

В таких случаях полезна схема:

  1. Сначала запрашиваем только ID по фильтру.

  2. Затем через batch_by_params запрашиваем полные карточки по 50 штук в одном batch.

Пример со сделками:

# импортируем функции для обычного REST-запроса и пакетной загрузки данных
from a24wh import request24, batch_by_params

# запрашиваем идентификаторы сделок
ids_response = request24(
    # указываем метод получения списка сделок
    method="crm.deal.list",

    # передаем параметры запроса
    parameters={
        # сортируем сделки по идентификатору
        "order": {"ID": "ASC"},

        # выбираем сделки из основной воронки, измененные после указанной даты
        "filter": {
            "=CATEGORY_ID": 0,
            ">DATE_MODIFY": "2026-01-01T00:00:00+03:00",
        },

        # получаем только идентификаторы сделок
        "select": ["ID"],
    },

    # передаем URL входящего вебхука
    webhook_url=webhook,

    # автоматически получаем все страницы списка
    all_iters=True,
)

# проверяем, произошла ли ошибка при получении идентификаторов
if "error_result" in ids_response:
    # останавливаем выполнение и выводим сообщение об ошибке
    raise RuntimeError(ids_response["error_message"])

# создаем список параметров для получения каждой сделки по идентификатору
params_list = [
    {"id": int(item["ID"])}
    for item in ids_response["result"]
]

# пакетно запрашиваем полные данные сделок
deals_response = batch_by_params(
    # указываем метод получения сделки
    method="crm.deal.get",

    # передаем список параметров с идентификаторами сделок
    params_list=params_list,

    # передаем URL входящего вебхука
    webhook_url=webhook,
)

На нагруженных порталах это часто оказывается заметно лучше, чем просить сразу всё и во всех полях, потому что:

  1. Серверу проще отдать легкий список ID.

  2. Детальные карточки потом получаются через get.

  3. batch режет вызовы на аккуратные пачки.

Пример со смарт-процессом и req24

Со смарт-процессами идея ровно та же. Ничего специального для них не требуется:

# импортируем функцию для вызова методов REST
from a24wh import request24

# вызываем метод для получения элементов смарт-процесса
response = request24(
    # указываем метод Битрикс24
    method="crm.item.list",

    # передаем параметры запроса
    parameters={
        # указываем идентификатор типа смарт-процесса
        "entityTypeId": 190,

        # выбираем элементы из воронки с идентификатором 3
        "filter": {"=categoryId": 3},

        # указываем поля, которые нужно получить
        "select": ["id", "title", "stageId", "assignedById"],
    },

    # передаем URL входящего вебхука
    webhook_url=webhook,
)

# проверяем, вернула ли библиотека ошибку
if "error_result" in response:
    # выводим сообщение об ошибке
    print(response["error_message"])
else:
    # выводим полученные элементы
    print(response["result"])

Дальше уже всё зависит от объема. Где-то хватит одного запроса, где-то пригодится all_iters, где-то будет разумнее сначала собрать ID, а потом детализировать их batch-вызовами.

Живой кейс: миграция данных между порталами

Один из самых естественных сценариев для Python рядом с Битрикс24 — межпортальная миграция. Пример: на одном портале собрали сделки, преобразовали данные и создали сделки на другом портале.

Упрощённый пример может выглядеть так:

# подключаем модуль для работы с переменными окружения
import os

# импортируем функцию для вызова классических REST-методов Битрикс24
from a24wh import request24

# получаем вебхук исходного портала
source_webhook = os.getenv("SRC_WEBHOOK")

# получаем вебхук целевого портала
target_webhook = os.getenv("DST_WEBHOOK")

# запрашиваем сделки на исходном портале
source = request24(
    # указываем метод получения списка сделок
    method="crm.deal.list",

    # передаем параметры запроса
    parameters={
        # выбираем сделки из воронки с идентификатором 0
        "filter": {"=CATEGORY_ID": 0},

        # указываем поля, которые нужно получить
        "select": ["ID", "TITLE", "OPPORTUNITY", "COMMENTS"],
    },

    # передаем вебхук исходного портала
    webhook_url=source_webhook,

    # автоматически получаем все страницы списка
    all_iters=True,
)

# проверяем, произошла ли ошибка при получении сделок
if "error_result" in source:
    # останавливаем выполнение и выводим сообщение об ошибке
    raise RuntimeError(source["error_message"])

# перебираем полученные сделки
for deal in source["result"]:
    # создаем сделку на целевом портале
    add_response = request24(
        # указываем метод создания сделки
        method="crm.deal.add",

        # передаем поля новой сделки
        parameters={
            "fields": {
                # копируем название сделки
                "TITLE": deal.get("TITLE"),

                # копируем сумму сделки
                "OPPORTUNITY": deal.get("OPPORTUNITY"),

                # копируем комментарий
                "COMMENTS": deal.get("COMMENTS"),
            },
        },

        # передаем вебхук целевого портала
        webhook_url=target_webhook,
    )

    # проверяем, удалось ли создать сделку
    if "error_result" in add_response:
        # выводим идентификатор исходной сделки и сообщение об ошибке
        print(
            "Не удалось перенести запись:",
            deal.get("ID"),
            add_response["error_message"],
        )

В реальной миграции к этому каркасу почти наверняка добавляется логика конкретного проекта: 

  • Таблица соответствия стадий.

  • Преобразование пользовательских полей.

  • Сопоставление пользователей.

  • Журналирование.

  • Архивирование исходных ответов в JSON.

Но сам каркас сценария получается очень прозрачным. И в этом, на мой взгляд, большая сила Python в интеграциях: сначала быстро делаем рабочее решение, потом постепенно наращиваем вокруг него дисциплину.

6. to_chat: отправка техсообщений в портал

Когда скрипт работает долго, когда идет перенос данных, когда ночной обмен падает посреди выполнения или когда просто хочется видеть статус не в консоли сервера, а прямо в Битрикс24, очень выручает отправка сообщений в чат.

Для этого в a24wh есть to_chat:

import os
from a24wh import to_chat

response = to_chat(
    text="Миграция сделок завершена. Перенесено 248 записей.",
    chat_id="chat11111",
    webhook_url=os.getenv("WEBHOOK_CHAT"),
)

Функция маленькая, но очень полезная. Под капотом используется im.message.add, а значит нужен scope im.

Какие удобные вещи можно делать на практике:

  • Отправлять сообщения о старте.

  • Писать прогресс по этапам.

  • Уведомлять об ошибках.

  • Закрывать процесс финальным статусом.

В итоге скрипт перестает быть чёрным ящиком в планировщике и начинает разговаривать с командой в привычном рабочем контуре.

Логирование как часть нормальной инженерной жизни

Отдельно хотелось, чтобы библиотека хорошо жила в реальных Python-проектах, а не только в одноразовых локальных скриптах. У a24wh есть свой логгер a24wh и набор переменных окружения:

A24WH_DEFAULT_WEBHOOK_URL=
A24WH_TIMEOUT=60
A24WH_RETRY_COUNT=3
A24WH_RETRY_DELAY=1.0
A24WH_REQUEST_DELAY=1.0
A24WH_AUTO_CONFIGURE_LOGGING=0
A24WH_LOG_LEVEL=INFO
A24WH_LOG_TO_CONSOLE=1
A24WH_LOG_TO_FILE=0
A24WH_LOG_FILE=a24wh.log

Библиотека не лезет без спроса управлять логированием всего приложения. Пока вы сами не включили автоконфигурацию, она нормально сосуществует с существующим logging config проекта. Это особенно полезно, если вы работаете в Django, Flask и любом сервисе с единым конфигом логирования. То есть логи библиотеки не живут отдельной жизнью, а могут органично встраиваться в общую систему наблюдаемости проекта.

Библиотека умеет брать вебхук по умолчанию из A24WH_DEFAULT_WEBHOOK_URL. Это удобно для простых локальных сценариев. Но на практике довольно часто приходится работать сразу с несколькими порталами, например: забрать данные с одного портала, перенести в другой, отправить статусы в третий.

При работе с несколькими порталами удобнее хранить их вебхуки в отдельных переменных и явно передавать нужный адрес в каждый вызов:

source_webhook = os.getenv("SRC_WEBHOOK")

target_webhook = os.getenv("DST_WEBHOOK")

chat_webhook = os.getenv("CHAT_WEBHOOK")

У такого подхода есть несколько плюсов. Во-первых, проще читать код. Во-вторых, так меньше риска случайно отправить данные не туда. В-третьих, тут очень к месту старое питоновское правило: явное лучше неявного.

Небольшой пример с pandas

Раз уж Python хорош не только для интеграций, но и для аналитики, покажу совсем короткий пример. Главная польза — библиотека сама получает все страницы и возвращает обычные данные Python, которые сразу можно передать в pandas.

# подключаем pandas для анализа табличных данных
import pandas as pd

# импортируем функцию для вызова REST-методов Битрикс24
from a24wh import request24

# запрашиваем список сделок
response = request24(
    method="crm.deal.list",
    parameters={
        "order": {"ID": "ASC"},
        "filter": {"=CATEGORY_ID": 0},
        "select": ["ID", "TITLE", "OPPORTUNITY", "STAGE_ID"],
    },
    webhook_url=webhook,
    all_iters=True,
)

# останавливаем скрипт, если запрос завершился с ошибкой
if "error_result" in response:
    raise RuntimeError(response["error_message"])

# преобразуем полученные сделки в таблицу pandas
df = pd.DataFrame(response["result"])

# группируем сделки по стадиям и считаем их количество
print(df.groupby("STAGE_ID")["ID"].count())
Пример отработки
Пример отработки

Здесь Python очень красиво раскрывается рядом с Битрикс24. a24wh собирает все страницы ответа в обычный список словарей. Его можно сразу преобразовать в DataFrame и перейти к анализу без ручной обработки пагинации.

Почему a24wh получилась именно такой

Мне не хотелось делать огромный комбайн на все случаи жизни. Хотелось сделать библиотеку, которую можно быстро:

  • Поставить.

  • Прочитать.

  • Понять.

  • Встроить в проект.

  • При необходимости спокойно обложить своей бизнес-логикой.

Поэтому a24wh сознательно маленькая. Она не пытается заменить документацию Битрикс24 и не строит поверх REST собственный мир. Наоборот, она исходит из того, что документация уже является главным источником правды, а задача библиотеки — сделать путь от документации до рабочего Python-кода коротким и надежным.

В этом смысле основной сценарий всегда один и тот же:

  1. Нашли нужный метод в документации Битрикс24.

  2. Собрали параметры.

  3. Вызвали request24 или req24.

  4. Проверили error_result.

  5. Обработали result.

Итог

Если вы работаете с Битрикс24 извне, язык не должен быть ограничением. REST Битрикс24 спокойно живет не только в PHP-мире, а Python отлично подходит для интеграций, миграций, служебных скриптов и небольших сервисов.

Ценность a24wh для меня в трех вещах:

  1. Быстрый вход в REST Битрикс24 из Python.

  2. Единый контракт ответа и ошибок.

  3. Достаточный набор утилит для большинства повседневных задач.

Если интересно попробовать, для начала установите библиотеку командой pip install a24wh. Затем выберите REST-метод в документации, передайте его параметры и получите данные портала в Python.

Комментарии (1)


  1. dmitrijtest24
    10.08.2026 09:29

    В данном предложении не совсем верно утверждение, что продукт «исторически вырос рядом с этой экосистемой» (PHP).

    Правильнее сказать, что Битрикс24 исторически вырос на этой экосистеме или в этой экосистеме.