Перейти к содержанию
12 минут чтения
Тема: PHP Telegram Bot API

Telegram-бот на PHP: отправка сообщений и работа с BotGate SDK

Подключим обычное PHP-приложение к Telegram через BotGate: установим SDK, проверим бота, отправим сообщение и файл, затем разберём получение обновлений. Фреймворк для этих примеров не нужен.

Что подготовить перед установкой

  1. Создайте бота через официальный BotFather и добавьте его Telegram-токен в кабинет BotGate.
  2. Получите API-ключ BotGate и публичный ID бота вида bot_…. Telegram-токен, API-ключ и публичный ID — разные значения.
  3. Подготовьте PHP 8.2 или новее, Composer и исходящее HTTPS-соединение с BotGate.
  4. Откройте личный чат с ботом и отправьте ему /start. Для первого сообщения используйте собственный тестовый чат.

chat_id — идентификатор чата-получателя, а не ID бота. Его можно получить из message.chat.id входящего обновления после настройки webhook. В BotGate getUpdates не поддерживается; пример разбора обновления есть ниже. Если бот должен писать в группу или канал, отдельно проверьте его участие и права.

Не публикуйте ключи в HTML, JavaScript, Git или скриншотах. PHP выполняет запрос на сервере; браузеру достаточно обратиться к вашему собственному авторизованному обработчику.

1. Установите пакет и создайте клиент

В каталоге вашего PHP-приложения:

Composer
composer require botgate/sdk

Composer установит зависимости HTTP-клиента. Сохраните composer.lock, чтобы окружения использовали согласованные версии. Эти команды предназначены для вашего проекта, а не для каталога сервера BotGate.

Передайте процессу PHP переменные BOTGATE_API_KEY и BOTGATE_BOT_ID; для отправки понадобится TELEGRAM_CHAT_ID. Используйте настройки хостинга, контейнера или менеджера секретов. Обычный PHP не загружает файл .env автоматически. Если проект использует dotenv, его загрузку нужно выполнить до этого кода.

Создайте bootstrap.php рядом с vendor. PHP-файлы в примерах должны начинаться с <?php; открывающий тег в блоках опущен.

PHP · bootstrap.php
declare(strict_types=1);

use BotGate\Client;
use BotGate\Config;

require __DIR__.'/vendor/autoload.php';

$requiredEnv = static function (string $name): string {
    $value = getenv($name);
    if ($value === false || trim($value) === '') {
        throw new RuntimeException('Не задана переменная '.$name);
    }

    return $value;
};

$client = Client::fromConfig(new Config(
    apiKey: $requiredEnv('BOTGATE_API_KEY'),
    timeout: 20.0,
    maxRetries: 0,
));

$bot = $client->bot($requiredEnv('BOTGATE_BOT_ID'));

maxRetries: 0 выбран намеренно: решение о повторе будет принимать приложение. Стандартный транспорт SDK повторяет HTTP 429, 502, 503 и 504. При ошибке после отправки неизвестно, успел ли Telegram выполнить действие; повтор может создать второе сообщение.

Сначала выполните безопасный запрос, который ничего не отправляет:

PHP · check.php
declare(strict_types=1);

require __DIR__.'/bootstrap.php';

$response = $bot->call('getMe');
echo $response->ok ? "Бот доступен\n" : "Проверка не пройдена\n";

Запустите php check.php в среде проекта. Успех означает доступность этого метода через шлюз; отправка в чат и входящий webhook проверяются отдельно.

2. Отправьте сообщение в тестовый чат

Создайте CLI-скрипт send.php. Он выполняет одну попытку и не повторяет сообщение автоматически:

PHP · send.php
declare(strict_types=1);

use BotGate\Exception\BotGateException;
use BotGate\Exception\RateLimitException;
use BotGate\Exception\TelegramException;
use BotGate\Exception\TransportException;

require __DIR__.'/bootstrap.php';

try {
    $response = $bot->call('sendMessage', [
        'chat_id' => $requiredEnv('TELEGRAM_CHAT_ID'),
        'text' => 'Проверка уведомлений из PHP',
    ]);

    $result = is_array($response->result) ? $response->result : [];
    echo 'Отправлено. message_id: '.($result['message_id'] ?? 'не указан')."\n";
} catch (RateLimitException $error) {
    $wait = $error->retryAfter();
    fwrite(STDERR, 'Лимит BotGate. Ожидание: '.($wait ?? 'не указано')." секунд\n");
    exit(1);
} catch (TelegramException $error) {
    if ($error->telegramErrorCode() === 429) {
        $wait = $error->retryAfter();
        fwrite(STDERR, 'Лимит Telegram. Ожидание: '.($wait ?? 'не указано')." секунд\n");
    } else {
        fwrite(STDERR, 'Telegram отклонил запрос, код '.($error->telegramErrorCode() ?? 'не указан').". Проверьте параметры и права бота.\n");
    }
    exit(1);
} catch (TransportException $error) {
    fwrite(STDERR, "Сбой соединения. Результат отправки неизвестен; проверьте чат и журнал.\n");
    exit(1);
} catch (BotGateException $error) {
    fwrite(STDERR, 'Запрос остановлен, HTTP '.$error->httpStatusCode().". Проверьте настройки и ответ API.\n");
    exit(1);
}

После php send.php проверьте сообщение в чате. Повторный запуск скрипта отправит ещё одно сообщение. Не привязывайте такой вызов к обычной загрузке публичной страницы: обновление страницы или повтор HTTP-запроса тогда тоже может вызвать отправку.

Для динамического текста начните без parse_mode. Если нужен HTML или Markdown, экранируйте пользовательские значения по правилам выбранного формата. Ошибка разметки — это ошибка параметров сообщения, а не признак недоступности Telegram.

