Клиент присылает через сайт скан паспорта и техническое задание — их нужно не потерять в почте, а сразу увидеть в карточке сделки. Или наоборот: бухгалтерия готовит закрывающие документы во внешней системе, и их нужно автоматически прикрепить к сделке, к которой они относятся. Руками это пять кликов: открыть карточку, найти поле, выбрать файл с диска. При десятке сделок в день терпимо, при сотне в день — уже отдельная задача для интеграции. В статье разберём, как прикрепить файл к сделке или лиду через API Битрикс24: в каком виде передавать содержимое файла в запросе, через какой метод, что делать с файлами, которые не помещаются в обычный запрос из-за размера. Материал для разработчиков, которые уже работают с REST API Битрикс24 — если доступа к вебхуку ещё нет, сначала настройте его.
Ниже — два рабочих способа передать файл вложением в сделку или лид через API Битрикс24, у каждого свои ограничения по размеру и по тому, что происходит с уже прикреплёнными файлами при обновлении.
Что понадобится перед началом
- Входящий вебхук с правами на CRM (scope
crm) или REST-приложение с правом на редактирование сделок — без этого метод вернётACCESS_DENIED - В сделке или лиде создано пользовательское поле типа «Файл» (техническое имя вида
UF_CRM_DOC_SCAN) — через интерфейс или методомcrm.deal.userfield.add - Если планируете работать с Диском Битрикс24 — права scope
diskи ID папки, куда будете грузить файлы - PHP 8.0 и новее и официальный класс-хелпер CRest (можно и без него, на чистом curl — логика та же)
- Тестовый портал или тестовая сделка: прикреплённый файл не «отклеивается» так же легко, как меняется текстовое поле — лучше не проверять формат на реальной карточке клиента
[ФОТО: раздел настройки полей сделки в Битрикс24 с созданным пользовательским полем типа «Файл»]
Как передать файл в сделку или лид через API Битрикс24: формат fileData
Шаг 1. Создаём поле типа «Файл» для сделки
Если поля ещё нет — создаём его методом crm.deal.userfield.add (для лида — аналогично, crm.lead.userfield.add). Ключевой параметр — USER_TYPE_ID: file. Если файлов в поле может быть несколько, добавляем MULTIPLE: Y; для одного файла оставляем N.
<?php
require_once('crest.php'); // официальный класс-хелпер CRest от Битрикс24
// Создаём поле "Скан документа" для сделок — можно прикреплять несколько файлов
$result = CRest::call('crm.deal.userfield.add', [
'fields' => [
'FIELD_NAME' => 'DOC_SCAN', // итоговый код поля будет UF_CRM_DOC_SCAN — Битрикс24 просто добавляет префикс UF_CRM_
'USER_TYPE_ID' => 'file',
'MULTIPLE' => 'Y', // разрешаем прикреплять несколько файлов в одно поле
'MANDATORY' => 'N',
'EDIT_FORM_LABEL' => ['ru' => 'Скан документа'],
],
]);
При создании поля через API код предсказуем — UF_CRM_ плюс то, что передано в FIELD_NAME: здесь получится UF_CRM_DOC_SCAN. А вот у поля, созданного через интерфейс администратора, код генерируется автоматически и выглядит как UF_CRM_1712345678 — с подписью поля он никак не связан, и угадать его нельзя. Поэтому код поля всегда берут из ответа метода добавления или отдельным вызовом crm.deal.userfield.list, а не переносят из чужого примера.
Шаг 2. Кодируем файл в base64 и собираем fileData
Здесь легко перепутать формат: у разных методов Битрикс24 файл передаётся по-разному, и это не унифицировано. Для пользовательских полей CRM (UF_CRM_* типа «Файл») нужен объект с ключом fileData, внутри которого массив из двух элементов — имя файла и его содержимое в base64.
<?php
// Читаем файл с сервера и кодируем в base64
$filePath = __DIR__ . '/uploads/contract-scan.pdf';
$fileName = basename($filePath);
$base64Content = base64_encode(file_get_contents($filePath));
// Формат, который понимают файловые поля CRM в Битрикс24
$fileField = [
'fileData' => [$fileName, $base64Content],
];
Для поля с несколькими файлами (MULTIPLE: Y) передаём массив таких объектов; для поля с одним файлом — сам объект, без обёртки в массив. Если отправить массив из нескольких fileData в поле без флага MULTIPLE, метод не вернёт ошибку — он просто отбросит значение целиком, и поле останется пустым.

