ربات تلگرام
تنظیم وبهوک ربات تلگرام روی هاست cPanel و رفع خطاهای رایج
تنظیم وبهوک ربات تلگرام یعنی به تلگرام بگویید آپدیت های ربات را به کدام آدرس HTTPS روی هاست شما بفرستد. این کار با یک فایل PHP روی هاست و یک بار فراخوانی متد setWebhook انجام می شود، و getWebhookInfo نشان می دهد وبهوک درست کار می کند یا نه.
اگر سورس ربات را آپلود کرده اید و فقط لینک setWebhook را می خواهید، آموزش کوتاه setWebhook در مرکز آموزش پیکوهاست کافی است. برای تنظیم وبهوک ربات تلگرام به شکل امن، فایل دریافت کننده باید درخواست هایی را که secret_token درست ندارند رد کند. کد این فایل و جدول خطاهای رایج getWebhookInfo پایین تر آمده است. اگر هنوز هاست نخریده اید یا نمی دانید سورس شما با وبهوک کار می کند یا polling، اول راهنمای انتخاب هاست ربات تلگرام را بخوانید.
آنچه پیش از تنظیم وبهوک ربات تلگرام لازم دارید
برای تنظیم وبهوک به توکن ربات، یک دامنه یا ساب دامین با گواهی SSL معتبر و دسترسی به File Manager هاست نیاز دارید.
- توکن ربات را BotFather بعد از ساخت ربات به شما می دهد.
- رکورد A دامنه یا ساب دامین باید به IP هاست اشاره کند. تلگرام وبهوک را فقط به IPv4 می فرستد و IPv6 را پشتیبانی نمی کند.
- گواهی SSL باید روی همان دامنه نصب باشد. در cPanel از بخش SSL/TLS Status می توانید AutoSSL را اجرا کنید تا گواهی صادر یا تمدید شود.
- فایل ها را با File Manager می سازید. اگر با آن کار نکرده اید، راهنمای تصویری آپلود فایل در cPanel مراحل را نشان می دهد.
طبق راهنمای وبهوک تلگرام نام دامنه باید در CN یا SAN گواهی آمده باشد، زنجیره گواهی های میانی کامل باشد و سرور TLS 1.2 یا بالاتر را پشتیبانی کند. پورت هم باید یکی از ۴۴۳، ۸۰، ۸۸ یا ۸۴۴۳ باشد. روی هاست اشتراکی همان ۴۴۳ پیش فرض را استفاده کنید و پورتی در آدرس ننویسید. آدرسی که ثبت می کنید باید آدرس نهایی باشد، چون تلگرام پاسخ 301 یا 302 را خطا حساب می کند.
پیش از ادامه، گواهی را از ترمینال سیستم خودتان یا Terminal هاست امتحان کنید:
curl -sSI https://bot.example.com/
openssl s_client -tls1_2 -connect bot.example.com:443 -servername bot.example.com </dev/null
در خروجی curl نباید خطای گواهی ببینید و کد پاسخ نباید 301 یا 302 باشد. در خروجی openssl دنبال خط Verify return code: 0 (ok) بگردید.
ساخت فایل دریافت کننده وبهوک با PHP
فایل دریافت کننده یک اسکریپت PHP است که هدر secret را چک می کند، بدنه JSON درخواست تلگرام را می خواند و جواب می دهد. توکن و secret را داخل پوشه عمومی سایت نگذارید. در ساختار زیر آن ها بیرون از public_html می مانند و فقط فایل وبهوک از اینترنت در دسترس است:
/home/USERNAME/bot-private/config.php
/home/USERNAME/bot-private/logs/
/home/USERNAME/public_html/bot-7f3k9/webhook.php
اگر برای ربات ساب دامین ساخته اید، webhook.php را در Document Root همان ساب دامین بگذارید. مسیر Document Root را در بخش Domains در cPanel می بینید. USERNAME نام کاربری حساب cPanel شماست. پوشه logs را هم بسازید، وگرنه لاگی ثبت نمی شود.
فایل config.php:
<?php
return [
'token' => '123456789:AAxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
'secret' => 'put-a-long-random-string_here',
'log' => '/home/USERNAME/bot-private/logs/webhook.log',
];
برای secret یک رشته تصادفی بسازید، مثلاً با openssl rand -hex 32 یا bin2hex(random_bytes(32)) در PHP. تلگرام برای secret_token رشته ای ۱ تا ۲۵۶ کاراکتری می پذیرد که فقط حروف انگلیسی، عدد، زیرخط و خط تیره داشته باشد.
فایل webhook.php:
<?php
declare(strict_types=1);
$config = require '/home/USERNAME/bot-private/config.php';
// Reject anything that does not carry our secret token
$received = $_SERVER['HTTP_X_TELEGRAM_BOT_API_SECRET_TOKEN'] ?? '';
if (!hash_equals($config['secret'], $received)) {
http_response_code(403);
exit;
}
$raw = file_get_contents('php://input');
$update = json_decode($raw, true);
if (!is_array($update)) {
http_response_code(400);
exit;
}
file_put_contents($config['log'], date('c') . ' ' . $raw . PHP_EOL, FILE_APPEND | LOCK_EX);
try {
$message = $update['message'] ?? null;
if (is_array($message) && isset($message['text'])) {
// Reply inside the webhook response; no extra request to the API
header('Content-Type: application/json');
echo json_encode([
'method' => 'sendMessage',
'chat_id' => $message['chat']['id'],
'text' => 'پیام شما رسید: ' . $message['text'],
], JSON_UNESCAPED_UNICODE);
}
} catch (Throwable $e) {
error_log('telegram bot: ' . $e->getMessage());
}
وقتی secret_token ثبت شده باشد، تلگرام آن را در هدر X-Telegram-Bot-Api-Secret-Token هر درخواست می فرستد و PHP این هدر را با نام HTTP_X_TELEGRAM_BOT_API_SECRET_TOKEN در $_SERVER قرار می دهد. تابع hash_equals دو رشته را در زمان ثابت مقایسه می کند تا نشود secret را با اندازه گیری زمان پاسخ حدس زد.
این کد جواب ربات را داخل بدنه پاسخ وبهوک برمی گرداند. مستندات Bot API این کار را مجاز می داند: نام متد را در فیلد method می نویسید و تلگرام آن را اجرا می کند. در این روش از نتیجه اجرا باخبر نمی شوید، پس برای کارهایی مثل گرفتن message_id پیام ارسال شده باید متد API را مستقیم با curl صدا بزنید. اگر اسکریپت هیچ خروجی چاپ نکند، PHP کد 200 برمی گرداند و تلگرام تحویل را موفق حساب می کند.
ثبت وبهوک با setWebhook
وبهوک با یک بار فراخوانی متد setWebhook ثبت می شود. آدرس HTTPS فایل وبهوک را در پارامتر url و secret را در secret_token می فرستید. این درخواست را می توانید از مرورگر، با curl یا با یک فایل PHP بفرستید.
از مرورگر
<TOKEN> و <SECRET> را با مقدارهای خودتان عوض کنید و کلمه bot پیش از توکن را پاک نکنید:
https://api.telegram.org/bot<TOKEN>/setWebhook?url=https://bot.example.com/bot-7f3k9/webhook.php&secret_token=<SECRET>&max_connections=10&drop_pending_updates=true
اگر همه چیز درست باشد این پاسخ را می بینید:
{"ok":true,"result":true,"description":"Webhook was set"}
در ایران api.telegram.org بدون فیلترشکن باز نمی شود. توکن و secret هم در تاریخچه مرورگر می مانند، پس روی سیستم مشترک یکی از دو روش بعدی را انتخاب کنید.
با curl
اگر هاست بیرون از ایران است و Terminal دارد، درخواست را از خود هاست بفرستید تا فیلترشکن لازم نباشد:
TOKEN='123456789:AAxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
SECRET='put-a-long-random-string_here'
curl -s "https://api.telegram.org/bot${TOKEN}/setWebhook" \
--data-urlencode "url=https://bot.example.com/bot-7f3k9/webhook.php" \
--data-urlencode "secret_token=${SECRET}" \
--data-urlencode 'allowed_updates=["message","callback_query"]' \
-d "max_connections=10" \
-d "drop_pending_updates=true"
این دستورها توکن و secret را در تاریخچه shell ذخیره می کنند. بعد از اجرا، تاریخچه همان نشست را با history -c پاک کنید.
با یک فایل PHP یک بارمصرف
اگر Terminal ندارید، این فایل را با نام set-webhook.php کنار webhook.php بسازید، یک بار در مرورگر باز کنید و بلافاصله پاکش کنید. توکن را از config.php می خواند، ولی تا وقتی روی هاست بماند، هر کسی آدرسش را بداند می تواند اجرایش کند و صف آپدیت ها را خالی کند:
<?php
$config = require '/home/USERNAME/bot-private/config.php';
$ch = curl_init("https://api.telegram.org/bot{$config['token']}/setWebhook");
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POSTFIELDS => [
'url' => 'https://bot.example.com/bot-7f3k9/webhook.php',
'secret_token' => $config['secret'],
'allowed_updates' => json_encode(['message', 'callback_query']),
'max_connections' => '10',
'drop_pending_updates' => 'true',
],
]);
echo curl_exec($ch);
پارامترهای setWebhook
| پارامتر | مقدار | کاربرد |
|---|---|---|
url | آدرس HTTPS فایل وبهوک | رشته خالی وبهوک را حذف می کند |
secret_token | ۱ تا ۲۵۶ کاراکتر از A-Z، a-z، 0-9، _ و - | در هدر X-Telegram-Bot-Api-Secret-Token هر درخواست فرستاده می شود |
max_connections | ۱ تا ۱۰۰، پیش فرض ۴۰ | سقف اتصال هم زمان تلگرام به وبهوک |
allowed_updates | آرایه JSON از نوع آپدیت ها | با فهرست خالی همه نوع ها به جز chat_member، message_reaction و message_reaction_count می آیند |
drop_pending_updates | true | آپدیت هایی را که در صف مانده اند دور می ریزد |
ip_address | یک IP ثابت | تلگرام به جای IP حاصل از DNS به این IP وصل می شود |
certificate | فایل گواهی عمومی | فقط برای گواهی self-signed؛ با SSL معتبر لازم نیست |
روی هاست اشتراکی max_connections را پایین نگه دارید. طبق مستندات، عدد کمتر بار سرور را محدود می کند و عدد بیشتر ظرفیت پردازش ربات را بالا می برد. روی هاست CloudLinux هر درخواست هم زمان یک Entry Process از سهم حساب می گیرد و وقتی این سهم پر شود، سرور خطای 508 برمی گرداند.
درباره allowed_updates دو نکته در مستندات هست. اگر در فراخوانی بعدی setWebhook آن را نفرستید، تنظیم قبلی باقی می ماند. تغییرش هم روی آپدیت هایی که پیش از فراخوانی ساخته شده اند اثر ندارد، پس ممکن است تا مدت کوتاهی آپدیت ناخواسته برسد.
بررسی وضعیت وبهوک با getWebhookInfo
متد getWebhookInfo نشان می دهد وبهوک روی چه آدرسی ثبت است، چند آپدیت در صف مانده و آخرین خطای تحویل چه بوده است. این متد پارامتری نمی خواهد و از مرورگر هم باز می شود:
https://api.telegram.org/bot<TOKEN>/getWebhookInfo
خروجی برای وبهوکی که مشکل دارد چیزی شبیه این است:
{
"ok": true,
"result": {
"url": "https://bot.example.com/bot-7f3k9/webhook.php",
"has_custom_certificate": false,
"pending_update_count": 12,
"ip_address": "203.0.113.10",
"last_error_date": 1789344000,
"last_error_message": "Wrong response from the webhook: 404 Not Found",
"max_connections": 10,
"allowed_updates": ["message", "callback_query"]
}
}
| فیلد | معنی | به چه چیزی نگاه کنید |
|---|---|---|
url | آدرس فعلی وبهوک | خالی یعنی وبهوکی ثبت نیست |
pending_update_count | آپدیت هایی که هنوز تحویل نشده اند | عدد رو به افزایش یعنی وبهوک جواب درست نمی دهد |
last_error_date | زمان آخرین خطا به Unix time | اگر به چند ساعت پیش برمی گردد، ممکن است مشکل حل شده باشد |
last_error_message | متن آخرین خطای تحویل | جدول بخش بعد |
last_synchronization_error_date | آخرین خطای هماهنگی آپدیت ها با دیتاسنترهای تلگرام | به هاست شما مربوط نیست |
ip_address | IP که تلگرام به آن وصل می شود | باید IP هاست باشد؛ اگر دامنه پشت CDN است، IP همان CDN را می بینید |
max_connections و allowed_updates | تنظیمات فعلی | با مقدارهایی که فرستاده اید مقایسه کنید |
تلگرام آپدیت های تحویل نشده را حداکثر ۲۴ ساعت نگه می دارد. اگر وبهوک بیش از یک روز خراب بماند، آپدیت های قدیمی تر از دست می روند.
خطاهای وبهوک تلگرام و راه حل آن ها
خطاهایی که هنگام تنظیم وبهوک ربات تلگرام یا بعد از آن می بینید دو دسته اند. دسته اول همان لحظه در پاسخ setWebhook برمی گردند و معمولاً به توکن، آدرس یا پورت مربوط اند. دسته دوم بعد از ثبت وبهوک، هنگام تحویل آپدیت ها، در last_error_message ثبت می شوند و علتشان SSL، ریدایرکت، مسیر فایل، خطای PHP یا کندی اسکریپت است.
خطاهایی که setWebhook برمی گرداند
پاسخ ناموفق setWebhook مقدار "ok":false دارد و علت در فیلد description نوشته می شود:
| description | علت | راه حل |
|---|---|---|
Unauthorized | توکن اشتباه است یا باطل شده | توکن را دوباره از BotFather کپی کنید |
Not Found | ساختار آدرس API اشتباه است، مثلاً bot پیش از توکن نیامده | آدرس باید به شکل /bot<TOKEN>/setWebhook باشد |
Bad Request: bad webhook: An HTTPS URL must be provided for webhook | آدرس با http شروع شده | آدرس را با https بنویسید |
Bad Request: bad webhook: Webhook can be set up only on ports 80, 88, 443 or 8443 | پورت غیرمجاز در آدرس | پورت را از آدرس بردارید |
Bad Request: bad webhook: Failed to resolve host: Name or service not known | دامنه به IP نمی رسد یا اشتباه تایپ شده | رکورد A را چک کنید و بعد از انتشار DNS دوباره امتحان کنید |
خطاهایی که در last_error_message می بینید
| last_error_message | علت محتمل | راه حل |
|---|---|---|
SSL error {...} | گواهی منقضی شده، با نام دامنه یکی نیست، زنجیره میانی ناقص است یا self-signed است | AutoSSL را در cPanel اجرا کنید و گواهی را با دستور openssl بالا دوباره امتحان کنید |
Wrong response from the webhook: 301 Moved Permanently یا 302 Found | ریدایرکت http به https، www، اسلش آخر آدرس یا قانونی در .htaccess | آدرس نهایی را ثبت کنید یا مسیر وبهوک را از قانون ریدایرکت مستثنا کنید |
Wrong response from the webhook: 403 Forbidden | secret در setWebhook با config.php یکی نیست، یا فایروال وب سرور درخواست را بسته | secret را در هر دو جا یکسان کنید و setWebhook را دوباره اجرا کنید؛ اگر خطا ماند، از پشتیبانی بخواهید لاگ فایروال را برای این آدرس بررسی کند |
Wrong response from the webhook: 404 Not Found | مسیر یا نام فایل اشتباه است؛ در لینوکس حروف بزرگ و کوچک فرق دارند | آدرس را در مرورگر باز کنید؛ پاسخ 403 یعنی فایل پیدا شده است |
Wrong response from the webhook: 500 Internal Server Error | خطای PHP، ناسازگاری نسخه PHP با سورس یا مسیر اشتباه config.php | فایل error_log کنار اسکریپت یا بخش Errors در cPanel را بخوانید؛ متن آن دقیق ترین سرنخ است |
| پاسخ 508 | پر شدن سهم Entry Processes حساب | max_connections را کم کنید و کار سنگین را از وبهوک بیرون بیاورید |
Read timeout expired | اسکریپت دیر جواب می دهد | جواب را زود برگردانید و کارهای طولانی مثل ارسال همگانی را به کران بسپارید |
Connection timed out یا Connection refused | درخواست تلگرام به سرور نمی رسد | ip_address در getWebhookInfo را با IP هاست مقایسه کنید و بررسی کنید فایروال بازه های IP تلگرام را نبسته باشد |
امن کردن آدرس وبهوک
بدون secret_token هر کسی که آدرس فایل وبهوک را پیدا کند، می تواند یک آپدیت ساختگی با شناسه کاربر دیگری به آن POST کند. اگر ربات دستورهای ادمین را بر اساس همین شناسه اجرا کند، آن دستورها هم اجرا می شوند. با secret فعال، درخواستی که هدر درست ندارد با 403 رد می شود.
چند کار دیگر هم ریسک را کم می کند:
- نام پوشه وبهوک را غیرقابل حدس بگذارید و توکن را در آدرس یا نام فایل نیاورید.
- فایل تنظیمات و لاگ را بیرون از
public_htmlنگه دارید. لاگ آپدیت ها شامل پیام ها و شناسه کاربران است. - فایل
set-webhook.phpو هر فایل آزمایشی دیگر را بعد از استفاده پاک کنید. - اگر خواستید دسترسی را به IPهای تلگرام محدود کنید، تلگرام در حال حاضر از بازه های
149.154.160.0/20و91.108.4.0/22درخواست می فرستد. خودش هشدار داده که این بازه ها ممکن است عوض شوند، پس این محدودیت را فقط کنار secret_token به کار ببرید. - اگر توکن لو رفت، از BotFather توکن را revoke کنید، توکن جدید را در
config.phpبگذارید و setWebhook را دوباره اجرا کنید.
تست دستی و لاگ وبهوک
لازم نیست برای هر تست به ربات پیام بدهید. با curl می توانید درخواست تلگرام را شبیه سازی کنید:
curl -i -X POST "https://bot.example.com/bot-7f3k9/webhook.php" \
-H "Content-Type: application/json" \
-H "X-Telegram-Bot-Api-Secret-Token: put-a-long-random-string_here" \
-d '{"update_id":1,"message":{"message_id":1,"date":1789344000,"chat":{"id":123456789,"type":"private"},"text":"test"}}'
پاسخ باید کد 200 و یک بدنه JSON با "method":"sendMessage" داشته باشد. همین دستور را بدون خط هدر secret اجرا کنید؛ این بار باید 403 بگیرید. اگر باز هم 200 گرفتید، شرط secret در کد کار نمی کند.
لاگ را با File Manager باز کنید، یا اگر Terminal دارید، آخرین آپدیت ها را با tail -n 20 /home/USERNAME/bot-private/logs/webhook.log ببینید. این فایل با هر پیام بزرگ تر می شود. اگر این چند خط را در webhook.php بعد از خط require بگذارید، لاگ در ۵ مگابایت کنار گذاشته می شود و فضا پر نمی شود:
if (is_file($config['log']) && filesize($config['log']) > 5 * 1024 * 1024) {
rename($config['log'], $config['log'] . '.old');
}
حذف وبهوک و برگشت به getUpdates
برای برگشت به polling، وبهوک را با متد deleteWebhook حذف کنید:
https://api.telegram.org/bot<TOKEN>/deleteWebhook?drop_pending_updates=true
پاسخ موفق {"ok":true,"result":true,"description":"Webhook was deleted"} است. اگر drop_pending_updates را نفرستید، آپدیت های مانده در صف حذف نمی شوند و اولین فراخوانی getUpdates آن ها را تحویل می گیرد. تا وقتی وبهوک فعال است، getUpdates با خطای Conflict: can't use getUpdates method while webhook is active; use deleteWebhook to delete the webhook first جواب می دهد.
برعکسش هم صدق می کند: پیش از ثبت وبهوک پردازه polling را متوقف کنید، وگرنه پشت سر هم خطای Conflict می گیرد. برای انتقال ربات به هاست یا دامنه دیگر لازم نیست وبهوک را حذف کنید، چون setWebhook با آدرس جدید جای آدرس قبلی را می گیرد.
بعد از فعال شدن وبهوک
به ربات /start بفرستید و getWebhookInfo را یک بار دیگر باز کنید. pending_update_count باید صفر باشد و last_error_date تازه ای ثبت نشده باشد. کارهای زمان بندی شده ربات را داخل webhook.php اجرا نکنید و برایشان کران جاب در cPanel بسازید.
هاست ربات تلگرام پیکوهاست cPanel، SSL رایگان و امکان تنظیم نسخه PHP دارد و در لوکیشن هایی مثل هاست ربات تلگرام آلمان ارائه می شود. اگر هاست ربات در ایران است، پیش از ثبت وبهوک مقایسه لوکیشن های اروپا و ایران برای ربات را ببینید. رباتی که به جای Bot API با MadelineProto کار می کند روند راه اندازی دیگری دارد؛ اگر هنوز بین این دو انتخاب نکرده اید مقایسه MadelineProto و Bot API را بخوانید و مراحل نصب را در آموزش نصب MadelineProto روی هاست cPanel ببینید. برای ربات پایتون هم راه اندازی جنگو و ربات تلگرام روی هاست پایتون را بخوانید.