Зіставлення t.me/Bot?start=payload з PHP: обмеження, → характер корисного навантаження та перевірка несанкціонованого доступу

Мапа t.me/Bot?start=payload на PHP

Глибока прив 'язка для ботів Telegram використовує єдиний шаблон URL-адреси:

https://t.me/<your_bot_username>?start=<payload>

Коли користувач відкриває його, а бот ще не запущений, Telegram відкриває чат і показує Старт. Після натискання ваш бот отримує Start (одне місце, потім корисне навантаження). Ви самі вирішуєте, що означає це корисне навантаження.

Цей посібник охоплює:

  • Практичні обмеження щодо Перейти корисне навантаження
  • Глибоке зв 'язування (намір холодного запуску) проти продовження діалогу в чаті
  • Рівень відображення PHP: створіть об 'єкт бази даних, вставте короткий ідентифікатор в URL-адресу, дозвольте його Перейти
  • Чому корисне навантаження не повинно ідентифікувати користувача і як зв 'язатися з ним message.from.id

Це не повний посібник з фреймворку бота, і він не охоплює міні-додаток initData або зворотні дзвінки на вбудовану клавіатуру.

URL-адреса та ліміт корисного навантаження

Start є параметром запиту. Telegram додає його до Перейти після одного пробілу. Обмеження виникають через довжину URL-адреси та те, як ви обробляєте рядок як ідентифікатор.

  1. Довжина URL-адреси. Утримуйте повну URL-адресу в межах кількох сотень символів, щоб клієнти електронної пошти та попередній перегляд не порушували її.
  2. Безпечна стеля. Поводьтеся з корисним навантаженням як з ідентифікатором з явним максимальним розміром, щоб ніхто не міг вставити довільний текст у ваш розпізнавач.

Тверде правило: тримайте корисне навантаження під 64 байта, ASCII, URL-safe - той самий бюджет, що й callback_data. bin2hex(random_bytes(7)) дає 14 шістнадцяткових символів і багато унікальних значень.

<?php
$shortId = bin2hex(random_bytes(7)); // 14 chars, [0-9a-f]
echo 'https://t.me/YourBot?start=' . $shortId;

Що таке глибинні посилання (і чим вони не є)

A Почати корисне навантаження намір холодного запуску Посилання може бути з браузера, електронної пошти, QR-коду або NFC-тегу. Можливо, користувач ніколи не розмовляв з вашим ботом. Telegram відкриває чат і чекає на Старт. Тільки тоді ви отримаєте Start.

Це відрізняється від продовження живого діалогу:

  • Усередині бота підтвердьте замовлення за допомогою InlineKeyboardMarkup і callback_query — обидві сторони вже відомі.
  • Якщо користувач вже запустив бота, Почати посилання все ще працює, але запускає новий холодний запуск, коли стан діалогу вже на вашому боці.

Використання Почати коли посилання повинно працювати для когось, хто прибуває з-за меж Telegram. Використовуйте callback_data коли обидві сторони вже ведуть розмову.

Шар відображення: спочатку об 'єкт, потім корисне навантаження

Не використовуйте корисне навантаження як первинний ключ. Спочатку створіть об 'єкт (купон, запрошення, магічне посилання), надайте йому внутрішній ідентифікатор, згенеруйте short_id, зберігайте його і кладіть тільки short_id в URL-адресі. Увімкнено Перейти, подивіться на рядок і пов 'яжіть його з користувачем Telegram, який відкрив чат.

<?php
$pdo = new PDO(
'mysql:host=127.0.0.1;dbname=shop;charset=utf8mb4',
getenv('DB_USER'),
getenv('DB_PASS'),
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
]
);

$shortId = bin2hex(random_bytes(7));

$stmt = $pdo->prepare(
'INSERT INTO coupons (short_id, kind, amount_cents, expires_at, status)
VALUES (:sid, :k, :a, :exp, :st)'
);
$stmt->execute([
':sid' => $shortId,
':k' => 'WELCOME10',
':a' => 1000,
':exp' => gmdate('Y-m-d H:i:s', time() + 7 * 86400),
':st' => 'pending',
]);

$url = 'https://t.me/YourShopBot?start=' . $shortId;
// email $url to the customer

Дозволити /запустити в PHP

Telegram доставляє Start як звичайне повідомлення. Відокремте перший пробіл, перевірте корисне навантаження, завантажте об 'єкт і зв' яжіть власника.

<?php
declare(strict_types=1);

$pdo = new PDO(
'mysql:host=127.0.0.1;dbname=shop;charset=utf8mb4',
getenv('DB_USER'),
getenv('DB_PASS'),
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
);

$raw = file_get_contents('php://input');
$update = json_decode($raw, true, 32, JSON_THROW_ON_ERROR);

$msg = $update['message'] ?? null;
if (!$msg || empty($msg['text'])) {
http_response_code(200);
exit;
}

$text = $msg['text'];
$parts = explode(' ', $text, 2);

