Сопоставление t.me/Bot?start=payload на PHP: лимиты, → сущность полезной нагрузки и проверка несанкционированного доступа

Карта t.me/Bot?start=payload на PHP

Глубокая привязка для ботов Telegram использует один шаблон URL:

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

Когда пользователь открывает его, а бот еще не запущен, Telegram открывает чат и показывает Start. После нажатия ваш бот получает /start <payload> (одно место, затем полезная нагрузка). Вы сами решаете, что означает эта полезная нагрузка.

В этом руководстве рассматриваются:

  • Практические ограничения на ПУСК полезная нагрузка
  • Глубокое связывание (намерение холодного старта) против продолжения диалога в чате
  • Слой сопоставления PHP: создайте сущность БД, вставьте короткий идентификатор в URL-адрес, разрешите его ПУСК
  • Почему полезная нагрузка не должна идентифицировать пользователя и как привязать к message.from.id

Это не полное руководство по фреймворку бота, и оно не охватывает Mini App initData или обратные вызовы встроенной клавиатуры.

URL-адрес и лимит полезной нагрузки

?start=<payload> является параметром запроса. 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 <payload>.

Это отличается от продолжения живого диалога:

  • Внутри бота подтвердите заказы с помощью 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 <payload> как обычное сообщение. Разделите первый пробел, проверьте полезную нагрузку, загрузите объект и привяжите право собственности.

<?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);
}

Две детали, которые стоит сохранить:

  • Проверяйте с помощью строгого регулярного выражения, прежде чем касаться базы данных.
  • UPDATE ... WHERE owner_telegram_id IS NULL является охранником гонки, поэтому выигрывает только одна претензия.

Почему полезная нагрузка не должна идентифицировать пользователя

Кодирование пользователя в полезной нагрузке (например, ?start=u_42 или по электронной почте) небезопасно:

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

Telegram не подписывает URL. Единственная надежная идентификация при холодном запуске - это message.from.id о полученном обновлении.

Производственные заметки

  • Идемпотентность Дедупликация по update_id (DB или Redis SET NX), поэтому повторные попытки веб-перехватчика не требуют дважды.
  • Тайный знак Установить секретный токен от setWebhook и отклонить несовпадающие заголовки.
  • ИТОГО Истекает срок действия ожидающих купонов и отклоняются устаревшие строки в распознавателе.
  • Ограничения ставки. Дросселируйте любую публичную конечную точку, которая будет начать Ссылки

Создайте объект, отправьте короткий идентификатор в URL-адресе, решите ПУСК по отношению к БД, привязать к message.from.id, и никогда не позволяйте полезной нагрузке называть имя пользователя.

Доставка ботов и мини-приложений с такими глубокими ссылками? См. botservice.biz.

Новые статьи — в Telegram

Разбираем, что автоматизировать в бизнесе и как это работает на практике. Без спама.