1С Битрикс · 8 мин чтения

crm.deal.get в Битрикс24: сделка с пользовательскими полями

Метод crm.deal.get принимает идентификатор сделки и возвращает её поля вместе с пользовательскими, которые в ответе лежат под ключами вида UF_CRM_1721134404. Работает он до сих пор, но в справке Битрикс24 у него стоит пометка «Развитие метода остановлено, используйте crm.item.get». Поэтому ниже я показываю оба варианта: старый вызов, чтобы вы понимали, что отдаёт уже написанный чужой код, и новый, на который я перевожу свои интеграции.

Статус crm.deal.get: что значит «развитие остановлено»

Пометка DEPRECATED в документации не означает, что метод завтра отключат. Это значит, что новые возможности в него добавляться не будут, а все новые сущности и поля появляются в универсальных методах crm.item.*. Для сделок это entityTypeId = 2. Я сталкивался с этим на практике: интеграция, написанная три года назад на crm.deal.get, продолжает отдавать данные, но часть связей в ответе помечена как устаревшая. Например, идентификатор контакта в старом ответе описан как «Устаревшее», а список контактов сделки предлагается брать через crm.deal.contact.items.get или универсальный crm.item.get.

Моё правило такое. Если интеграция уже работает и менять её нет повода, я не трогаю crm.deal.get. Новый код пишу сразу на crm.item.get, потому что переписывать потом придётся в любом случае.

Параметр crm.deal.get crm.item.get
Статус в документации Устаревший Актуальный
Параметры запроса id entityTypeId, id
Имена обычных полей Верхний регистр: TITLE, STAGE_ID camelCase: title, stageId
Пользовательские поля UF_CRM_... ufCrm_...
Где лежит результат Прямо в result В result.item

Вебхук и вызов: как получить данные сделки по ID

Для серверной интеграции проще всего входящий вебхук. Он создаётся в разделе «Разработчикам» портала, при создании выбираются права, и в нужны только «CRM». В итоге вы получаете URL вида https://ваш-портал.bitrix24.ru/rest/ID_ПОЛЬЗОВАТЕЛЯ/КОД_ВЕБХУКА/. К нему дописывается имя метода. Секретный код в URL равен паролю: не кладите его в репозиторий и в код страниц на Tilda, которые видит любой посетитель.

Самый простой запрос к старому методу:

curl -X POST \
  -H "Content-Type: application/json" \
  -d '{"id": 123}' \
  https://ваш-портал.bitrix24.ru/rest/1/КОД_ВЕБХУКА/crm.deal.get.json

В ответе придёт объект result с полями сделки и объект time со статистикой выполнения. Нужные мне ключи: TITLE, STAGE_ID, OPPORTUNITY, CURRENCY_ID, ASSIGNED_BY_ID, CONTACT_ID, COMPANY_ID. Список всех полей, включая пользовательские, отдаёт crm.deal.fields.

Если сделка не найдена, Битрикс24 вернёт ошибку в теле ответа, поэтому в коде я всегда проверяю наличие ключа error, а не только HTTP-статус.

PHP-обработчик на обычном хостинге

Для Tilda и WordPress такой запрос я делаю из PHP-скрипта на том же хостинге, где живёт сайт. Отдельный сервер не нужен: хватает расширения curl, которое на shared-хостинге включено почти всегда.

<?php
function b24_call(string $method, array $params): array
{
    $url = 'https://ваш-портал.bitrix24.ru/rest/1/КОД_ВЕБХУКА/' . $method . '.json';
    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_POST           => true,
        CURLOPT_HTTPHEADER     => ['Content-Type: application/json'],
        CURLOPT_POSTFIELDS     => json_encode($params),
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT        => 15,
    ]);
    $raw = curl_exec($ch);
    curl_close($ch);
    $data = json_decode((string)$raw, true);
    return is_array($data) ? $data : ['error' => 'bad_response'];
}

$res = b24_call('crm.deal.get', ['id' => 123]);
if (isset($res['error'])) {
    exit('Ошибка: ' . $res['error']);
}
echo $res['result']['TITLE'];

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

Бесплатный материал

🎁 Полезный скрипт в подарок

Подпишитесь на Telegram - пришлю готовый скрипт по этой теме.

Без спама. Отписка в 1 клик.

Пользовательские поля сделки в ответе REST

Главная причина, по которой читатели ищут именно этот запрос, пользовательские поля. Они создаются в интерфейсе CRM, и у каждого есть технический код. В старом методе он выглядит как UF_CRM_1721134404: префикс и число, которое портал присвоил при создании поля. Название поля из интерфейса в ответе не приходит, только код.

