Клиент просит: «Хотим, чтобы при переходе сделки на этап "Готово к отгрузке" наша складская система сама резервировала товар и писала обратно в CRM номер накладной». Простым вебхуком это не закрыть — нужна двусторонняя логика, свой интерфейс внутри портала, обращения к REST API от имени приложения, а не разового скрипта. Здесь начинается разработка полноценного приложения для Битрикс24. Разработчик, который создаёт приложение для Битрикс24 впервые, упирается в одни и те же вопросы: локальное или облачное, что такое ONAPPINSTALL, откуда берутся access_token и refresh_token и где их хранить. Разберём по шагам — от регистрации в портале до первого рабочего запроса к API.
Статья рассчитана на разработчика, который уже писал что-то с REST API Битрикс24 (хотя бы через входящий вебхук) и готов написать install.php с нуля.
Что понадобится
- Портал Битрикс24 с правами администратора — без них раздел «Разработчикам» просто не появится в меню
- Сервер с публичным HTTPS-адресом для install.php и обработчиков событий (на http Битрикс24 запросы не пришлёт)
- PHP 8.0 и новее с включённым cURL — для запросов к oauth-серверу и REST API (код ниже работает и на 7.4, но держать сегодня публичный обработчик на неподдерживаемой версии не стоит)
- Хранилище для токенов: таблица в БД или как минимум файл, но не переменная сессии — install.php вызывается один раз, сессия к этому моменту уже никого не спасёт
- Тестовый портал Битрикс24 (регистрируется бесплатно за несколько минут) — ставить первую версию приложения сразу клиенту не стоит

Создаём приложение для Битрикс24 с нуля: пошаговая настройка
Шаг 1. Выбираем тип приложения — локальное или облачное
Локальное приложение ставится на один конкретный портал и не публикуется в Маркетплейсе — это то, что нужно для интеграции под конкретного клиента. Облачное (оно же тиражное, «маркетплейс») проходит модерацию Битрикс24 и доступно всем порталам через каталог решений. Механизм авторизации у них общий — OAuth 2.0, событие ONAPPINSTALL, install.php — разница только в том, где приложение регистрируется и кто его ставит.
Для 9 из 10 задач заказной разработки нужно именно локальное приложение. Облачное имеет смысл, только если продукт планируется продавать через Маркетплейс — тогда закладывайте время на модерацию, это отдельный процесс, и он не про код, а про соответствие требованиям Битрикс24 к оформлению карточки решения, описанию прав и поведению интерфейса. Разработчик, который впервые проходит модерацию, обычно тратит на неё больше времени, чем на саму интеграцию — присылают замечания, их правят, отправляют снова.
Частая ошибка на этом шаге: делают локальное приложение, а через полгода клиент просит «а давайте продадим это решение другим компаниям» — и выясняется, что архитектура была рассчитана на один портал с захардкоженным доменом. Если есть шанс, что приложение вырастет за пределы одного клиента, сразу проектируйте хранилище токенов с ключом по порталу, а не как синглтон.

Шаг 2. Регистрируем приложение в разделе «Разработчикам»
Для локального приложения: «Разработчикам» → «Другое» → «Локальное приложение». Указываем название, тип (серверное приложение — то, что нам нужно, а не «виджет» или чисто клиентское на JS), URL, по которому будет открываться приложение, и путь до install.php — это тот адрес, на который Битрикс24 один раз обратится при установке.
После сохранения портал выдаёт client_id и client_secret — это ключи вашего приложения для OAuth 2.0, их нужно сохранить в конфиге на сервере. client_secret никогда не должен уходить в браузер или во фронтенд-код — только в серверных запросах к oauth-серверу.

