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

Ошибка Telegram chat not found: как проверить chat_id и доступ бота

Приложение подключилось к Telegram, getMe отвечает, но первая отправка заканчивается Bad Request: chat not found. На примере уведомления в тестовый чат проверим, от имени какого бота идёт запрос, откуда взялся chat_id и доступен ли получатель. Затем разберём группы, каналы и смену идентификатора при переходе в супергруппу.

Что означает chat not found

Это ответ API на запрос к определённому чату. Он отличается от таймаута: сервер уже сообщил об ошибке. По одной фразе нельзя заключить, что чат удалён или Telegram недоступен. Сначала нужно проверить сочетание «бот + адресат».

JSON · пример ответа Telegram
{
  "ok": false,
  "error_code": 400,
  "description": "Bad Request: chat not found"
}

Посмотрите error_code и description в JSON-ответе. Код 400 сам по себе ещё не означает chat not found: важен текст конкретного отказа. Если ваша библиотека выбрасывает исключение, найдите в нём исходный ответ Telegram.

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

1. Убедитесь, что запрос выполняет нужный бот

Частая ошибка интеграции — перепутать тестового и рабочего бота. Пользователь написал одному, а приложение пытается отправить сообщение от другого. Проверка должна начинаться с getMe через тот же клиент и конфигурацию, которые использует неудачная отправка.

  • Сверьте username и ID из ответа с ботом, которому вы отправили /start или которого добавили в группу.
  • Сверьте токен в конфигурации приложения с нужным ботом. Не публикуйте токен в логах или скриншотах.
  • Если CLI работает, а очередь — нет, сравните окружение и настройки worker-а. Долгоживущий процесс мог сохранить прежнюю конфигурацию.

Успешный getMe подтверждает, что этот запрос выполнен от имени определённого бота. Он ещё ничего не говорит о доступе к получателю. Пример диагностики ниже проверяет эти этапы отдельно.

2. Получите chat_id из надёжного источника

Для обычного входящего сообщения используйте message.chat.id, для публикации канала — channel_post.chat.id. Не подменяйте адрес чата полем from.id: например, у сообщения в группе автор и чат — разные сущности. Формат обновлений описан в Telegram Update.

JSON · условное обновление из группы, идентификаторы вымышлены
{
  "update_id": 900001,
  "message": {
    "message_id": 15,
    "from": {"id": 123456789, "is_bot": false, "first_name": "Тест"},
    "chat": {"id": -1001234567890, "type": "supergroup", "title": "Тестовая группа"},
    "date": 1790931600,
    "text": "/check"
  }
}

Адрес группы в этом примере — -1001234567890. Число 123456789 относится к автору. Не копируйте эти вымышленные значения в рабочую конфигурацию.

Что передавать в chat_id
ПолучательЧто проверить
Личный чатВозьмите chat.id из сообщения именно этому боту. Обычный username человека и его телефон не заменяют числовой идентификатор личного чата.
Группа или супергруппаСкопируйте полный chat.id, включая знак минус. Не дописывайте префикс -100 вручную: идентификатор нужно получить из API.
Публичная супергруппа или каналДля поддерживающего такой адрес метода можно использовать @username. Передавайте имя, а не полный URL t.me и не название чата.
Закрытая группа или каналСсылка-приглашение не является chat_id. Получите числовой ID через доступные боту обновления и подтвердите, что бот добавлен в нужный чат.

Возьмите обновление из уже работающего обработчика: webhook или процесса long polling. Не запускайте второй getUpdates параллельно существующему процессу и не удаляйте рабочий webhook ради получения ID. Если события не поступают, сначала пройдите диагностику получения обновлений.

Сохраняйте ID без округления, потери знака и преобразования в экспоненциальную запись. Подойдут десятичная строка или достаточно широкий знаковый целочисленный тип; 32-битное поле БД для групповых ID не подходит. В интерфейсе настроек полезно рядом показывать название проверенного чата, чтобы человек заметил ошибку выбора.

3. Проверьте отношения между ботом и чатом

Личное уведомление пользователю

Для обычной личной переписки пользователь должен сначала сам обратиться к боту, например отправить /start. Бот не получает возможность написать произвольному человеку только потому, что приложение знает его ID. Это базовое ограничение описано во введении Telegram для разработчиков.

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

Группа и канал

В клиенте Telegram проверьте, присутствует ли нужный бот в списке участников и не ограничена ли отправка сообщений. Для публикации в канале проверьте права администратора на публикацию; для группы не выдавайте дополнительные административные права без необходимости.

Не смешивайте чтение обновлений и отправку: настройки privacy mode относятся к получению сообщений в группе. Они не исправляют неправильный chat_id и не заменяют права на публикацию. Если входящие события не доходят, это отдельная ветка диагностики webhook.

getChat позволяет проверить, какой чат доступен по указанному адресу; getChatMember — состояние самого бота в группе или канале. Успех getChat не гарантирует разрешение на sendMessage. Для тем форума отдельно проверьте message_thread_id: ID темы передаётся своим параметром и не заменяет ID чата. Business-сценарии и личные сообщения каналам требуют дополнительных параметров и не входят в пример ниже.

