Интеграция 1С-Битрикс с Telegram-ботом

Создаём Telegram-бота для уведомлений о заказах, обратной связи и поддержки клиентов. Пошаговая настройка webhook, обработка команд и интеграция с инфоблоками.

Дмитрий Мещеряков
Дмитрий Мещеряков
📅 26 августа 2026 г.📖 9 мин чтения

Уведомления о заказах в Telegram — одна из тех доработок, которые заказчик замечает сразу и хвалит. Делается она за вечер, и ровно поэтому в ней часто остаются две дыры: публичный эндпоинт вебхука без проверки подлинности и выдача данных о заказе любому, кто угадал его номер.

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

Зачем Telegram-бот для интернет-магазина

Telegram — один из самых популярных мессенджеров в России. Интеграция с ботом даёт:

  • Мгновенные уведомления — менеджер узнаёт о заказе за секунды, а не когда откроет почту
  • Двусторонняя связь — клиент пишет в бот, менеджер отвечает из админки
  • Автоматизация — статус заказа, трек-номер, напоминания без участия человека
💡 Совет

Что получите в итоге: Работающего бота, который отправляет уведомления о новых заказах в Telegram-группу и позволяет клиентам узнать статус заказа по номеру.

Шаг 1: Создание бота в BotFather

Откройте @BotFather в Telegram и создайте бота:

bash
/newbot
# Введите имя: МойМагазин Уведомления
# Введите username: myshop_notify_bot
# BotFather вернёт токен:
# 7234567890:AAHdqTcvCH1vGWJxfSeofSAs0K5PALDsaw

Сохраните токен — он понадобится для API-запросов.

⚠️ Важно

Токен бота — полноценный доступ к боту. Кто им владеет, тот может читать все входящие сообщения и писать от имени бота всем, кто с ним общался. Держите его в конфигурации вне репозитория (Configuration::getValue() или переменная окружения), а не в константе init.php, которая уедет в git. Если токен всё-таки засветился — отзывайте через /revoke у BotFather, смена пароля тут не поможет.

Шаг 2: Модуль интеграции в Битрикс

Создадим простой модуль для отправки сообщений. Структура:

bash
local/
├── php_interface/
│   └── init.php
└── classes/
    └── Telegram/
        └── Bot.php

Класс для работы с Telegram API

php
<?php
// local/classes/Telegram/Bot.php
namespace Local\Telegram;

class Bot
{
    private string $token;
    private string $apiUrl = 'https://api.telegram.org/bot';

    public function __construct(string $token)
    {
        $this->token = $token;
        $this->apiUrl .= $token . '/';
    }

    /**
     * Отправка сообщения в чат
     */
    public function sendMessage(
        int|string $chatId,
        string $text,
        array $options = []
    ): array {
        $params = array_merge([
            'chat_id' => $chatId,
            'text' => $text,
            'parse_mode' => 'HTML',
        ], $options);
        return $this->request('sendMessage', $params);
    }

    /**
     * Отправка сообщения с inline-кнопками
     */
    public function sendMessageWithButtons(
        int|string $chatId,
        string $text,
        array $buttons
    ): array {
        return $this->sendMessage($chatId, $text, [
            'reply_markup' => json_encode([
                'inline_keyboard' => $buttons
            ])
        ]);
    }

    /**
     * Базовый запрос к API
     */
    private function request(string $method, array $params = []): array
    {
        $ch = curl_init($this->apiUrl . $method);
        curl_setopt_array($ch, [
            CURLOPT_POST => true,
            CURLOPT_POSTFIELDS => http_build_query($params),
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT => 10,
            CURLOPT_SSL_VERIFYPEER => true,
        ]);
        $response = curl_exec($ch);
        $error = curl_error($ch);
        curl_close($ch);

        if ($error) {
            throw new \Exception("Telegram API error: $error");
        }

        return json_decode($response, true);
    }
}

Подключение в init.php

php
<?php
// local/php_interface/init.php
use Bitrix\Main\Loader;
use Bitrix\Main\EventManager;
// Автозагрузка классов
Loader::registerAutoLoadClasses(null, [
    'Local\\Telegram\\Bot' => '/local/classes/Telegram/Bot.php',
]);
// Токен бота (лучше хранить в .settings.php)
define('TELEGRAM_BOT_TOKEN', '7234567890:AAHdqTcvCH1vGWJxfSeofSAs0K5PALDsaw');
define('TELEGRAM_CHAT_ID', '-1001234567890'); // ID группы для уведомлений