Шаг 3. Настраиваем install.php и права доступа приложения
В той же карточке приложения указывается набор прав (scope) — какие разделы REST API приложению разрешено вызывать: crm, task, disk, user и так далее. Правило то же, что и с входящими вебхуками: выдавайте только то, что реально используете. Расширить права можно позже, но пользователю портала придётся переустановить приложение и подтвердить новый набор — это не проходит незаметно, так что закладывайте это в план релизов.
install.php на этом шаге — просто пустой файл-заглушка, который отвечает HTTP 200. Логику добавим в следующем шаге, когда разберёмся, что именно туда прилетает.
<?php
// Пока просто подтверждаем, что обработчик существует и отвечает
http_response_code(200);
Частая ошибка: путь до install.php указывают с опечаткой или без HTTPS, и Битрикс24 при установке молча падает с ошибкой на стороне портала, а разработчик пытается понять, что не так в коде, хотя проблема в адресе.
Шаг 4. Обрабатываем событие ONAPPINSTALL
ONAPPINSTALL — системное событие, которое Битрикс24 генерирует сразу после того, как пользователь нажал «Установить» и подтвердил права. Именно на install.php, указанный в карточке приложения, приходит POST-запрос с данными установки.
В теле запроса — набор полей: DOMAIN (адрес портала), AUTH_ID (это и есть access_token), AUTH_EXPIRES (время жизни в секундах), REFRESH_ID (refresh_token), member_id (уникальный идентификатор конкретной установки — не путать с ID пользователя) и APPLICATION_TOKEN, который понадобится нам на шаге 6.
<?php
// install.php — обработчик события ONAPPINSTALL
$domain = $_REQUEST['DOMAIN'] ?? null;
$accessToken = $_REQUEST['AUTH_ID'] ?? null;
$refreshToken = $_REQUEST['REFRESH_ID'] ?? null;
$memberId = $_REQUEST['member_id'] ?? null;
$appToken = $_REQUEST['APPLICATION_TOKEN'] ?? null;
if (!$domain || !$accessToken || !$memberId) {
// Без этих полей сохранять нечего — что-то пошло не так на стороне портала
http_response_code(400);
exit;
}
// member_id уникален для установки — по нему и ключуем запись в БД,
// а не по домену (домен у портала может смениться)
saveAppInstallation($memberId, $domain, $accessToken, $refreshToken, $appToken);
http_response_code(200);
Частая ошибка: сохраняют токены по домену портала как первичному ключу. Домен у Битрикс24-портала иногда меняется (переход с тестового поддомена на кастомный, ребрендинг компании) — а member_id при этом остаётся тем же. Если завязаться на домен, после смены адреса приложение «слепнет» и придётся переустанавливать заново.

