Пример ниже действительно отправляет сообщение. Используйте отдельную тестовую группу и своего бота. Токен и API-ключ не нужно публиковать, вставлять в скриншоты или сохранять вместе с кодом.
Что подготовить перед отправкой
Здесь рассматриваем обычную супергруппу с включёнными темами и уже созданную тему «Заказы». Личные диалоги с темами, комментарии к публикациям канала и личные сообщения каналу не входят в этот сценарий.
Добавьте своего бота в тестовую группу и разрешите ему отправлять текстовые сообщения.
Откройте нужную тему и убедитесь, что она доступна для отправки. На время теста не удаляйте и не пересоздавайте её.
Подготовьте доступ к входящим обновлениям этого бота через уже работающий обработчик.
Для PHP-примера нужен 64-битный PHP 8.2+ с расширением cURL. Запускайте файл из терминала, не через публичный URL.
Если бот ещё не создан, начните с инструкции по BotFather. Для уже работающего бота не меняйте webhook только ради получения номера темы.
Не путайте группу, тему и отдельное сообщение
Поле | Что выбирает | В нашем примере |
|---|---|---|
chat_id | Группу, в которую отправляем сообщение | -1001234567890 |
message_thread_id | Нужную тему внутри этой группы | 73 |
message_id | Отдельное сообщение внутри чата | 418 |
Числа в таблице вымышленные. Сохраните настройки адресата парой: группа и тема. Не подставляйте ID произвольного сообщения вместо ID темы и не переносите номер темы из другой группы.
При отправке sendMessage принимает chat_id и message_thread_id. У входящего сообщения эти значения находятся в message.chat.id и message.message_thread_id. Описание полей есть в Telegram Bot API.
Получите ID темы из нового сообщения
Откройте «Заказы» и от своего пользовательского аккаунта отправьте команду /topic@your_bot, заменив your_bot на username вашего бота. Адресованная команда подходит для проверки даже при включённом Privacy Mode. Боту не обязательно отвечать на неё: сначала нам нужно увидеть само входящее обновление.
В отладке уже проверенного обработчика найдите сообщение именно из этой темы. Ниже сокращённый пример тела обновления: не относящиеся к задаче поля опущены.
{
"update_id": 900001,
"message": {
"message_id": 418,
"message_thread_id": 73,
"chat": {
"id": -1001234567890,
"title": "Тестовые уведомления",
"type": "supergroup",
"is_forum": true
},
"is_topic_message": true,
"text": "/topic@your_bot"
}
}
Для этого примера сохраняем chat_id=-1001234567890 и message_thread_id=73. Номер 418 относится только к присланной команде.
Сверьте название и ID группы: бот может одновременно состоять в нескольких чатах.
Если message_thread_id отсутствует, отправьте новую команду в явно выбранной отдельной теме и снова проверьте обновление. Не подставляйте случайное число.
Если обновление вообще не пришло, сначала проверьте получение сообщений, а не исходящий sendMessage.
Privacy Mode описан в документации Telegram. Для диагностики доставки используйте руководство по webhook. Не вызывайте deleteWebhook и не очищайте ожидающие обновления ради этого теста.
Если тему создаёт ваша программа через createForumTopic, сохраните возвращённый message_thread_id вместе с ID группы. Создание темы требует дополнительных прав; для получения номера существующей темы создавать новую не нужно. Документация createForumTopic.
Отправьте одно тестовое сообщение на PHP
Создайте файл send-to-topic.php с кодом ниже. Он читает переменные окружения процесса, отправляет один запрос без автоматических повторов и выводит идентификаторы из ответа. Файл .env самостоятельно не загружается; Composer для этого примера не нужен.
<?php
declare(strict_types=1);
$requiredEnv = static function (string $name): string {
$value = getenv($name);
if ($value === false || trim($value) === '') {
throw new RuntimeException('Missing environment variable: '.$name);
}
return trim($value);
};
try {
$chatId = $requiredEnv('TELEGRAM_CHAT_ID');
$threadId = filter_var(
$requiredEnv('TELEGRAM_THREAD_ID'),
FILTER_VALIDATE_INT,
['options' => ['min_range' => 1]],
);
if (!preg_match('/^-[1-9][0-9]*$/', $chatId) || $threadId === false) {
throw new RuntimeException('Expected a negative group ID and a positive topic ID.');
}
$token = $requiredEnv('TELEGRAM_BOT_TOKEN');
$url = 'https://api.telegram.org/bot'.$token.'/sendMessage';
$headers = ['Content-Type: application/json'];
$payload = [
'chat_id' => $chatId,
'message_thread_id' => $threadId,
'text' => 'Проверка: уведомление для темы «Заказы».',
];
$curl = curl_init($url);
if ($curl === false) {
throw new RuntimeException('Cannot initialize cURL.');
}
try {
curl_setopt_array($curl, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => $headers,
CURLOPT_POSTFIELDS => json_encode($payload, JSON_THROW_ON_ERROR | JSON_UNESCAPED_UNICODE),
CURLOPT_CONNECTTIMEOUT => 5,
CURLOPT_TIMEOUT => 15,
]);
$raw = curl_exec($curl);
$status = curl_getinfo($curl, CURLINFO_HTTP_CODE);
if ($raw === false || $status >= 500) {
throw new RuntimeException('Delivery outcome is unknown. Check the topic before retrying.');
}
} finally {
unset($curl);
}
try {
$response = json_decode($raw, true, 512, JSON_THROW_ON_ERROR);
} catch (JsonException) {
throw new RuntimeException('Unreadable response. Check the topic before retrying.');
}
if (!is_array($response) || !array_key_exists('ok', $response)) {
throw new RuntimeException('Unexpected response. Check the topic before retrying.');
}
if ($status !== 200 || $response['ok'] !== true) {
fwrite(STDERR, 'Request failed: HTTP '.$status.'; '
.($response['description'] ?? $response['error'] ?? 'no description').PHP_EOL);
exit(1);
}
echo json_encode([
'chat_id' => $response['result']['chat']['id'] ?? null,
'message_thread_id' => $response['result']['message_thread_id'] ?? null,
'message_id' => $response['result']['message_id'] ?? null,
], JSON_THROW_ON_ERROR | JSON_PRETTY_PRINT).PHP_EOL;
} catch (Throwable $exception) {
fwrite(STDERR, $exception->getMessage().PHP_EOL);
exit(1);
}
В терминале Bash задайте данные тестового адресата. Вместо чисел из примера используйте полученные значения. Ввод токена скрыт; не включайте set -x и не публикуйте окружение процесса.
read -r -s -p 'Telegram bot token: ' TELEGRAM_BOT_TOKEN printf '\n' export TELEGRAM_BOT_TOKEN export TELEGRAM_CHAT_ID='-1001234567890' export TELEGRAM_THREAD_ID='73' php send-to-topic.php unset TELEGRAM_BOT_TOKEN
Скрипт отправляет обычный текст без parse_mode: так ошибка форматирования не мешает проверке адреса. После успешного теста можно добавить свой текст и нужную разметку.
При таймауте или непонятном ответе остановитесь и проверьте тему. Отсутствие подтверждения у программы не доказывает, что сообщение не было отправлено. Повторный запуск может создать дубликат.
Проверьте ответ и сообщение в Telegram
При успешном запросе скрипт выводит примерно такой результат. Это пример, а не универсальные значения для вашей группы:
{
"chat_id": -1001234567890,
"message_thread_id": 73,
"message_id": 419
}
Сравните chat_id и message_thread_id с настройками адресата. Не ограничивайтесь отсутствием ошибки в терминале.
Откройте тему «Заказы» в Telegram и найдите тестовое сообщение. Убедитесь, что оно не появилось в соседней теме.
Сохраните пару идентификаторов в конфигурации своей интеграции. Для другой темы получите её отдельный ID.
Перед подключением реальных событий проверьте, что один тестовый заказ создаёт ровно одно уведомление.
Обработка принятого запроса и просмотр сообщения человеком — разные вещи. Ответ API не подтверждает, что менеджер уже прочитал уведомление. Если сообщения приходят дважды, используйте разбор причин дублей.
Что проверить при ошибке или неправильной теме
Сообщение попало не туда. Проверьте фактически отправленный JSON, а не только значение в настройках. Не потерялся ли
message_thread_idпри сборке запроса? Совпадает лиchat_idс группой, из которой вы получили обновление?Ответ указывает на проблему с темой. Получите пару ID заново из свежего сообщения в нужной теме. Проверьте, что тему не удалили и не создали повторно под тем же названием. Не исправляйте проблему подбором чисел.
Нет доступа к группе или отправке. Сверьте бота, членство в группе и ограничения на отправку. Соседние случаи разобраны в статье про chat not found.
Тема закрыта. Уточните её состояние у администратора и откройте нужную тему для теста. Не выдавайте боту все административные права как универсальное решение.
Таймаут или HTTP 5xx. Результат может быть неизвестен: сначала проверьте чат и журналы. Далее используйте диагностику соединения.
Telegram ограничил частоту запросов. Не запускайте скрипт в цикле. Правила ожидания и
retry_afterразобраны в руководстве по 429.
Для этого примера выбрана отдельная именованная тема, а не «Общая». Не переносите на неё случайно найденные ID и не смешивайте выбор темы с ответом на конкретное сообщение. Сначала добейтесь воспроизводимой отправки в одну тестовую тему, затем расширяйте сценарий.
Как выполнить тот же запрос через BotGate
Если ваш бот уже подключён к BotGate, пара идентификаторов и тело запроса остаются теми же. Меняются адрес API и авторизация. В send-to-topic.php замените три строки настройки $token, $url и $headers следующим блоком:
$botId = $requiredEnv('BOTGATE_BOT_ID');
$url = 'https://bot-gate.ru/api/v1/bots/'.rawurlencode($botId).'/sendMessage';
$headers = [
'Content-Type: application/json',
'Authorization: Bearer '.$requiredEnv('BOTGATE_API_KEY'),
];
Остальную часть файла менять не нужно. BOTGATE_BOT_ID — публичный ID подключённого бота из кабинета, не Telegram username. Используйте API-ключ владельца этого бота. В этом варианте TELEGRAM_BOT_TOKEN скрипту не нужен.
read -r -s -p 'BotGate API key: ' BOTGATE_API_KEY printf '\n' export BOTGATE_API_KEY export BOTGATE_BOT_ID='bot_REPLACE_ME' export TELEGRAM_CHAT_ID='-1001234567890' export TELEGRAM_THREAD_ID='73' php send-to-topic.php unset BOTGATE_API_KEY
Сначала замените bot_REPLACE_ME и тестовые числа своими значениями. Выполните один запуск и повторите проверку ответа и сообщения в выбранной теме.
При получении ID темы используйте обновление из вашего обработчика webhook BotGate после проверки подлинности. Метод getUpdates через сервис недоступен. Формат запроса и проверка webhook описаны в документации BotGate.
BotGate передаёт параметры запроса, но не исправляет неверный номер темы, закрытую тему или отсутствие прав. При ошибке смотрите не только HTTP-статус, но и тело ответа: отказ Telegram проходит через BotGate с HTTP 400 и полями Telegram; собственные ошибки сервиса проверяются отдельно.
Первичные источники
Telegram Bot API: sendMessage — параметры отправки.
Telegram Bot API: Message — поля входящего и отправленного сообщения.
Telegram Bot API: createForumTopic — создание темы и результат.
Telegram: Privacy Mode — получение сообщений в группе.
PHP: curl_exec и getenv — HTTP-запрос и окружение процесса.