Шаг 3: Уведомления о новых заказах

Подписываемся на событие создания заказа:

php
<?php
// local/php_interface/init.php (продолжение)
EventManager::getInstance()->addEventHandler(
    'sale',
    'OnSaleOrderSaved',
    ['Local\\Events\\OrderHandler', 'onOrderSaved']
);
php
<?php
// local/classes/Events/OrderHandler.php
namespace Local\Events;
use Bitrix\Main\Event;
use Bitrix\Sale\Order;
use Local\Telegram\Bot;
class OrderHandler
{
    public static function onOrderSaved(Event $event): void
    {
        /** @var Order $order */
        $order = $event->getParameter('ENTITY');
        // Только для новых заказов
        if (!$event->getParameter('IS_NEW')) {
            return;
        }
        self::sendNewOrderNotification($order);
    }
    private static function sendNewOrderNotification(Order $order): void
    {
        $bot = new Bot(TELEGRAM_BOT_TOKEN);
        // Собираем данные заказа
        $orderId = $order->getId();
        $price = number_format($order->getPrice(), 0, ',', ' ');
        $currency = $order->getCurrency();
        // Данные покупателя
        $props = $order->getPropertyCollection();
        $phone = $props->getPhone()?->getValue() ?? '—';
        $name = $props->getPayerName()?->getValue() ?? '—';
        // Товары
        $basket = $order->getBasket();
        $items = [];
        foreach ($basket as $item) {
            $items[] = "• {$item->getField('NAME')} × {$item->getQuantity()}";
        }
        $itemsList = implode("\n", array_slice($items, 0, 5));
        if (count($items) > 5) {
            $itemsList .= "\n... и ещё " . (count($items) - 5) . " поз.";
        }
        // Формируем сообщение
        $message = "🛒 <b>Новый заказ #{$orderId}</b>\n\n"
            . "💰 Сумма: <b>{$price} {$currency}</b>\n"
            . "👤 {$name}\n"
            . "📱 {$phone}\n\n"
            . "📦 Состав:\n{$itemsList}";
        // Кнопка для перехода в админку
        $buttons = [[
            [
                'text' => '📋 Открыть заказ',
                'url' => $_SERVER['HTTP_HOST'] 
                    . "/bitrix/admin/sale_order_view.php?ID={$orderId}"
            ]
        ]];
        try {
            $bot->sendMessageWithButtons(
                TELEGRAM_CHAT_ID,
                $message,
                $buttons
            );
        } catch (\Exception $e) {
            // Логируем ошибку, но не ломаем оформление заказа
            \Bitrix\Main\Diag\Debug::writeToFile(
                $e->getMessage(),
                'Telegram notification error'
            );
        }
    }
}
⚠️ Важно

Важно: Никогда не прерывайте оформление заказа из-за ошибки отправки в Telegram. Оборачивайте отправку в try-catch и логируйте ошибки.

Шаг 4: Webhook для входящих сообщений

Чтобы бот мог отвечать на сообщения, настроим webhook:

php
<?php
// local/tools/telegram_webhook.php
use Bitrix\Main\Loader;
use Local\Telegram\Bot;
require $_SERVER['DOCUMENT_ROOT'] . '/bitrix/modules/main/include/prolog_before.php';

Loader::includeModule('sale');

// Получаем данные от Telegram
$input = file_get_contents('php://input');
$update = json_decode($input, true);
if (!$update || !isset($update['message'])) {
    exit;
}
$message = $update['message'];
$chatId = $message['chat']['id'];
$text = $message['text'] ?? '';
$bot = new Bot(TELEGRAM_BOT_TOKEN);