Шаг 3. Отправляем файл в существующую сделку через crm.deal.update
<?php
// Прикрепляем скан к уже существующей сделке
$result = CRest::call('crm.deal.update', [
'ID' => 4521,
'FIELDS' => [
'UF_CRM_DOC_SCAN' => $fileField, // технический код поля "Скан документа"
],
]);
if (isset($result['result']) && $result['result'] === true) {
echo 'Файл прикреплён к сделке';
}
Отдельная оговорка, и здесь она важнее, чем обычно: в актуальной документации Битрикс24 методы crm.deal.add и crm.deal.update помечены как устаревшие, а для обновления именно файловых полей документация даёт отдельную, более настойчивую рекомендацию — не использовать crm.deal.update/crm.lead.update/crm.contact.update/crm.company.update для этой задачи, а переходить на crm.item.update. Формат fileData при этом не меняется, переносится заменой имени метода и добавлением параметра entityTypeId. Для нового кода стоит сразу закладываться на crm.item.update — в статье используется crm.deal.update, потому что он пока продолжает работать и массово встречается в существующих интеграциях, но это тот случай, когда «пока работает» не равно «рекомендовано».
Шаг 4. Прикрепляем файлы сразу при создании сделки — crm.deal.add
<?php
// Создаём сделку сразу с двумя прикреплёнными документами
$result = CRest::call('crm.deal.add', [
'FIELDS' => [
'TITLE' => 'Поставка оборудования — комплект документов',
'UF_CRM_DOC_SCAN' => [
['fileData' => ['contract.pdf', base64_encode(file_get_contents('contract.pdf'))]],
['fileData' => ['spec.xlsx', base64_encode(file_get_contents('spec.xlsx'))]],
],
],
]);
$dealId = $result['result'] ?? null;
Частая ошибка: то же самое поле передают одинаково что при создании, что при обновлении, забывая, что при создании сделки поле ещё пустое, а при обновлении в нём могут уже лежать файлы — про это в следующем шаге.
Шаг 5. Не теряем уже прикреплённые файлы при обновлении
Здесь кроется главная засада формата. crm.deal.update не «дописывает» файл в поле, как в интерфейсе, а полностью заменяет содержимое поля тем, что вы прислали. Документация Битрикс24 прямо предупреждает: файлы, чей id не передан в массиве при обновлении многофайлового поля, удаляются из поля. Значит, перед добавлением нового файла нужно сначала прочитать текущее значение поля и включить в запрос идентификаторы уже прикреплённых файлов.
<?php
// Получаем текущее значение поля, чтобы не потерять уже прикреплённые файлы
$deal = CRest::call('crm.deal.get', ['ID' => 4521]);
$existingFiles = $deal['result']['UF_CRM_DOC_SCAN'] ?? [];
// Сохраняем существующие файлы по их ID и добавляем новый
$updatedFiles = [];
foreach ($existingFiles as $file) {
$updatedFiles[] = ['id' => $file['id']]; // так файл остаётся в поле как есть
}
$updatedFiles[] = ['fileData' => ['new-doc.pdf', base64_encode(file_get_contents('new-doc.pdf'))]];
CRest::call('crm.deal.update', [
'ID' => 4521,
'FIELDS' => [
'UF_CRM_DOC_SCAN' => $updatedFiles,
],
]);

