Проекты
Заявка с сайта → Telegram
Практическое руководство по отправке заявок с сайта в Telegram на PHP. Настройка базы данных, генерация коротких ID, отправка inline-кнопок и обработка вебхуков.
Одна из самых частых практических задач для PHP-разработчика — отправка заявок (лидов) с сайта напрямую в Telegram-чат компании или менеджеру. Это решает проблему «потерянных писем» в спаме и позволяет моментально реагировать на новые заказы.
В этой главе мы построим интерактивную систему: заявка с сайта сохраняется в базу данных, отправляется в чат менеджеров с кнопкой «Взять в работу», а при клике на кнопку статус заявки обновляется в БД, и сообщение в Telegram меняет свой вид, показывая, кто именно забрал лид.
Форма на сайте
↓
Обработчик PHP (submit.php) → Запись в БД (leads)
↓
Отправка в Telegram (sendMessage с Inline-кнопкой)
↓
Клик менеджера «Взять в работу»
↓
Webhook (webhook.php) → Обновление БД → Изменение сообщения (editMessageText)Шаг 1. База данных и сохранение лида
Для работы нам понадобится таблица в базе данных. Мы будем использовать уникальный строковый идентификатор лида вместо обычного автоинкремента (ID 1, 2, 3). Это защитит данные от перебора конкурентами и сэкономит место в лимите callback_data (который в Telegram ограничен всего 64 байтами, туда не поместятся длинные тексты, поэтому лаконичный ID — идеальное решение).
Создадим простую таблицу leads:
CREATE TABLE leads (
id VARCHAR(14) PRIMARY KEY,
name VARCHAR(255) NOT NULL,
phone VARCHAR(50) NOT NULL,
status VARCHAR(50) DEFAULT 'new',
manager_username VARCHAR(255) DEFAULT NULL,
created_at INT NOT NULL
);Теперь напишем скрипт submit.php, который принимает POST-запрос от формы на сайте, сохраняет лид в базу и отправляет уведомление в Telegram.
Используем функцию telegramApi(), которую мы сделали в главе Первый запрос: getMe. Здесь мы подключаем файл с ней (предположим, она вынесена в bot_lib.php) и вызываем её — полную реализацию функции cURL здесь не дублируем.
Для отправки сообщения нам также понадобится ID чата менеджеров TELEGRAM_MANAGER_CHAT. Как получить этот ID (группы или личного чата), подробно описано в главе chat_id и user_id.
<?php
// submit.php
require_once 'bot_lib.php'; // Здесь находится функция telegramApi()
// Настройки подключения к БД
$dsn = 'mysql:host=localhost;dbname=my_shop;charset=utf8mb4';
$dbUser = 'db_user';
$dbPass = 'db_password';
try {
$pdo = new PDO($dsn, $dbUser, $dbPass, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
]);
} catch (PDOException $e) {
die('Ошибка подключения к БД: ' . $e->getMessage());
}
// Получаем данные из формы
$name = trim($_POST['name'] ?? '');
$phone = trim($_POST['phone'] ?? '');
if (empty($name) || empty($phone)) {
die('Заполните все поля!');
}
// Генерируем уникальный ID лида (14 символов)
$leadId = bin2hex(random_bytes(7));
// Сохраняем в БД
$stmt = $pdo->prepare('INSERT INTO leads (id, name, phone, created_at) VALUES (?, ?, ?, ?)');
$stmt->execute([$leadId, $name, $phone, time()]);
// Читаем ID чата менеджеров из окружения
$managerChatId = getenv('TELEGRAM_MANAGER_CHAT');
if (!$managerChatId) {
die('Ошибка: Не задан TELEGRAM_MANAGER_CHAT в окружении');
}
// Формируем текст сообщения с HTML-разметкой
$text = "<b>🚨 Новая заявка!</b>\n\n";
$text .= "<b>Имя:</b> " . htmlspecialchars($name, ENT_QUOTES, 'UTF-8') . "\n";
$text .= "<b>Телефон:</b> " . htmlspecialchars($phone, ENT_QUOTES, 'UTF-8') . "\n";
$text .= "<b>ID заявки:</b> <code>{$leadId}</code>";
// Подключаем Inline-клавиатуру (подробнее о структуре в главе "Inline Keyboard")
// Ссылка на главу: <a href="/telegram-bot-api/inline-keyboards">Inline Keyboard</a>
$keyboard = [
'inline_keyboard' => [[
['text' => '📥 Взять в работу', 'callback_data' => 'take:' . $leadId]
]]
];
// Отправляем запрос через наш хелпер
$response = telegramApi('sendMessage', [
'chat_id' => $managerChatId,
'text' => $text,
'parse_mode' => 'HTML',
'reply_markup' => $keyboard
]);
// Логируем результат для отладки
file_put_contents('debug.log', json_encode($response, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) . "\n", FILE_APPEND);
if (isset($response['ok']) && $response['ok'] === true) {
echo "Заявка успешно отправлена!";
} else {
echo "Ошибка при отправке в Telegram. Подробности в debug.log";
}Шаг 2. Анализ ответа Telegram API
После вызова sendMessage Telegram возвращает JSON-ответ. Нам важно убедиться, что поле ok равно true. Чтобы посмотреть, что именно пришло от API, мы записали ответ в файл debug.log с помощью json_encode. Посмотреть лог в реальном времени в терминале можно командой:
tail -f debug.logВот пример успешного ответа, который запишется в лог:
{
"ok": true,
"result": {
"message_id": 1425,
"from": {
"id": 123456789,
"is_bot": true,
"first_name": "My Business Bot",
"username": "my_biz_bot"
},
"chat": {
"id": -100123456789,
"title": "Отдел продаж",
"type": "supergroup"
},
"date": 1698754321,
"text": "🚨 Новая заявка!\n\nИмя: Иван\nТелефон: +79991112233\nID заявки: a1b2c3d4e5f6g7",
"entities": [
{ "offset": 0, "length": 16, "type": "bold" },
{ "offset": 23, "length": 4, "type": "bold" }
]
}
}Если запрос завершился ошибкой (например, неверный chat_id или бот не добавлен в группу), поле ok будет false, а в поле description вернется причина ошибки:
{
"ok": false,
"error_code": 400,
"description": "Bad Request: chat not found"
}Шаг 3. Обработка клика «Взять в работу» на Webhook
Когда менеджер нажимает кнопку «Взять в работу», Telegram отправляет CallbackQuery на ваш Webhook. Нам нужно принять этот запрос, обновить статус лида в базе данных, изменить текст сообщения (убрать кнопку и написать, кто забрал лид) и обязательно вызвать answerCallbackQuery, чтобы у пользователя пропала анимация загрузки на кнопке.
Подробнее о настройке вебхуков читайте в главе Webhook, а о работе с кнопками — в callback_query.
Используем функцию telegramApi(), которую мы сделали в главе Первый запрос: getMe. Здесь мы подключаем её из файла bot_lib.php.
<?php
// webhook.php
require_once 'bot_lib.php'; // Здесь находится функция telegramApi()
$input = file_get_contents('php://input');
$update = json_decode($input, true);
if (!$update || !isset($update['callback_query'])) {
exit; // Игнорируем всё, кроме кликов по кнопкам
}
$callbackQuery = $update['callback_query'];
$callbackData = $callbackQuery['data'];
$chatId = $callbackQuery['message']['chat']['id'];
$messageId = $callbackQuery['message']['message_id'];
$callbackQueryId = $callbackQuery['id'];
$manager = $callbackQuery['from'];
// Проверяем, наш ли это callback
if (strpos($callbackData, 'take:') !== 0) {
exit;
}
$leadId = str_replace('take:', '', $callbackData);
// Подключение к БД
$dsn = 'mysql:host=localhost;dbname=my_shop;charset=utf8mb4';
$dbUser = 'db_user';
$dbPass = 'db_password';
try {
$pdo = new PDO($dsn, $dbUser, $dbPass, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
]);
} catch (PDOException $e) {
exit; // В вебхуках ошибки БД лучше гасить молча для Telegram, но логировать у себя
}
// Проверяем статус лида в БД
$stmt = $pdo->prepare('SELECT * FROM leads WHERE id = ?');
$stmt->execute([$leadId]);
$lead = $stmt->fetch();
if (!$lead) {
// Отправляем всплывающее уведомление в Telegram, если лид не найден
telegramApi('answerCallbackQuery', [
'callback_query_id' => $callbackQueryId,
'text' => 'Ошибка: Заявка не найдена в базе.',
'show_alert' => true
]);
exit;
}
if ($lead['status'] !== 'new') {
// Если лид уже обработан другим менеджером
$takenBy = $lead['manager_username'] ? '@' . $lead['manager_username'] : 'другим менеджером';
telegramApi('answerCallbackQuery', [
'callback_query_id' => $callbackQueryId,
'text' => "Эта заявка уже обрабатывается {$takenBy}!",
'show_alert' => true
]);
exit;
}
// Обновляем статус в БД
$managerUsername = $manager['username'] ?? $manager['first_name'];
$updateStmt = $pdo->prepare("UPDATE leads SET status = 'in_progress', manager_username = ? WHERE id = ?");
$updateStmt->execute([$managerUsername, $leadId]);
// Формируем новый текст сообщения без кнопки
$newText = "<b>✅ Заявка взята в работу!</b>\n\n";
$newText .= "<b>Имя:</b> " . htmlspecialchars($lead['name'], ENT_QUOTES, 'UTF-8') . "\n";
$newText .= "<b>Телефон:</b> " . htmlspecialchars($lead['phone'], ENT_QUOTES, 'UTF-8') . "\n";
$newText .= "<b>Менеджер:</b> @" . htmlspecialchars($managerUsername, ENT_QUOTES, 'UTF-8');
// Обновляем сообщение в Telegram (удаляем кнопку и меняем текст)
// Используем метод editMessageText (подробнее в главе "Редактирование и удаление")
// Ссылка на главу: <a href="/telegram-bot-api/edit-delete">Редактирование и удаление</a>
telegramApi('editMessageText', [
'chat_id' => $chatId,
'message_id' => $messageId,
'text' => $newText,
'parse_mode' => 'HTML'
]);
// Подтверждаем обработку клика, чтобы убрать бесконечную анимацию загрузки на кнопке
telegramApi('answerCallbackQuery', [
'callback_query_id' => $callbackQueryId,
'text' => 'Вы успешно взяли заявку!'
]);
Важные нюансы безопасности и архитектуры
- Не используйте сессии PHP: Скрипт
submit.phpвызывается пользователем из браузера, аwebhook.phpвызывается серверами Telegram. У них абсолютно разные сессии PHP. Всегда передавайте состояние через базу данных и уникальный ID лида вcallback_data. - Защита формы: Бот отправляет всё, что придет в POST-запросе. Чтобы спамеры не завалили ваш чат мусором, обязательно защищайте форму на сайте с помощью CSRF-токенов и капчи (например, Cloudflare Turnstile или hCaptcha).
- Лимиты API: Telegram разрешает отправлять не более 30 сообщений в секунду. Если у вас на сайте идет мощный поток заявок, отправка напрямую в обработчике формы затормозит загрузку страницы для пользователя. В таких случаях отправку нужно выносить в фоновую очередь (например, через Redis или таблицу очередей в MySQL). Подробнее об этом читайте в главе Production: очередь и retries.
Дальше: Магазин в боте