// Обработка команд
if (str_starts_with($text, '/start')) {
    $bot->sendMessage($chatId, 
        "👋 Привет! Я бот магазина.\n\n"
        . "Отправьте номер заказа, чтобы узнать его статус.\n"
        . "Например: <code>12345</code>"
    );
    exit;
}
// Проверка статуса заказа по номеру
if (preg_match('/^\d+$/', trim($text))) {
    $orderId = (int) trim($text);
    $order = \Bitrix\Sale\Order::load($orderId);

    // Проверяем не только существование заказа, но и право на него:
    // без этого любой желающий перебором номеров выгрузит статусы
    // и трек-номера всех заказов магазина
    if (!$order || !isOrderOwnedByChat($order, $chatId)) {
        $bot->sendMessage($chatId, "Заказ #{$orderId} не найден");
        exit;
    }
    $status = $order->getField('STATUS_ID');
    $statusName = \Bitrix\Sale\Internals\StatusLangTable::getList([
        'filter' => ['STATUS_ID' => $status, 'LID' => 'ru'],
        'select' => ['NAME']
    ])->fetch()['NAME'] ?? $status;
    $shipments = $order->getShipmentCollection();
    $trackNumber = '';
    foreach ($shipments as $shipment) {
        if (!$shipment->isSystem()) {
            $trackNumber = $shipment->getField('TRACKING_NUMBER') ?: '';
            break;
        }
    }
    $response = "📦 <b>Заказ #{$orderId}</b>\n\n"
        . "Статус: {$statusName}\n";
    if ($trackNumber) {
        $response .= "Трек-номер: <code>{$trackNumber}</code>";
    }
    $bot->sendMessage($chatId, $response);
    exit;
}
// Неизвестная команда
$bot->sendMessage($chatId, 
    "🤔 Не понял. Отправьте номер заказа цифрами."
);

Регистрация webhook

bash
curl -X POST "https://api.telegram.org/bot<TOKEN>/setWebhook" \
  -d "url=https://your-site.ru/local/tools/telegram_webhook.php" \
  -d "secret_token=$(openssl rand -hex 32)"

Значение secret_token Telegram будет присылать в заголовке X-Telegram-Bot-Api-Secret-Token с каждым обновлением. Первое, что должен делать обработчик, — сверять его:

php
$secret = (string) ($_SERVER['HTTP_X_TELEGRAM_BOT_API_SECRET_TOKEN'] ?? '');

if (!hash_equals(\Bitrix\Main\Config\Configuration::getValue('telegram_webhook_secret'), $secret)) {
    http_response_code(403);
    exit;
}
⚠️ Важно

Без этой проверки ваш вебхук — открытый эндпоинт. Адрес скрипта не секрет: он попадает в логи, в историю команд, его несложно подобрать по типовым путям. Кто угодно может отправить туда поддельное «обновление» с любым chat_id и любым текстом — и заставить вашего бота выполнять команды от имени несуществующего пользователя.

⚠️ Важно

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

Связь «этот Telegram-аккаунт имеет право на этот заказ» нужно установить явно. Рабочая схема — привязка при оформлении: клиент переходит в бота по ссылке t.me/yourbot?start=<одноразовый_код>, бот получает код в /start, находит по нему пользователя сайта и сохраняет chat_id в его профиль. Дальше проверка сводится к сравнению владельца заказа с сохранённым chat_id.

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

💡 Совет

Отвечайте Telegram быстро. Если обработчик не уложится в таймаут, Telegram посчитает доставку неудачной и пришлёт то же обновление снова — а ваш бот отправит пользователю второй такой же ответ. Тяжёлую работу выносите за пределы обработки вебхука, а для защиты от повторов храните обработанные update_id: это поле есть в каждом обновлении и служит готовым ключом идемпотентности.

Шаг 5: Уведомление об изменении статуса

Клиенту полезно знать, когда заказ отправлен:

php
<?php
// local/classes/Events/OrderHandler.php (дополнение)
EventManager::getInstance()->addEventHandler(
    'sale',
    'OnSaleStatusOrder',
    ['Local\\Events\\OrderHandler', 'onStatusChange']
);
// В классе OrderHandler:
public static function onStatusChange(
    int $orderId,
    string $newStatus
): void {
    // Отправляем только при переходе в "Отправлен"
    if ($newStatus !== 'F') { // F = shipped в стандартной конфигурации
        return;
    }
    $order = Order::load($orderId);
    if (!$order) return;
    // Ищем телефон/telegram клиента
    $props = $order->getPropertyCollection();
    $telegram = null;
    foreach ($props as $prop) {
        if ($prop->getField('CODE') === 'TELEGRAM_ID') {
            $telegram = $prop->getValue();
            break;
        }
    }
    if (!$telegram) return;
    // Получаем трек-номер
    $trackNumber = '';
    foreach ($order->getShipmentCollection() as $shipment) {
        if (!$shipment->isSystem()) {
            $trackNumber = $shipment->getField('TRACKING_NUMBER');
            break;
        }
    }
    $bot = new Bot(TELEGRAM_BOT_TOKEN);
    $message = "🚚 Ваш заказ #{$orderId} отправлен!";
    if ($trackNumber) {
        $message .= "\n\nТрек-номер: <code>{$trackNumber}</code>";
        $message .= "\n\n<a href=\"https://www.pochta.ru/tracking#{$trackNumber}\">Отследить</a>";
    }
    $bot->sendMessage($telegram, $message);
}

