Как отправить форму из Bitrix в CRM Битрикс24 через init.php
Когда к нам приходят с задачей «клиент хочет, чтобы заявки с сайта сразу попадали в CRM», первое, что мы рассматриваем — это обработчик события OnAfterFormResultSubmit в init.php. Почему? Потому что это самое быстрое решение без установки дополнительных модулей и без сложных интеграционных слоёв. В нашей практике такой подход работает в 80% проектов: форма обратной связи, заказ звонка, заявка на расчёт — всё это уходит в Битрикс24 напрямую. Главное преимущество, гибкость: вы сами решаете, какие поля формы маппить на поля лида, добавлять ли UF-поля, прикреплять ли контакт. И всё это пишется в одном файле, без лишних сущностей.
<?php
// init.php
use Bitrix\Main\EventManager;
use Bitrix\Main\Loader;
EventManager::getInstance()->addEventHandler('form', 'OnAfterFormResultSubmit', function ($formId, $resultId, $arValues) {
// Убедитесь, что это нужная форма
if ($formId !== 1) return; // замените на ID вашей формы
// Параметры вебхука (замените на свои)
$webhookUrl = 'https://ваш-портал.bitrix24.ru/rest/1/ВАШ_ТОКЕН/';
// Формируем поля лида
$fields = [
'TITLE' => 'Заявка с сайта: ' . date('d.m.Y H:i'),
'NAME' => htmlspecialcharsbx($arValues['form_text_1'] ?? ''), // поле "Имя"
'PHONE' => [['VALUE' => htmlspecialcharsbx($arValues['form_text_2'] ?? ''), 'VALUE_TYPE' => 'WORK']],
'SOURCE_ID' => 'WEB',
'ASSIGNED_BY_ID' => 1, // ID ответственного
];
// Если есть комментарий
if (!empty($arValues['form_textarea_3'])) {
$fields['COMMENTS'] = htmlspecialcharsbx($arValues['form_textarea_3']);
}
// Отправляем запрос
$httpClient = new \Bitrix\Main\Web\HttpClient();
$response = $httpClient->post($webhookUrl . 'crm.lead.add', [
'fields' => $fields,
'params' => ['REGISTER_SONET_EVENT' => 'Y'],
]);
// Логируем ответ (опционально)
\Bitrix\Main\Diag\Debug::dumpToFile(['response' => $response], '', '__leads.log');
});Разберём, что здесь ключевое. В основе, вызов crm.lead.add через REST-запрос. Мы подключаем HttpClient из ядра Bitrix, это надёжнее, чем curl вручную, и не требует дополнительных библиотек. В наших проектах мы часто меняем только маппинг полей: например, если форма собирает адрес или бюджет, добавляем UF_CRM_.... Для передачи кастомных полей достаточно указать их символьный код как ключ массива $fields. Если нужно привязать лид к существующему контакту, добавляем CONTACT_ID в поля. У нас был случай, когда клиент требовал передавать UTM-метки, мы просто дописали условие на наличие $_GET['utm_source'] и добавили UTM_SOURCE в поля лида. Главное, не забыть экранировать данные через htmlspecialcharsbx, чтобы избежать XSS в CRM.
Если вебхук неактивен или истёк, запрос вернёт ошибку.Мы сталкивались с этим, когда на тестовом портале токен «протухал» после смены пароля администратора. Проверяйте токен перед запуском и обязательно логируйте ответы, это сэкономит часы дебага. Добавьте Debug::dumpToFile, как в примере, и смотрите лог после первой отправки формы.
Интеграция сайта Bitrix с Битрикс24: ключевые точки входа
Реальная история: четыре года назад на проекте с интернет-магазином автозапчастей мы вручную прокладывали каждую форму через $_POST в init.php. Клиент добавил корзину — пришлось писать второй обработчик. Потом — регистрацию дилеров. Через полгода код превратился в спагетти, где одно событие конфликтовало с другим. Сейчас мы начинаем любой интеграционный проект с карты точек входа: на какие события сайта вообще можно «повесить» отправку в CRM. Их три, формы, оформление заказа и регистрация пользователя. И у каждого, свой момент срабатывания, свой состав данных и свой типичный кейс. Если не разобраться в этой матрице на старте, вы либо продублируете лиды, либо потеряете часть конверсий.
| Событие / компонент | Когда срабатывает | Какие данные | Типичный кейс |
|---|---|---|---|
OnAfterFormResultSubmit |
После сохранения веб-формы | Поля формы, ID результата | Заявка с контактной страницы |
OnSaleOrderSaved |
После оформления заказа | Состав заказа, сумма, контакт | Лиды из корзины в CRM |
OnAfterUserRegister |
После регистрации пользователя | Email, телефон, имя | Автоматическое создание контакта |
Разберём каждый вариант детальнее. OnAfterFormResultSubmit, самый частый выбор в наших проектах. Событие стреляет сразу после записи результата веб-формы в БД. Ключевой момент: в $arFields приходит массив RESULT_ID и FORM_ID, но сами значения полей приходится доставать через CFormResult::GetDataByID(). Мы используем его для простых заявок, «обратный звонок», «запись на сервис», «вопрос директору». OnSaleOrderSaved, событие модуля интернет-магазина. Срабатывает при создании или обновлении заказа. В $order передаётся объект Bitrix\Sale\Order, откуда извлекаем состав корзины, итоговую сумму и свойства с контактами. Подходит для отправки лидов с коммерческой информацией, когда в CRM нужно видеть не просто «заявка», а «заказ на 15 200 ₽, три позиции». OnAfterUserRegister, событие модуля главного модуля. Срабатывает после успешной регистрации. Данные, $arFields с EMAIL, PHONE, NAME. Мы применяем его, когда клиент хочет автоматически создавать контакт в CRM при регистрации на сайте, например, для личного кабинета оптовика.
Какой выбрать под задачу? Если у вас один канал лидов, форма обратной связи, берите OnAfterFormResultSubmit. Если интернет-магазин, OnSaleOrderSaved даёт больше контекста для сделки. Если B2B-портал с регистрацией, OnAfterUserRegister. Комбинировать их тоже можно, но важно не создать дубли: на каждое событие, один лид в CRM.
Когда использовать REST API, а когда, встроенные методы
В наших проектах выбор между REST API Битрикс24 и встроенными обработчиками init.php определяется простым правилом: если сайт и CRM находятся в одной экосистеме Bitrix (один аккаунт, одна лицензия), используем встроенные методы через CCrmLead::Add() или CRest::call() напрямую. REST API нужен, когда CRM стоит на отдельном домене или облаке Битрикс24, а сайт, на другой коробке. В этом случае все запросы идут через HTTP с авторизацией по вебхуку. Мы предпочитаем встроенные методы для простых интеграций, они быстрее, не зависят от сети и не требуют обработки токенов. REST API, для сложных сценариев с кастомными полями, файлами или когда нужно синхронизировать сделки в обе стороны.
init.php обработчик формы: пошаговая настройка
Самый частый запрос на эту тему — «сделайте, чтобы заявка с контактной формы улетала в CRM». И если для простых форм можно обойтись готовыми решениями, то когда нужно передать кастомное поле, прикрепить файл или провалидировать данные перед отправкой — без собственного обработчика в init.php не обойтись. У нас в практике это стандартный этап интеграции: сначала настраиваем вебхук, затем пишем функцию-обработчик на событие OnAfterFormResultSubmit, и только потом тестируем. Ниже, пошаговая инструкция для разработчика-джуниора, чтобы вы не гадали, что куда писать.
- Создайте вебхук в Битрикс24 (исходящий, REST API). Зайдите в «Разработчикам» → «Другое» → «Вебхуки» → «Добавить вебхук». Выберите тип «Исходящий», укажите права, как минимум
crm.lead.addиcrm.lead.update. Скопируйте URL вебхука, он понадобится в коде обработчика. - В
init.phpнапишите функцию-обработчик и зарегистрируйте её на событиеOnAfterFormResultSubmit. Само событие срабатывает после сохранения результата веб-формы, но до редиректа, идеальный момент, чтобы отправить данные в CRM. Регистрируем черезEventManager, это нативный D7-API. Внутри функции получаем$WEB_FORM_IDи$RESULT_ID, загружаем ответы формы, формируем массив для лида и отправляем черезCRest::call.
use Bitrix\Main\EventManager;
use Bitrix\Main\Loader;
use Bitrix\Main\Web\HttpClient;
use Bitrix\Main\Web\Json;
// Регистрируем обработчик
EventManager::getInstance()->addEventHandler(
'form',
'OnAfterFormResultSubmit',
'handleFormToCrm'
);
function handleFormToCrm($WEB_FORM_ID, $RESULT_ID, $arParams)
{
// ID вашей формы (замените на свой)
$targetFormId = 3;
if ((int)$WEB_FORM_ID !== $targetFormId) {
return;
}
if (!Loader::includeModule('form')) {
return;
}
// Загружаем ответы формы
$answers = [];
$rsResult = \CFormResult::GetByID($RESULT_ID);
if ($arResult = $rsResult->Fetch()) {
$rsAnswers = \CFormResult::GetDataByID($RESULT_ID);
while ($arAnswer = $rsAnswers->Fetch()) {
$fieldName = $arAnswer['FIELD_SID'] ?? 'field_' . $arAnswer['ANSWER_ID'];
$answers[$fieldName] = $arAnswer['USER_TEXT'] ?? $arAnswer['USER_DATE'];
}
}
// Формируем данные для лида
$leadData = [
'TITLE' => 'Заявка с сайта: ' . ($answers['NAME'] ?? 'Без имени'),
'NAME' => $answers['NAME'] ?? '',
'PHONE' => [['VALUE' => $answers['PHONE'] ?? '', 'VALUE_TYPE' => 'WORK']],
'EMAIL' => [['VALUE' => $answers['EMAIL'] ?? '', 'VALUE_TYPE' => 'WORK']],
'SOURCE_ID' => 'WEB',
// Кастомное поле — замените UF_CRM_SOURCE на ваше
'UF_CRM_SOURCE' => 'site_form',
];
// URL вебхука из Битрикс24 (замените на свой)
$webhookUrl = 'https://your-domain.bitrix24.ru/rest/1/your-webhook-secret/';
try {
$httpClient = new HttpClient();
$httpClient->setHeader('Content-Type', 'application/json');
$response = $httpClient->post(
$webhookUrl . 'crm.lead.add',
Json::encode(['fields' => $leadData])
);
$result = Json::decode($response);
if (isset($result['error'])) {
AddMessage2Log('Ошибка CRM: ' . $result['error_description'], 'CRM');
} else {
AddMessage2Log('Лид создан: ID ' . $result['result'], 'CRM');
}
} catch (\Exception $e) {
AddMessage2Log('Исключение: ' . $e->getMessage(), 'CRM');
}
}- Проверьте логи и убедитесь, что лид создаётся. Заполните форму на сайте, затем откройте «Администрирование» → «Журнал событий» → «CRM». Если всё настроено верно, увидите запись «Лид создан: ID 123». Если нет, ищите сообщение об ошибке там же.
Не используйте die() или exit() в обработчике, это прервёт выполнение страницы. Форма может не отправиться, а пользователь увидит пустой экран. Если нужно остановить выполнение при ошибке, выбрасывайте исключение или возвращайте false, событие поддерживает отмену.
Отправка лидов из Bitrix в Bitrix24: поля и кастомные поля
Когда мы уже настроили отправку формы в init.php, следующий вопрос почти всегда звучит одинаково: «А можно ли передать дополнительные данные — не только имя и телефон, но и, скажем, регион, предпочтительное время звонка или ссылку на страницу заказа?». Ответ — да, но с нюансом: стандартных полей лида (TITLE, NAME, PHONE, EMAIL) хватает лишь для простейших сценариев. В реальных проектах, где менеджеры в CRM привыкли видеть структурированные данные, мы используем пользовательские поля (UF-поля) Битрикс24. У каждого UF-поля есть системный код вида UF_CRM_XXXXXXXXXX, который нужно знать, чтобы передать в него значение из формы. Самый надёжный способ узнать код, открыть редактор полей в CRM (Настройки → Поля лидов) и посмотреть в URL или в исходном коде страницы. Либо, что мы делаем чаще, запросить через REST API, об этом ниже.
// Пример массива FIELDS для отправки лида с кастомными полями
$fields = [
'TITLE' => 'Заявка с сайта: ' . $name,
'NAME' => $name,
'PHONE' => [['VALUE' => $phone, 'VALUE_TYPE' => 'WORK']],
'EMAIL' => [['VALUE' => $email, 'VALUE_TYPE' => 'WORK']],
'SOURCE_ID' => 'WEB',
'UF_CRM_1682345678' => $region, // строка — регион клиента
'UF_CRM_1682345679' => $preferred_time, // строка — время звонка
'UF_CRM_1682345680' => [ // список — интересы (множественное)
'Товар A',
'Товар B'
],
'UF_CRM_1682345681' => [ // файл — скриншот ошибки
'fileData' => [
'screenshot.png',
base64_encode(file_get_contents($_FILES['screenshot']['tmp_name']))
]
],
];Ключевой момент здесь, не угадывать коды полей, а получить их программно. В наших проектах мы используем REST-метод crm.lead.userfield.list, он возвращает массив всех пользовательских полей лида с их системными именами, типами и списками возможных значений. Вызов выглядит так: CRest::call('crm.lead.userfield.list'). В ответе ищем поле по его заголовку (поле listLabel) и забираем FIELD_NAME. Особое внимание, множественным полям типа «список». Если поле допускает множественный выбор (multiple = Y), передавать нужно массив строк, как в примере выше с интересами. Одиночный список, просто строка. Для файловых полей (FIELD_TYPE = file) формируем массив с ключом fileData, где первый элемент, имя файла, второй, содержимое в base64. Без этого Битрикс24 не примет вложение.
| Поле формы | Код поля лида | Тип |
|---|---|---|
| Имя | NAME |
строка |
| Телефон | PHONE |
массив с VALUE |
| Регион | UF_CRM_1682345678 |
строка |
| Интересы | UF_CRM_1682345680 |
список (множественный) |
| Скриншот | UF_CRM_1682345681 |
файл |
Кастомные поля Битрикс24 API: как передать из формы
Стандартные поля лида — Имя, Телефон, Email, Комментарий — закрывают базовые сценарии, но ровно до того момента, как клиент просит добавить «Номер договора», «Желаемую дату доставки» или «Источник: рекламный кабинет VK». Встроенной опции нет, и здесь в дело вступают пользовательские поля (UF, User Fields). У нас в практике это одна из самых частых доработок: мы регулярно создаём UF-поля через REST API, а затем передаём их значения из формы на сайте. Главное, знать, как правильно назвать поле и какой тип данных указать, иначе Битрикс24 просто проигнорирует переданный параметр.
// Создаём кастомное поле "Номер договора" для лидов
$ufField = [
'ENTITY_ID' => 'CRM_LEAD',
'FIELD_NAME' => 'UF_CRM_CONTRACT_NUMBER',
'USER_TYPE_ID' => 'string',
'EDIT_FORM_LABEL' => ['ru' => 'Номер договора'],
'LIST_COLUMN_LABEL' => ['ru' => 'Номер договора'],
'SETTINGS' => ['SIZE' => 50, 'MAX_LENGTH' => 100],
];
$result = CRest::call('crm.lead.userfield.add', ['fields' => $ufField]);
// Проверка результата
if ($result['result']) {
echo "UF-поле создано, ID: " . $result['result'];
} else {
echo "Ошибка: " . $result['error_description'];
}Ключевых параметров в этом запросе четыре. ENTITY_ID, привязка к сущности: CRM_LEAD для лидов, CRM_DEAL для сделок, CRM_CONTACT для контактов. FIELD_NAME, системное имя, обязательно начинается с UF_CRM_. USER_TYPE_ID определяет тип данных: string для строки, integer для числа, date для даты, file для файла, enumeration для списка. Если нужен список, дополнительно передаём LIST с массивом значений, а в SETTINGS для строки указываем SIZE и MAX_LENGTH. Для даты SETTINGS обычно оставляем пустым. В наших проектах мы чаще всего используем строку для текстовых меток и список для фиксированных вариантов вроде «Источник: контекст / таргет / органика».
Грабли:Если назвать поле UF_CONTRACT_NUMBER без префикса UF_CRM_, Битрикс24 создаст его, но не привяжет к CRM-сущности. Поле появится в списке пользовательских полей системы, но в карточке лида или сделки вы его не увидите. Итог, передача значения пройдёт без ошибки, но данные потеряются. Префикс UF_CRM_, обязательное условие для всех кастомных полей CRM.
Создание сделки вместо лида: когда и как
Распространённая ошибка: разработчик отправляет коммерческую заявку — расчёт стоимости, заказ товара, запрос на монтаж — как лид. В CRM это превращается в дополнительный клик менеджера: открыть лид, конвертировать в сделку, проставить сумму, перевести стадию. А ведь форма уже содержит все данные для сделки, сумму, товарные позиции, адрес. В наших проектах с интернет-магазинами строительных материалов мы давно перешли на прямое создание сделки из формы, минуя стадию лида. Разница принципиальная: у лида нет стадий воронки продаж и суммы, а у сделки они обязательны. Если форма явно ведёт к покупке, создавайте сделку сразу, это уберёт лишний шаг у менеджера и ускорит обработку заявки.
use Bitrix\Crm\DealTable;
use Bitrix\Crm\FieldMultiTable;
use Bitrix\Main\Loader;
Loader::includeModule('crm');
$dealFields = [
'TITLE' => 'Заказ с сайта: ' . htmlspecialcharsbx($_POST['product_name']),
'STAGE_ID' => 'DEAL_STAGE_NEW', // первая стадия воронки продаж
'CONTACT_ID' => $contactId, // ID существующего контакта
'OPPORTUNITY' => (float)$_POST['price'],
'CURRENCY_ID' => 'RUB',
];
$result = \CCrmDeal::Add($dealFields, true, ['REGISTER_SONET_EVENT' => 'N']);
if (!$result->isSuccess()) {
// логируем ошибку, как мы обсуждали в секции про обработку ошибок
}Ключевой момент в коде, привязка сделки к контакту. Если контакт уже существует (например, покупатель ранее оформлял заказ), передаём его CONTACT_ID, который получаем через поиск по email или телефону. Если контакта нет, создаём его через CCrmContact::Add прямо перед сделкой, передав имя, телефон и email из формы. Стадию воронки указываем через STAGE_ID. В Битрикс24 есть предустановленные стадии: DEAL_STAGE_NEW, «Новая», DEAL_STAGE_PREPAYMENT_INVOICE, «Выставлен счёт». Если у клиента своя воронка, в админке CRM можно посмотреть символьный код нужной стадии в настройках воронки продаж. В наших проектах мы обычно выносим выбор стадии в отдельную константу в init.php, чтобы менеджер мог поменять её без правки кода.
Грабли: если не указать STAGE_ID, сделка создастся в первой стадии воронки по умолчанию. У клиента это может оказаться стадия «Успешно реализовано» или «Отказ», что полностью сломает воронку продаж. Всегда явно задавайте стадию, даже если она совпадает с первой, это защита от случайных изменений в настройках CRM.
Обработка ошибок и логирование при отправке в CRM
Из практики: на проекте с интернет-магазином строительных материалов мы настроили отправку заявок в Битрикс24 через init.php. Всё работало полгода, пока владелец вебхука не сменил пароль — и лиды перестали создаваться. Узнали мы об этом только через две недели, когда менеджеры позвонили и сказали, что заявок нет. С тех пор у нас железное правило: любая отправка в CRM должна быть обёрнута в try-catch с обязательным логированием. Типичные ошибки — неверный вебхук (слетел ключ), отсутствие прав у пользователя вебхука (админ поменял роли), дубликаты (повторный POST с теми же данными) и превышение лимитов запросов. Без обработчика вы просто не узнаете, что CRM молчит.
use Bitrix\Main\Application;
use Bitrix\Main\Error;
use Bitrix\Main\EventLog\EventLog;
function sendLeadToCrm(array $fields): bool
{
$webhookUrl = 'https://your-domain.bitrix24.ru/rest/1/TOKEN/crm.lead.add.json'; // замените на ваш вебхук
try {
$httpClient = new \Bitrix\Main\Web\HttpClient();
$httpClient->setHeader('Content-Type', 'application/json', true);
$response = $httpClient->post($webhookUrl, json_encode(['fields' => $fields]));
if (!$httpClient->getStatus() === 200) {
throw new \RuntimeException('HTTP ' . $httpClient->getStatus() . ': ' . $httpClient->getError());
}
$result = json_decode($response, true);
if (isset($result['error'])) {
throw new \RuntimeException($result['error_description'] ?? $result['error']);
}
// Логируем успех
EventLog::log(['SEVERITY' => 'INFO', 'AUDIT_TYPE_ID' => 'CRM_LEAD_SEND', 'ITEM_ID' => $result['result'], 'DESCRIPTION' => 'Лид создан']);
return true;
} catch (\Throwable $e) {
// Логируем ошибку
Application::getInstance()->getExceptionHandler()->writeToLog($e);
EventLog::log(['SEVERITY' => 'ERROR', 'AUDIT_TYPE_ID' => 'CRM_LEAD_ERROR', 'DESCRIPTION' => $e->getMessage()]);
return false;
}
}- 401 Unauthorized, неверный или отозванный вебхук. Решение: проверьте токен в URL вебхука, пересоздайте входящий вебхук в Битрикс24.
- 403 Forbidden, у пользователя вебхука нет прав на
crm.lead.add. Решение: назначьте права «CRM, все» для пользователя вебхука. - 400 Bad Request, невалидный JSON или обязательное поле пусто. Решение: валидируйте
$fieldsперед отправкой, проверьте соответствие типов полей. - Дубликат (дубль), API вернул
DUPLICATE_CONTROLилиDUPLICATE. Решение: включите контроль дубликатов в настройках вебхука или передавайтеREGISTER_SONET_EVENT= false. - Превышение лимита запросов,
QUERY_LIMIT_EXCEEDED(2 запроса/сек на аккаунт). Решение: введите задержкуusleep(500000)между вызовами или используйте очередь.
Частые вопросы
Можно ли передать в Битрикс24 файл из формы сайта (например, фото или PDF) через init.php?
Да, можно, но не через стандартный метод crm.lead.add. Файл нужно предварительно загрузить через rest/disk.folder.uploadfile в папку Битрикс24, а затем передать ID файла в поле UF_CRM_... типа «Файл».
Что делать, если после отправки формы лид создаётся, но кастомное поле UF_CRM_... остаётся пустым?
Проверьте, что в массиве $arFields вы передаёте значение с правильным ключом (например, 'UF_CRM_1234567890'), а не 'UF_CRM_...'. Убедитесь, что поле существует в настройках CRM и имеет тип «Строка» или «Список», а не «Привязка к разделам».
Чем отличается создание лида через CCrmLead::Add от вызова rest/crm.lead.add в init.php?
CCrmLead::Add — это внутренний метод Битрикс, работающий без REST и токенов, подходит для обработки в init.php. REST-метод требует вебхука и используется для внешних систем. В init.php используйте CCrmLead::Add, он быстрее и не зависит от настроек REST.
А если у меня на сайте несколько форм, и для каждой нужно своё поле в сделке — как не запутаться?
Добавьте в обработчик проверку по ID формы ($_POST['WEB_FORM_ID']) или по имени поля-идентификатора (например, скрытое поле 'FORM_TYPE'). В зависимости от значения формируйте разный массив полей для CCrmDeal::Add.
Можно ли при создании сделки автоматически прикрепить её к уже существующему контакту по email, а не создавать новый?
Да, перед созданием сделки выполните CCrmContact::GetList с фильтром по email. Если контакт найден, передайте его ID в поле 'CONTACT_ID' массива сделки. Если нет — сначала создайте контакт через CCrmContact::Add.
Что делать, если при отправке формы сайт выдаёт ошибку 500, а в логах пусто?
Включите логирование в самом начале init.php: AddMessage2Log('Start form handler', 'crm_forms', 1). Затем проверьте /bitrix/modules/main/classes/general/option.php?show_log=Y. Если логов нет — убедитесь, что файл init.php подключается (проверьте через phpinfo в начале файла).