Карта 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-адреса и того, как вы обрабатываете строку в качестве идентификатора.
- Длина URL-адреса. Держите полный URL-адрес в пределах нескольких сотен символов, чтобы клиенты электронной почты и предварительные просмотры не нарушали его.
- Безопасный потолок. Относитесь к полезной нагрузке как к идентификатору с явным максимальным размером, чтобы никто не мог вставлять произвольный текст в ваш распознаватель.
Твердое правило: держите полезную нагрузку под 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 или по электронной почте) небезопасно:
- Подделка Любой, кто угадает другой идентификатор пользователя, может открыть ссылку со своего аккаунта в Telegram. Ваш бот может кредитовать пользователя 42, в то время как пользователь 99 фактически нажал Start. Исправлено: полезная нагрузка называет объект; привязывает право собственности из
message.from.id. - Утечки Простые идентификаторы проходят через рефереров, переадресацию электронной почты, скриншоты и предварительный просмотр ссылок. Непрозрачные короткие внутренние диафрагмы протекают реже.
Telegram не подписывает URL. Единственная надежная идентификация при холодном запуске - это message.from.id о полученном обновлении.
Производственные заметки
- Идемпотентность Дедупликация по
update_id(DB или RedisSET NX), поэтому повторные попытки веб-перехватчика не требуют дважды. - Тайный знак Установить
секретный токенотsetWebhookи отклонить несовпадающие заголовки. - ИТОГО Истекает срок действия ожидающих купонов и отклоняются устаревшие строки в распознавателе.
- Ограничения ставки. Дросселируйте любую публичную конечную точку, которая будет
начатьСсылки
Создайте объект, отправьте короткий идентификатор в URL-адресе, решите ПУСК по отношению к БД, привязать к message.from.id, и никогда не позволяйте полезной нагрузке называть имя пользователя.
Доставка ботов и мини-приложений с такими глубокими ссылками? См. botservice.biz.