Шаг 5. Реализуем обмен и хранение токенов OAuth 2.0
То, что пришло в install.php — AUTH_ID и REFRESH_ID — это уже готовые access_token и refresh_token, полученные без отдельного шага «код авторизации → токен» (в отличие от классического OAuth 2.0 authorization code flow с редиректом пользователя). Битрикс24 сразу выдаёт рабочую пару токенов при установке приложения — это упрощённый вариант, специфичный для встроенных приложений Битрикс24.
Сохранённые токены дальше используются для вызова любого метода REST API:
<?php
// Пример вызова метода REST API с сохранённым access_token
$domain = 'yourportal.bitrix24.ru';
$accessToken = getStoredAccessToken($memberId); // из вашего хранилища
$response = file_get_contents(
"https://{$domain}/rest/crm.deal.list.json?auth={$accessToken}"
);
$deals = json_decode($response, true);
access_token живёт недолго — обычно порядка часа (точное значение приходит в AUTH_EXPIRES, не хардкодьте константу). Дальше нужен шаг 7 — обновление через refresh_token.
Шаг 6. Проверяем APPLICATION_TOKEN на входящих запросах
Как и с исходящими вебхуками, Битрикс24 не подписывает запросы криптографически — теоретически POST на ваш install.php или другой обработчик событий может отправить кто угодно. APPLICATION_TOKEN, полученный при установке, — это тот секрет, по которому легитимные запросы от конкретного портала можно отличить от подделки.
// Сверяем токен приложения перед тем, как доверять данным запроса
$storedAppToken = getStoredAppToken($memberId);
if (!hash_equals($storedAppToken, $_REQUEST['APPLICATION_TOKEN'] ?? '')) {
http_response_code(403);
exit;
}
Используем hash_equals(), а не === — сравнение строк напрямую теоретически уязвимо к timing-атакам, это дешёвая и бесплатная защита.
Шаг 7. Обновляем access_token через refresh_token
Когда access_token истёк, а обращаться к API всё ещё нужно, делаем запрос к серверу авторизации Битрикс24 с параметром grant_type=refresh_token.
<?php
// Обновляем access_token, когда истёк срок его действия
function refreshAccessToken(string $refreshToken, string $clientId, string $clientSecret): array
{
$url = 'https://oauth.bitrix24.tech/oauth/token/'
. '?grant_type=refresh_token'
. '&client_id=' . urlencode($clientId)
. '&client_secret=' . urlencode($clientSecret)
. '&refresh_token=' . urlencode($refreshToken);
$response = file_get_contents($url);
$result = json_decode($response, true);
// Битрикс24 при обновлении выдаёт новую пару токенов —
// старый refresh_token после этого уже не сработает
return $result; // содержит access_token, refresh_token, expires_in
}
Частая ошибка: обновляют access_token, но не перезаписывают refresh_token в хранилище — при следующей попытке обновления сервер отвечает ошибкой авторизации, и приложение выглядит так, будто пользователь его удалил, хотя дело в устаревшем refresh_token.
Шаг 8. Проверяем права и делаем первый рабочий запрос
Если приложению нужен, скажем, доступ к CRM, а в карточке приложения (шаг 3) забыли отметить scope crm, любой запрос к crm.* методам вернёт ошибку доступа — даже если токены валидны. Это не баг авторизации, а именно нехватка прав, и разработчики теряют на этой путанице время чаще, чем кажется.
Проверить, какие права реально выданы установленному приложению, можно методом scope:
<?php
// Проверяем, какие права реально доступны приложению на этом портале
$response = file_get_contents(
"https://{$domain}/rest/scope.json?auth={$accessToken}"
);
$grantedScopes = json_decode($response, true)['result'];
Если нужного раздела в списке нет — правим scope в карточке приложения и просим пользователя переустановить его на портале.
Частые ошибки и как их избежать
Хранят токены в сессии или файле без привязки к порталу. Возникает, когда приложение тестируют на одном портале и переносят логику «как есть» на второй. Решение: с самого начала ключуйте хранилище по member_id, даже если сейчас работаете только с одним клиентом.
Не обрабатывают повторную установку приложения. Если пользователь удалил и заново поставил приложение (или обновил права), ONAPPINSTALL прилетит снова с новыми токенами для того же member_id. Решение: в install.php делайте INSERT ... ON DUPLICATE KEY UPDATE (или аналог), а не только INSERT — иначе вторая установка упадёт с ошибкой уникальности.
Держат client_secret в коде фронтенда или в JS-обработчике внутри приложения. Часто это делают ради скорости — не хочется поднимать отдельный серверный роут. Решение: весь обмен с oauth-сервером, где участвует client_secret, — только на бэкенде, никогда не в браузере пользователя.
Не логируют факт обращения к install.php при ошибке установки. Когда установка падает у клиента, а не на тестовом портале, разработчик остаётся без диагностики. Решение: логируйте сам факт запроса и состав пришедших полей — домен, member_id, список ключей, — но не их значения.
Целиком писать $_REQUEST в лог нельзя: там лежат access_token, refresh_token и APPLICATION_TOKEN, то есть готовый доступ к порталу клиента. Логи попадают в бэкапы, в системы сбора ошибок и в чужие руки при компрометации сервера, а токен из лога работает ровно так же, как «настоящий». Если для отладки нужны значения — маскируйте: первые четыре символа и длина дают достаточно, чтобы отличить «токен пришёл пустым» от «токен пришёл не тот».
Из практики А2
На одном из проектов приложение изначально хранило токены в таблице с уникальным ключом по домену портала — работало полгода без нареканий, пока клиент не сменил юридическое лицо и не переехал на новый поддомен bitrix24.ru. member_id остался прежним, а домен — нет, и приложение перестало находить свою запись в базе. Снаружи это выглядело как «приложение сломалось само по себе», хотя реальная причина — неправильный ключ хранения, который мы обсуждали в шаге 4 выше.
Отдельно всплыла история с правами доступа: заказчик на старте просил минимум — только чтение сделок. Через два месяца понадобилось создавать задачи по сделкам, и оказалось, что просто добавить scope task в карточку приложения недостаточно — все пользователи портала, у кого приложение уже стояло, увидели запрос на переустановку с новыми правами при следующем открытии. Часть менеджеров этот экран проигнорировала, и приложение у них молча работало в режиме «только чтение» ещё неделю, пока не разобрались, почему создание задач срабатывает не у всех. С тех пор при добавлении scope сразу предупреждаем клиента: рассылка по пользователям о необходимости подтвердить новые права — часть релиза, а не мелочь.
Ещё одно наблюдение, которое пригодится тем, кто пишет install.php впервые: не полагайтесь на то, что событие ONAPPINSTALL придёт ровно один раз за всю жизнь установки. На практике оно прилетает повторно при обновлении версии приложения через карточку в портале, а иногда — при технических сбоях на стороне Битрикс24, когда портал повторяет доставку. Обработчик, который падает при повторном вызове (например, из-за нарушения уникальности ключа в БД), выглядит рабочим на демо и ломается через месяц на проде без единой строчки в логах, если логирование не настроено заранее.
Итог
После этой инструкции у вас есть рабочая связка: зарегистрированное в Битрикс24 приложение, install.php, который принимает ONAPPINSTALL и сохраняет токены с привязкой к порталу, проверка application_token на входящих запросах и логика обновления access_token через refresh_token. Этого достаточно, чтобы начать вызывать методы REST API от имени приложения и наращивать бизнес-логику поверх — например, обновление сделок через REST API или обработку событий CRM. Если на каком-то шаге застряли — опишите ситуацию в комментарии или напишите нам напрямую.