В веб-приложении результат удобнее сохранять в журнале уведомлений: идентификатор бизнес-события, попытка, время и полученный message_id. Пользовательскую страницу не следует удерживать до завершения долгой сетевой операции; для регулярных уведомлений используйте очередь.

3. Отправьте небольшой файл

Для multipart-загрузки SDK принимает части запроса. MultipartField::file() не открывает путь сам. Передайте ресурс fopen, иначе строка пути станет содержимым загружаемого файла.

Создайте рядом со скриптом небольшой example.txt с тестовым текстом:

PHP · send-file.php
declare(strict_types=1);

use BotGate\Http\MultipartField;

require __DIR__.'/bootstrap.php';

$stream = fopen(__DIR__.'/example.txt', 'rb');
if ($stream === false) {
    throw new RuntimeException('Не удалось открыть тестовый файл.');
}

try {
    $bot->callMultipart('sendDocument', [
        MultipartField::field('chat_id', $requiredEnv('TELEGRAM_CHAT_ID')),
        MultipartField::file('document', $stream, 'example.txt', 'text/plain'),
        MultipartField::field('caption', 'Тестовый документ из PHP'),
    ]);
} finally {
    if (is_resource($stream)) {
        fclose($stream);
    }
}

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

Скачивание устроено отдельно: сначала вызовите getFile с полученным file_id, затем передайте file_path в $bot->downloadFile(...). Метод возвращает PSR-поток. Сохраняйте его в разрешённое место по частям и закрывайте после чтения; не считайте публичный URL с токеном безопасным способом отдать файл браузеру. Формат маршрута описан в документации файлов.

4. Принимайте события через webhook

Чтобы бот реагировал на команды, нужен HTTPS POST-обработчик. Его адрес указывается в карточке бота BotGate. Сервис доставляет JSON и заголовок X-BotGate-Signature; Webhook Secret берётся из той же карточки.

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

PHP · decode-update.php
declare(strict_types=1);

use BotGate\DTO\Update;
use BotGate\Webhook\SignatureValidator;

function decodeBotGateUpdate(string $body, string $signature, string $secret): Update
{
    if ($secret === '') {
        throw new RuntimeException('Webhook Secret не настроен.');
    }

    (new SignatureValidator($secret))->validate($body, $signature);
    $update = Update::fromJson($body);

    if (!is_int($update->payload['update_id'] ?? null)) {
        throw new UnexpectedValueException('Отсутствует корректный update_id.');
    }

    return $update;
}

Загрузите Composer autoload и эту функцию в вашем POST-маршруте. Тело читайте из php://input, подпись — из $_SERVER['HTTP_X_BOTGATE_SIGNATURE'], секрет — из защищённой конфигурации. Не декодируйте и не пересобирайте JSON до проверки подписи.

При неверной подписи возвращайте 403, при некорректном JSON — 400. Ошибка конфигурации или сохранения события требует ответа об ошибке сервера. После проверки надёжно сохраните событие или поставьте задание в очередь и только затем отвечайте HTTP 2xx. BotGate ожидает подтверждение в течение 10 секунд.

Из $update->message() можно получить массив сообщения и message.chat.id; для кнопки используется $update->callbackQuery(). Сохраняйте уникальность пары «бот + update_id», чтобы повтор доставки не выполнил одну команду дважды. Полное тело события не нужно выводить в публичный ответ или обычные диагностические логи.

Подробная последовательность проверки адреса, TLS, очереди и обработчика есть в руководстве по webhook. Если проект использует Laravel, готовый маршрут и событие пакета разобраны в статье для Laravel.

Какие ошибки нужно различать

Ошибки PHP SDK и действия приложения
СитуацияДействие
401 / 403 / 404Проверьте API-ключ, публичный ID, владельца, статус бота и разрешённый метод. Повтор без исправления настроек не поможет.
HTTP 429 BotGateSDK бросает RateLimitException. Метод retryAfter() возвращает ожидание из числового заголовка Retry-After, если он передан.
Ошибка Telegram в JSON, HTTP 400SDK бросает TelegramException. telegramErrorCode() содержит код Telegram, parameters() — параметры ошибки. При коде 429 используйте retryAfter(); null означает, что корректное время ожидания не передано.
Таймаут / 502 / обрыв соединенияРезультат операции может быть неизвестен. Не повторяйте отправку автоматически только на основании этой ошибки.

Не стройте обработку ошибок только на проверке $response->ok: неуспешный ответ приводит к исключению ещё до получения DTO. У TelegramException метод httpStatusCode() возвращает HTTP-статус BotGate (400), а telegramErrorCode() — код Telegram (например, 429). Планирование отложенной попытки через SDK разобрано в статье о лимитах.

В журнале сохраняйте этап, метод, идентификатор уведомления и безопасный код ошибки. Не записывайте API-ключ и полный дамп исключения вместе с запросом: транспортное исключение может содержать сетевые подробности.

Что проверить до подключения реальных уведомлений

  1. getMe проходит из той же среды, где работает приложение.
  2. Однократный запуск отправляет одно сообщение нужному получателю.
  3. Тестовый документ содержит байты файла, а не строку пути к нему.
  4. Неверная подпись webhook отклоняется; правильная проверяется по исходному телу.
  5. Ошибки лимита, параметров и соединения обрабатываются по-разному.
  6. Повтор одного бизнес-события не приводит к бесконтрольной повторной отправке.

SDK решает сетевую часть интеграции. Хранение состояния диалога, правила команд, очередь уведомлений и защита от дублей остаются задачами приложения.

Источники

Подключение, настройка клиента и обработка ошибок описаны в README BotGate PHP SDK. Форматы запросов и ответов сервиса — в документации BotGate.

Это учебная интеграция для собственного тестового бота. Отправляющие скрипты меняют состояние чата.