ربات تلگرام

تنظیم وبهوک ربات تلگرام روی هاست 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_updatestrueآپدیت هایی را که در صف مانده اند دور می ریزد
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_addressIP که تلگرام به آن وصل می شودباید 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 Forbiddensecret در 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 ببینید. برای ربات پایتون هم راه اندازی جنگو و ربات تلگرام روی هاست پایتون را بخوانید.