Есть тонкость, на которой теряют время: код поля в разных порталах разный. Поле «Источник заказа» на тестовом портале и на боевом получит два разных числа. Поэтому в коде я не зашиваю UF_CRM_1721134404 намертво, а вынимаю соответствие «название поля, код» из метода crm.deal.userfield.list и кладу в конфиг. Так перенос с тестового портала на боевой сводится к смене одного файла.

curl -X POST \
  -H "Content-Type: application/json" \
  -d '{"filter": {"FIELD_NAME": "UF_CRM_1721134404"}}' \
  https://ваш-портал.bitrix24.ru/rest/1/КОД_ВЕБХУКА/crm.deal.userfield.list.json

В ответе будет описание поля: тип, подписи на языках портала и, для списков, перечень значений.

Какие значения приходят для разных типов

По моему опыту, больше всего вопросов вызывают три типа:

  • Список. Приходит числовой идентификатор значения, а не текст. Расшифровку нужно брать из описания поля (crm.deal.userfield.get, ключ LIST) и кэшировать у себя.
  • Множественное поле. Приходит массив, даже если выбрано одно значение. Код, который ждёт строку, ломается на пустом массиве.
  • Файл, привязка, деньги. Это структуры или строки специального формата. Например, денежное поле хранит сумму и валюту в одной строке через разделитель. Как именно эти значения выглядят на вашем портале, проверяйте тестовым вызовом, а не по памяти.

Пустое пользовательское поле приходит как пустая строка или null, и проверять нужно оба случая.

Замена: crm.item.get для получения элемента CRM

Универсальный метод принимает тип сущности и идентификатор. Для сделки тип равен 2. Результат лежит во вложенном объекте item.

curl -X POST \
  -H "Content-Type: application/json" \
  -d '{"entityTypeId": 2, "id": 123}' \
  https://ваш-портал.bitrix24.ru/rest/1/КОД_ВЕБХУКА/crm.item.get.json

В ответе поля уже в camelCase: title, stageId, opportunity, assignedById. Пользовательские поля приходят в виде ufCrm_1721134404, то есть число то же, а запись другая. В документации метода есть параметр, который возвращает пользовательские поля в оригинальной записи UF_CRM_... (useOriginalUfNames). Перед использованием сверьтесь с актуальной справкой, потому что он нужен только при миграции со старого кода, а в новом я просто работаю с camelCase.

В PHP-функции выше для нового метода меняются только вызов и путь к данным:

$res = b24_call('crm.item.get', ['entityTypeId' => 2, 'id' => 123]);
if (isset($res['error'])) {
    exit('Ошибка: ' . $res['error']);
}
$deal = $res['result']['item'];
echo $deal['title'];
echo $deal['ufCrm_1721134404'] ?? '';

Если вам нужны не все поля, список можно сузить. Для этого в универсальных методах есть выборка по нужным ключам, а для списков сделок аналогичную роль играет crm.item.list с фильтром и параметром select. Единственный запрос на сделку с десятком полей обходится дёшево, а вот массовая выгрузка всех полей по тысяче сделок упирается в лимиты, о которых дальше.

Сделка после события: вебхук плюс запрос

Чаще всего я получаю сделку не по расписанию, а в ответ на событие. В портале создаётся исходящий вебхук с событием OnCrmDealUpdate и адресом моего PHP-обработчика. В событии приходит только идентификатор сделки (в data[FIELDS][ID]), поэтому полные данные я всё равно забираю отдельным вызовом crm.item.get. Несколько фактов, на которых спотыкаются:

  • Тело запроса приходит в формате application/x-www-form-urlencoded, поэтому читать нужно $_POST, а не php://input как JSON.
  • Событие срабатывает на любое обновление сделки, не только на смену стадии. Если нужна именно стадия, сравнивайте её с сохранённым значением у себя.
  • Повторных отправок нет: если обработчик не ответил или упал, событие потеряно. Когда потери недопустимы, документация советует офлайн-события.
  • У исходящего вебхука настраиваются только адрес и список событий, права на чтение выбираются у входящего вебхука, которым вы потом вызываете REST.
  • В auth приходит application_token. Я сохраняю его при настройке и сверяю при каждом запросе, чтобы обработчик не принимал чужие POST.