Диагностические запросы напрямую к Telegram

Команды рассчитаны на Bash и curl, например в Linux-консоли вашего сервера. SDK и сторонний сервис не нужны. Выполняйте шаги в одной сессии: сначала проверьте ответ getMe, затем переходите к чату и правам. Эти три метода не отправляют сообщения.

Введите токен скрыто. Функция передаёт URL через стандартный ввод curl, чтобы токен не попадал в аргументы процесса. Не включайте отладочную трассировку и не публикуйте содержимое сессии.

Bash · подготовка и проверка бота
set +x
read -rsp 'Telegram Bot Token: ' TG_TOKEN
printf '\n'

telegram_call() {
  local method="$1"
  shift
  printf 'url = "https://api.telegram.org/bot%s/%s"\n' "$TG_TOKEN" "$method" |
    curl -q --config - --request POST --silent --show-error \
      --connect-timeout 5 --max-time 15 \
      --write-out '\nHTTP=%{http_code}\n' "$@"
}

telegram_call getMe

Ожидается ok: true и ваш бот в result. Сверьте username и запомните числовой result.id: он понадобится для проверки членства. Если getMe отклонён, сначала исправьте токен или соединение.

Bash · проверка адресата
read -rp 'chat_id тестового чата: ' CHAT_ID
telegram_call getChat --data-urlencode "chat_id=$CHAT_ID"

Проверьте result.id, type и название чата, если оно есть. При chat not found вернитесь к источнику ID и участию бота. Для группы или канала после успешного getChat выполните ещё один запрос:

Bash · состояние самого бота в группе или канале
read -rp 'Числовой result.id бота из getMe: ' TELEGRAM_BOT_ID
telegram_call getChatMember \
  --data-urlencode "chat_id=$CHAT_ID" \
  --data-urlencode "user_id=$TELEGRAM_BOT_ID"

Здесь user_id — ID самого бота, а не автора сообщения. Статусы left и kicked означают отсутствие участия, restricted требует проверки конкретных ограничений. В канале проверьте can_post_messages. Успех чтения сведений не заменяет проверку разрешения на отправку.

Если группа была преобразована в супергруппу

У мигрировавшей группы меняется идентификатор. Telegram может передать новый ID в parameters.migrate_to_chat_id ответа об ошибке или в служебном сообщении. Читайте это поле в JSON-ответе, а не вычисляйте адрес по тексту ошибки. Поле описано в ResponseParameters.

  1. Сопоставьте старый и новый ID с нужной группой и ботом.
  2. Проверьте новый адрес через getChat и права бота.
  3. Обновите сохранённого получателя в приложении и настройки, из которых его читает очередь.
  4. Учтите уже поставленные задания: если старый ID записан прямо в их payload, изменение переменной окружения его не заменит.

Не вычисляйте новый адрес добавлением цифр к старому. И не меняйте все сохранённые чаты по одному сообщению об ошибке. Для уведомлений удобнее хранить ссылку на запись получателя, чтобы управлять изменением адреса в одном месте и видеть, какие задания оно затронет.

Выполните одну проверочную отправку

После исправления адреса и прав отправьте простой текст без разметки. Следующая команда действительно создаёт сообщение в выбранном тестовом чате. Она использует функцию telegram_call и переменные из предыдущих шагов; автоматических повторов в ней нет.

Bash · одна отправка без разметки
telegram_call sendMessage \
  --data-urlencode "chat_id=$CHAT_ID" \
  --data-urlencode "text=Проверка получателя: сообщение доставлено."

unset TG_TOKEN CHAT_ID TELEGRAM_BOT_ID
unset -f telegram_call

ok: true и объект сообщения в result подтверждают выполнение метода. Проверьте, что текст появился именно в нужном чате. Если сообщение не отправляете, всё равно выполните две строки unset для очистки переменных и функции после диагностики.

Если простой текст доставлен, а исходное уведомление отклоняется, сравните остальные параметры. Ошибка can't parse entities относится к разметке — её разбираем в отдельном руководстве. Неверная тема форума или ссылка на удалённое сообщение также требуют своей проверки.

Почему не нужно повторять любую ошибку 400

Код 400 объединяет разные причины. Не делайте правило «получили 400 — заменяем chat_id» и не считайте любую такую ошибку chat not found. Описание помогает диагностике, а структурированные поля следует использовать там, где они предусмотрены.

Действия при ошибках отправки в чат
СитуацияЧто делать
chat not foundОстановите конкретное уведомление. Исправьте бота, адрес или доступ и только затем повторяйте проверку.
Telegram сообщил запрет доступа или блокировкуПроверьте причину отказа; для заблокировавшего бота пользователя приостановите доставку. Частые повторы не возвращают разрешение.
migrate_to_chat_idПроверьте и обновите адрес нужной группы. Учтите старые задания в очереди.
Telegram 429Это лимит, а не неверный получатель. Примените отдельную политику ожидания.
Таймаут, обрыв соединения, 502Результат отправки может быть неизвестен. Слепой повтор способен создать второе сообщение.