Отправка уведомлений: не в обработчике события

Отдельно про то, где вызывать sendMessage() при создании заказа.

Соблазн понятен: повесить обработчик на событие сохранения заказа и отправить сообщение прямо оттуда. Так делают почти все, и это работает — пока Telegram доступен. В момент, когда API отвечает медленно или лежит, оформление заказа у покупателя начинает висеть эти самые секунды таймаута, а при неудачном стечении — падает с ошибкой. Магазин перестаёт принимать заказы из-за недоступности мессенджера, что абсурдно.

Правильная схема: обработчик события только ставит задачу (запись в таблицу очереди или в свою сущность), а отправкой занимается отдельный процесс — агент через cron или воркер. Тогда недоступность Telegram означает задержку уведомления, а не отказ в оформлении.

Побочная выгода: появляется место для повторов. Telegram вернул ошибку — задача осталась в очереди и уйдёт через минуту, а не потеряется навсегда.

Итоги

Код отправки сообщений в Telegram простой, и написать его — самая лёгкая часть задачи. Что действительно требует внимания:

Токен и секрет вебхука — вне репозитория. Токен даёт полный контроль над ботом.

Вебхук проверяет secret_token. Без этого адрес скрипта — единственная преграда, а он не секрет.

Данные о заказе выдаются только владельцу. Номера заказов последовательны; выдача по номеру без проверки — это выгрузка базы заказов для любого желающего.

Отправка уведомлений — асинхронная. Внешний сервис не должен влиять на возможность оформить заказ.

Обработка входящих идемпотентна по update_id — Telegram повторяет доставку.

Что стоит добавить дальше: inline-кнопки для действий менеджера прямо из уведомления (подтвердить, отменить), привязку чата к пользователю сайта при оформлении и уведомления об изменении статуса — но только после того, как закрыты пункты выше.

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

Как защитить Telegram-вебхук от подделки?

Передать параметр secret_token при регистрации вебхука через setWebhook и сверять его первой строкой обработчика. Telegram присылает это значение в заголовке X-Telegram-Bot-Api-Secret-Token с каждым обновлением; сравнивать нужно через hash_equals, при несовпадении отдавать 403. Без этой проверки адрес скрипта — единственная преграда, а он не секрет: попадает в логи, в историю команд и подбирается по типовым путям. Кто угодно сможет отправить туда поддельное обновление с любым chat_id и любым текстом.

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

Потому что номера заказов последовательны, и скрипт, перебирающий числа от единицы, выгрузит состояние всех заказов магазина за несколько минут — вместе с трек-номерами, по которым на сайте службы доставки виден адрес получателя. Связь «этот Telegram-аккаунт имеет право на этот заказ» нужно устанавливать явно, а не полагаться на знание номера.

Как привязать Telegram-аккаунт к пользователю сайта?

Через одноразовый код при оформлении: клиент переходит в бота по ссылке вида t.me/yourbot?start=КОД, бот получает код в команде /start, находит по нему пользователя сайта и сохраняет chat_id в его профиль. Дальше проверка сводится к сравнению владельца заказа с сохранённым chat_id. Более простой, хотя и менее строгий вариант для магазинов без личного кабинета: спрашивать номер заказа вместе с последними четырьмя цифрами телефона и сверять оба значения — это не криптография, но перебор становится бессмысленным.

Где хранить токен Telegram-бота?

В конфигурации вне репозитория — через Configuration::getValue() или переменную окружения, а не в константе init.php, которая уедет в git. Токен — это полноценный доступ к боту: кто им владеет, тот может читать все входящие сообщения и писать от имени бота всем, кто с ним общался. Если токен всё-таки засветился, отзывайте его командой /revoke у BotFather — смена пароля тут не поможет.

Почему уведомления нельзя отправлять прямо из обработчика события?

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

Почему бот отправляет один и тот же ответ дважды?

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