Такая схема стоит у меня за рассылкой уведомлений менеджеру в Telegram и за передачей данных сделки в другие системы. В n8n то же самое собирается узлом Webhook и узлом HTTP Request с вызовом crm.item.get. Для бота на aiogram логика та же: событие приходит в обработчик, данные дозапрашиваются по ID.

Несколько сделок сразу: batch и лимиты запросов

Когда сделок много, по одному запросу на каждую не уложиться. Метод batch принимает до 50 вызовов в одном HTTP-запросе. Тело выглядит так:

curl -X POST \
  -H "Content-Type: application/json" \
  -d '{"halt": 0, "cmd": {"d1": "crm.item.get?entityTypeId=2&id=123", "d2": "crm.item.get?entityTypeId=2&id=124"}}' \
  https://ваш-портал.bitrix24.ru/rest/1/КОД_ВЕБХУКА/batch.json

Batch сокращает число HTTP-запросов, но не отменяет ограничения на ресурсоёмкость самих методов. Здесь работают два независимых лимита, и про второй пишут редко.

Лимит Как проявляется Код ошибки
Интенсивность запросов Счётчик превышает порог, следующий запрос блокируется со статусом 503 QUERY_LIMIT_EXCEEDED
Ресурсоёмкость метода Накопленное время выполнения превышает лимит, метод блокируется для вебхука, статус 429 OPERATION_TIME_LIMIT

По справке, интенсивность считается по алгоритму Leaky Bucket: на тарифе «Энтерпрайз» порог 250 при скорости 5 запросов в секунду, на остальных порог 50 при 2 запросах в секунду. Тарифы и цифры меняются, поэтому актуальные значения сверяйте на странице лимитов в документации. Лимит считается по IP, значит несколько скриптов с одного хостинга делят одну квоту.

Второй лимит можно отслеживать: в каждом ответе есть time.operating, накопленное время выполнения метода. Если оно растёт, я увеличиваю паузы между пачками. Списочные методы отдают по 50 записей за страницу, дальше нужно идти по start и смотреть ключ next в ответе.

Типичные ошибки при получении сделки через REST

Из того, что реально приходилось разбирать на проектах:

  • Пустой ответ при верном ID. Вебхук создан под пользователем без доступа к этой сделке. REST работает с правами того, от чьего имени выпущен вебхук.
  • Нет пользовательского поля в ответе. Поле создано для другой сущности (лида, контакта) или код взят с другого портала. Сверяйте через crm.deal.userfield.list.
  • Поля «потерялись» после перехода на crm.item.get. Ключи сменили регистр: TITLE стало title, UF_CRM_... стало ufCrm_.... Код, который ждёт старые имена, получает null.
  • JSON не разбирается. На ошибке лимита вместо данных приходит тело с описанием ошибки. Проверяйте HTTP-статус и ключ error до обращения к result.
  • Секрет вебхука утёк. URL вставили в клиентский скрипт страницы. Выпустите новый вебхук, а запросы переносите на сервер.

Лицензия и тариф портала тоже влияют: набор доступных функций CRM и лимиты зависят от тарифа. Если вы только выбираете условия под интеграцию, лицензии Битрикс24 я подбираю под задачу, а цену называю после обсуждения.

Условия и возможности Битрикс24 меняются, поэтому перед запуском интеграции я сверяюсь с актуальной документацией REST и страницей лимитов.

Частые вопросы

Можно ли ещё использовать crm.deal.get в новых проектах?

Технически метод отвечает, но развитие его остановлено, и в справке рекомендован crm.item.get. Для нового кода я беру универсальный метод, чтобы не переписывать интеграцию при очередном изменении старых методов.

Как узнать код пользовательского поля сделки?

Вызовите crm.deal.userfield.list и найдите поле по подписи, код лежит в FIELD_NAME. Для универсальных методов к нему применяется запись ufCrm_... с тем же числом. Коды различаются между порталами, поэтому храните соответствие в конфиге.

Почему в списке пришло число вместо текста значения?

Поле типа «Список» хранит идентификатор значения. Тексты значений берутся из описания поля через crm.deal.userfield.get, их удобно закэшировать и подставлять у себя.

Что делать, если приходит QUERY_LIMIT_EXCEEDED?

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

Есть задача?

Обсудим в мессенджере

Расскажите, что нужно сделать — отвечу в течение 4 часов в рабочее время. Первая консультация бесплатно.

Продолжая пользование настоящим сайтом Вы выражаете своё согласие на обработку Ваших персональных данных (файлов куки) с использованием Yandex.Metrika.
Понятно