if ($parts[0] !== '/start') {
http_response_code(200);
exit;
}

$payload = $parts[1] ?? '';

if ($payload === '' || strlen($payload) > 64 || !preg_match('/\A[A-Za-z0-9_\-]+\z/', $payload)) {
sendMessage($msg['chat']['id'], 'This link looks broken. Please request a new one.');
http_response_code(200);
exit;
}

$stmt = $pdo->prepare(
'SELECT id, kind, amount_cents, expires_at, status, owner_telegram_id
FROM coupons WHERE short_id = :sid LIMIT 1'
);
$stmt->execute([':sid' => $payload]);
$coupon = $stmt->fetch();

if (!$coupon) {
sendMessage($msg['chat']['id'], 'This coupon link is no longer valid.');
http_response_code(200);
exit;
}

if (strtotime($coupon['expires_at']) < time()) {
sendMessage($msg['chat']['id'], 'This coupon has expired.');
http_response_code(200);
exit;
}

$telegramId = (int) $msg['from']['id'];

$pdo->beginTransaction();
try {
if ($coupon['owner_telegram_id'] === null) {
$upd = $pdo->prepare(
'UPDATE coupons SET owner_telegram_id = :tid, status = :st
WHERE id = :id AND owner_telegram_id IS NULL'
);
$upd->execute([
':tid' => $telegramId,
':st' => 'claimed',
':id' => $coupon['id'],
]);
if ($upd->rowCount() === 0) {
$pdo->rollBack();
sendMessage($msg['chat']['id'], 'This coupon was just claimed by someone else.');
http_response_code(200);
exit;
}
sendMessage(
$msg['chat']['id'],
sprintf('Coupon claimed: %s, %d cents.', $coupon['kind'], $coupon['amount_cents'])
);
} elseif ((int) $coupon['owner_telegram_id'] === $telegramId) {
sendMessage($msg['chat']['id'], 'You already claimed this coupon.');
} else {
sendMessage($msg['chat']['id'], 'This coupon is not for your account.');
}
$pdo->commit();
} catch (Throwable $e) {
$pdo->rollBack();
error_log('start payload resolve failed: ' . $e->getMessage());
http_response_code(500);
exit;
}

http_response_code(200);

function sendMessage(int $chatId, string $text): void
{
$token = getenv('BOT_TOKEN');
$url = "https://api.telegram.org/bot{$token}/sendMessage";

$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_TIMEOUT => 10,
CURLOPT_POSTFIELDS => http_build_query([
'chat_id' => $chatId,
'text' => $text,
'parse_mode' => 'HTML',
]),
]);
$resp = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($resp === false || $code !== 200) {
error_log("sendMessage failed http={$code} body={$resp}");
}
curl_close($ch);
}

Дві частини, які слід зберегти:

  • Перевірте за допомогою сильного регулярного виразу, перш ніж торкатися бази даних.
  • ОНОВЛЕННЯ ... ДЕ OWNER_Telegram_ID NULL є захисником перегонів, тому виграє лише одна претензія.

Чому корисне навантаження не повинно ідентифікувати користувача

Кодування користувача в корисному навантаженні (наприклад, ?start=u_42 або електронною поштою) є небезпечним:

  1. Подделка. Будь-хто, хто вгадує інший ідентифікатор користувача, може відкрити посилання зі свого облікового запису Telegram. Ваш бот може зарахувати користувачеві 42, тоді як користувач 99 фактично натиснув Start. Виправлено: корисне навантаження називає об 'єкт; прив' язує право власності з message.from.id.
  2. Витоки Прості ідентифікатори проходять через джерела переходу, переадресацію електронних листів, знімки екрана та попередній перегляд посилань. Непрозорі короткі внутрішні діафрагми протікають рідше.

Telegram не підписує URL-адресу. Єдиною надійною ідентифікацією холодного запуску є message.from.id про отримане оновлення.

Виробничі примітки

  • Ідемпотентність Дедуплікація на update_id (БД або Redis ВСТАНОВИТИ NX), тому повторні спроби вебхука не потрібно повторювати двічі.
  • Таємний знак Установить секретный токен від setWebhook і відхилити невідповідні заголовки.
  • РАЗОМ Термін дії купонів, що очікують на розпізнавання, закінчується, а застарілі рядки в розпізнавальному пристрої відхиляються.
  • Ліміти ставок. Розімкніть будь-яку загальнодоступну кінцеву точку, яка буде Почати Посилання

Створіть об 'єкт, надішліть короткий ідентифікатор в URL-адресі, вирішіть Перейти по відношенню до бази даних, прив 'язати до message.from.id, і ніколи не дозволяйте корисному навантаженню називати ім 'я користувача.

Доставка ботів і гаджетів з такими глибокими посиланнями? Див. botservice.biz.

Нові статті — у Telegram

Розбираємо, що автоматизувати в бізнесі та як це працює на практиці. Без спаму.