Projects
Website lead → Telegram
A practical guide on sending applications from your website to Telegram using PHP. Includes database setup, short ID generation, inline button delivery, and webhook processing.
One of the most common practical tasks for PHP developers is sending leads (requests) directly from a website to a company's Telegram chat or manager. This solves the problem of "lost letters" in spam and allows instant reaction to new orders.
In this chapter we will build an interactive system: a lead from the site is saved to the database, sent to managers' chat with a "Take it" button, and when clicked the lead status updates in the DB, and the Telegram message changes its appearance, showing who exactly took the lead.
Форма на сайте
↓
Обработчик PHP (submit.php) → Запись в БД (leads)
↓
Отправка в Telegram (sendMessage с Inline-кнопкой)
↓
Клик менеджера «Взять в работу»
↓
Webhook (webhook.php) → Обновление БД → Изменение сообщения (editMessageText)Step 1. Database and Lead Storage
To run the application, we'll need a table in the database. We'll use a unique string identifier for the lead instead of a regular auto-increment (ID 1, 2, 3). This protects data from competitor brute-forcing and saves space within the limit callback_data (which is limited to only 64 bytes in Telegram; long texts won't fit there, so a concise ID is the ideal solution).
Let's create a simple table 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
);Now let's write script submit.php, which receives a POST request from the website form, saves the lead to the database, and sends a notification to Telegram.
We use function telegramApi(), that we created in chapter Первый запрос: getMe. Here we include the file containing it (assuming it's placed in bot_lib.php) and call it — the full implementation of the cURL function is not duplicated here.
To send a message, we also need the managers' chat ID TELEGRAM_MANAGER_CHAT. How to get this ID (group or personal chat) is described in detail in chapter 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";
}Step 2. Analysis of Telegram API Response
After calling sendMessage, Telegram returns a JSON response. It's important to ensure that field ok equals true. To see what was actually received from the API, we wrote the response to file debug.log using json_encode. You can view the log in real-time in the terminal with the command:
tail -f debug.logHere is an example of a successful response that will be written to the 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" }
]
}
}If the request failed (for example, invalid chat_id or bot not added to the group), field ok will be false, and the error reason will be returned in field description:
{
"ok": false,
"error_code": 400,
"description": "Bad Request: chat not found"
}Step 3. Handling the 'Take it' Click on Webhook
When a manager presses the 'Take it' button, Telegram sends CallbackQuery to your Webhook. We need to accept this request, update the lead status in the database, modify the message text (remove the button and write who took the lead) and definitely call answerCallbackQuery so the loading animation disappears on the button for the user.
Read more about webhook configuration in chapter Webhook, and about working with buttons in callback_query.
We use function telegramApi(), that we created in chapter Первый запрос: getMe. Here we include it from file 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' => 'Вы успешно взяли заявку!'
]);
Important Security and Architecture Nuances
- Do not use PHP sessions: The script
submit.phpis called by the user from their browser, whilewebhook.phpis called by Telegram servers. They have completely different PHP sessions. Always pass state through the database and the unique lead ID tocallback_data. - Form protection: The bot sends everything that arrives in the POST request. To prevent spammers from flooding your chat with garbage, you must always protect the form on the website with CSRF tokens and a captcha (e.g., Cloudflare Turnstile or hCaptcha).
- API limits: Telegram allows sending no more than 30 messages per second. If you have a strong flow of leads on your site, sending directly to the form handler will slow down page loading for users. In such cases, sending needs to be offloaded to a background queue (e.g., via Redis or a queue table in MySQL). Read more in chapter Production: очередь и retries.
Next: Магазин в боте
"}