Работаем с Диском Битрикс24 для больших файлов
Base64 увеличивает объём файла примерно на треть — исходные 10 МБ превращаются в запросе почти в 13-14 МБ. Ограничения на размер запроса и время его выполнения у портала есть, и упереться в них проще, чем кажется: к росту объёма от base64 добавляется время на передачу по каналу клиента.
Большие видео, архивы с фотографиями объекта или толстые презентации лучше не гонять через файловое поле CRM напрямую: каждая повторная отправка при нестабильном канале — это заново закодированный в base64 файл целиком, а не докачка с того места, где прервалось. Для таких случаев логичнее загрузить файл на Диск Битрикс24, а в сделке оставить ссылку.
Шаг 6. Загружаем файл в папку на Диске
<?php
// ID папки на Диске нужно знать заранее — например, папка проекта или клиента
$folderId = 128;
$fileContent = base64_encode(file_get_contents('presentation.mp4'));
$result = CRest::call('disk.folder.uploadfile', [
'id' => $folderId,
'data' => ['NAME' => 'presentation.mp4'],
'fileContent' => ['presentation.mp4', $fileContent], // здесь без обёртки fileData — другой метод, другой формат
'generateUniqueName' => true, // не перезатираем файл с таким же именем в папке
]);
$diskFile = $result['result'];
$downloadUrl = $diskFile['DOWNLOAD_URL'];
Обратите внимание: у disk.folder.uploadfile параметр fileContent принимает массив [имя, base64] напрямую, без обёртки fileData, в отличие от файловых полей CRM из шагов 2-5. Это разные методы с разным форматом, и перепутать их — самая частая практическая ошибка при первом знакомстве с загрузкой файлов в Битрикс24.
Шаг 7. Привязываем файл с Диска к сделке
Прямая привязка объекта Диска к файловому полю CRM по его идентификатору официально не задокументирована — поля типа «Файл» в CRM хранят собственные внутренние ID файлов, а не ID объектов Диска, это разные хранилища. Рабочий и предсказуемый вариант — сохранить ссылку на файл в текстовое поле сделки или добавить её в комментарий к сделке через crm.timeline.comment.add.
<?php
// Оставляем ссылку на загруженный на Диск файл в ленте сделки
CRest::call('crm.timeline.comment.add', [
'fields' => [
'ENTITY_ID' => 4521,
'ENTITY_TYPE' => 'deal',
'COMMENT' => 'Презентация для клиента загружена на Диск: ' . $downloadUrl,
],
]);
Если файлов немного и они укладываются в разумный размер запроса, проще пропустить Диск и сразу использовать fileData, как в шагах 2-5 — Диск оправдан именно для крупных файлов и там, где нужна версионность или совместное редактирование средствами самого Битрикс24.
Частые ошибки и как их избежать
Ошибка: путают формат fileData для полей CRM и fileContent для методов Диска. Возникает, потому что оба принимают base64 и выглядят похоже, но у одного значение — объект с ключом fileData, у другого — обычный индексный массив. Решение: держать перед глазами, для какого именно метода пишете код, не копировать пример между CRM-полем и Диском не глядя.
Ошибка: отправляют несколько файлов в поле без флага MULTIPLE. Поле создано под один файл, а в запрос кладут массив из нескольких fileData. Метод не возвращает ошибку — просто ничего не сохраняет. Решение: заранее смотреть настройки поля через crm.deal.userfield.get, а не полагаться на то, что «наверное, сработает».
Ошибка: передают base64 GET-запросом. У GET есть практическое ограничение на длину URL около 2048 символов, и уже некрупный файл в base64 в него не влезает — часть содержимого обрезается, файл на портале открывается повреждённым. Решение: файлы — только через POST, без исключений.
Ошибка: обновляют многофайловое поле, забыв про уже прикреплённые файлы. Разработчик добавляет новый документ, а старые из карточки пропадают, потому что не переданы их id. Решение: перед обновлением такого поля всегда читать его текущее значение через crm.deal.get.
Из практики А2
На одном проекте документы (акты, счета) генерировались во внешней системе и сразу прикреплялись к сделке через crm.deal.update — без интерфейса, полностью в фоне. Первая версия интеграции слала в поле только новый файл при каждом обновлении, и через пару недель менеджеры заметили, что в карточках остаётся один последний акт вместо всей цепочки документов по сделке — предыдущие файлы физически никуда не делись, но пропадали из поля, потому что их id не передавались повторно. После добавления шага с чтением текущего значения поля перед записью проблема ушла полностью.
Отдельно на другом проекте с недвижимостью в сделку нужно было прикреплять фотоотчёты с объектов — по 40-60 фотографий за раз, суммарно иногда за сотню мегабайт. Через файловое поле это работало нестабильно на слабом канале клиента: запрос иногда обрывался на середине кодирования и отправки, и вся пачка файлов терялась целиком, включая уже «долетевшие» фотографии. Решением стал именно вариант с Диском: каждая фотография грузится отдельным вызовом disk.folder.uploadfile в папку, привязанную к сделке, а в карточке остаётся не сам файл, а ссылка на папку — так обрыв на одной фотографии не откатывает всю партию.
Итог
После этой инструкции можно прикреплять файлы к сделкам и лидам из внешнего кода: через файловые поля CRM для документов разумного размера и через Диск Битрикс24 — для крупных файлов и папок с вложениями. Если на каком-то шаге застряли — опишите ситуацию в комментарии или напишите нам напрямую.