Обработку ограничений и отложенные попытки разбираем в руководстве про 429. Изменение сетевого маршрута само по себе не исправляет адрес получателя и права бота.

Как убедиться, что причина устранена

  1. getMe показывает ожидаемого бота из того окружения, где происходил сбой.
  2. chat_id получен из проверенного обновления или API, а не из ссылки-приглашения.
  3. Подтверждены нужный чат, участие бота и права на отправку.
  4. После миграции обновлены и настройки, и правила обработки старых заданий.
  5. Одно тестовое уведомление пришло в нужный чат, а ошибочные задания не повторяются бесконечно.

В тестах приложения подставьте отказ getChat, недоступного участника, ответ с migrate_to_chat_id и сетевой сбой. Проверяйте разные действия для этих случаев. Для проверки логики не нужно рассылать сообщения случайным пользователям или перебором искать работающий ID.

Если вы используете BotGate

Проверки бота, chat_id и прав остаются теми же. В настройках клиента дополнительно сверьте публичный ID bot_…: он выбирает бота в BotGate и не заменяет адрес получателя.

При обращении через BotGate такой ответ метода приходит с HTTP 400. В PHP SDK это TelegramException: код Telegram доступен через telegramErrorCode(), пояснение — через getMessage(). Собственный HTTP 404 BotGate означает другую проблему: например, неверный публичный ID бота в URL. Подробнее — в таблице ошибок сервиса.

Для получения chat_id используйте свой webhook: getUpdates в BotGate не поддерживается. Настройка и проверка подписи описаны в руководстве PHP.

Те же проверки через SDK

Пример использует PHP 8.2+ и botgate/sdk. Установите пакет через composer require botgate/sdk и создайте bootstrap.php из инструкции подключения. Он загружает Composer, создаёт $bot с maxRetries: 0 и функцию $requiredEnv. Задайте TELEGRAM_CHAT_ID для своего тестового получателя.

Создайте рядом check-chat.php. Файл должен начинаться с <?php; тег в блоке опущен. Скрипт только читает сведения, но его запросы учитываются в лимите API.

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

use BotGate\Exception\BotGateException;
use BotGate\Exception\TelegramException;

require __DIR__.'/bootstrap.php';

$chatId = trim($requiredEnv('TELEGRAM_CHAT_ID'));
$step = 'getMe';

try {
    $me = $bot->call('getMe')->result;
    if (!is_array($me) || !is_int($me['id'] ?? null)) {
        throw new UnexpectedValueException('Не получен ID бота.');
    }
    echo 'ID бота: '.$me['id']."\n";
    echo 'Username: '.($me['username'] ?? 'не задан')."\n";

    $step = 'getChat';
    $chat = $bot->call('getChat', ['chat_id' => $chatId])->result;
    if (!is_array($chat) || !is_int($chat['id'] ?? null)) {
        throw new UnexpectedValueException('Не получен ID чата.');
    }
    echo 'ID чата: '.$chat['id']."\n";
    echo 'Тип чата: '.($chat['type'] ?? 'не указан')."\n";

    if (in_array($chat['type'] ?? null, ['group', 'supergroup', 'channel'], true)) {
        $step = 'getChatMember';
        $member = $bot->call('getChatMember', [
            'chat_id' => $chat['id'],
            'user_id' => $me['id'],
        ])->result;
        if (!is_array($member)) {
            throw new UnexpectedValueException('Не получено состояние бота в чате.');
        }
        echo 'Статус бота: '.($member['status'] ?? 'не указан')."\n";
        if (($chat['type'] ?? null) === 'channel') {
            echo 'Право публикации: '.(($member['can_post_messages'] ?? false) ? 'да' : 'не подтверждено')."\n";
        }
    }
} catch (TelegramException $error) {
    fwrite(STDERR, $step.': Telegram '.($error->telegramErrorCode() ?? 'без кода')."\n");
    fwrite(STDERR, $error->getMessage()."\n");
    $newChatId = $error->parameters()['migrate_to_chat_id'] ?? null;
    if (is_int($newChatId)) {
        fwrite(STDERR, 'Telegram сообщил новый ID группы: '.$newChatId."\n");
    }
    exit(1);
} catch (BotGateException $error) {
    fwrite(STDERR, $step.': ошибка SDK, HTTP '.$error->httpStatusCode().". Проверьте соединение и настройки.\n");
    exit(1);
}

Запуск: php check-chat.php. Если остановился getMe, сначала разберите выбранного бота и авторизацию. Если getMe прошёл, а getChat нет — проверьте адресата и доступ к нему. Если оба прошли, изучите членство и права; окончательную проверку отправки выполняйте отдельно.

Статусы left и kicked указывают на отсутствие участия, restricted требует проверки конкретных ограничений. Само слово member не является универсальным разрешением на все методы API. Сверяйте ответ с настройками именно этого чата. Диагностический вывод предназначен для вашего терминала, а не публичной страницы.

Источники и границы примера

Руководство относится к обычным уведомлениям от бота пользователю, в группу или канал. Оно не заменяет отдельную настройку Telegram Business, тем форума и других специальных сценариев.