How to Build a Telegram Bot with PHP (2026): Complete Guide
How to Build a Telegram Bot with PHP (2026): Complete Guide
PHP is an underrated choice for Telegram bots. It runs on virtually any shared hosting plan, deployment is as simple as uploading a file via FTP, and the Telegram Bot API is a plain JSON over HTTPS interface that PHP handles naturally with curl or file_get_contents. If you already have a PHP web hosting plan, you can have a working Telegram bot deployed in under an hour. This guide walks through everything: creating your bot, handling messages, building inline keyboards, configuring webhooks, and deploying to a live server. Find more developer resources in the Developer Tools category.
Prerequisites
- A Telegram account
- PHP 8.1+ (locally or on a server)
- A web server accessible via HTTPS (for webhooks) — shared hosting, VPS, or a tunneling tool like ngrok for local development
- Basic PHP knowledge
Step 1: Create Your Bot with @BotFather
Every Telegram bot begins with @BotFather — Telegram's official bot management interface.
- Open Telegram and search for @BotFather.
- Send
/newbot. - Choose a display name (e.g. "My PHP Bot").
- Choose a username — must be unique and end with "bot" (e.g.
myphp_bot). - BotFather returns your API token — a string like
1234567890:ABCdefGHIjklMNOpqrSTUvwxYZ. Store this securely.
Step 2: Understanding the Telegram Bot API
The Telegram Bot API is a REST-like HTTPS API. Every action — sending a message, getting updates, answering inline queries — is an HTTP request to:
https://api.telegram.org/bot{TOKEN}/{method}
Updates (incoming messages, callbacks, etc.) can be retrieved two ways:
- getUpdates (polling): Your script calls
getUpdatesrepeatedly to fetch new messages. Simple for development, not suitable for shared hosting (requires a persistent process). - Webhooks: Telegram POSTs JSON to your URL every time a new update arrives. Perfect for PHP on shared hosting — no persistent process required.
Step 3: A Minimal PHP Bot (Webhook)
Create a file called bot.php:
<?php
define('BOT_TOKEN', 'YOUR_TOKEN_HERE');
define('API_URL', 'https://api.telegram.org/bot' . BOT_TOKEN . '/');
// Read the incoming update
$input = file_get_contents('php://input');
$update = json_decode($input, true);
if (!$update) {
exit;
}
// Extract the message
$message = $update['message'] ?? null;
if (!$message) {
exit;
}
$chatId = $message['chat']['id'];
$text = $message['text'] ?? '';
// Route commands
if ($text === '/start') {
sendMessage($chatId, 'Hello! I am a PHP bot. Send /help for commands.');
} elseif ($text === '/help') {
sendMessage($chatId, "Commands:\n/start — Welcome message\n/help — This help text\n/echo [text] — Echo your text");
} elseif (str_starts_with($text, '/echo ')) {
$reply = substr($text, 6);
sendMessage($chatId, $reply);
} else {
sendMessage($chatId, 'Unknown command. Try /help');
}
// ── Helpers ───────────────────────────────────────────────────────────────
function sendMessage(int $chatId, string $text, array $extra = []): void
{
$payload = array_merge([
'chat_id' => $chatId,
'text' => $text,
'parse_mode' => 'HTML',
], $extra);
apiRequest('sendMessage', $payload);
}
function apiRequest(string $method, array $payload): array
{
$ch = curl_init(API_URL . $method);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($payload),
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 10,
]);
$response = curl_exec($ch);
curl_close($ch);
return json_decode($response, true) ?? [];
}
Step 4: Handling Inline Keyboards
Inline keyboards attach buttons directly to a message. Each button carries a callback_data string your bot receives when the user taps it.
// Send a message with an inline keyboard
function sendWithKeyboard(int $chatId, string $text, array $keyboard): void
{
sendMessage($chatId, $text, [
'reply_markup' => json_encode([
'inline_keyboard' => $keyboard,
]),
]);
}
// Example: send a yes/no prompt
sendWithKeyboard($chatId, 'Do you want to continue?', [
[
['text' => '✅ Yes', 'callback_data' => 'confirm_yes'],
['text' => '❌ No', 'callback_data' => 'confirm_no'],
],
]);
Handle callback queries (button taps) in your main update routing logic:
// At the top of bot.php, after decoding $update:
$callbackQuery = $update['callback_query'] ?? null;
if ($callbackQuery) {
$callbackChatId = $callbackQuery['message']['chat']['id'];
$data = $callbackQuery['data'];
$callbackId = $callbackQuery['id'];
// Always answer the callback query to stop the loading indicator
apiRequest('answerCallbackQuery', ['callback_query_id' => $callbackId]);
if ($data === 'confirm_yes') {
sendMessage($callbackChatId, 'Great, continuing!');
} elseif ($data === 'confirm_no') {
sendMessage($callbackChatId, 'Cancelled.');
}
exit;
}
Step 5: Sending Photos and Files
function sendPhoto(int $chatId, string $photoUrl, string $caption = ''): void
{
apiRequest('sendPhoto', [
'chat_id' => $chatId,
'photo' => $photoUrl, // URL or file_id
'caption' => $caption,
'parse_mode' => 'HTML',
]);
}
function sendDocument(int $chatId, string $filePath, string $caption = ''): void
{
// For local files, use multipart/form-data via curl
$ch = curl_init(API_URL . 'sendDocument');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => [
'chat_id' => $chatId,
'document' => new CURLFile($filePath),
'caption' => $caption,
],
CURLOPT_RETURNTRANSFER => true,
]);
curl_exec($ch);
curl_close($ch);
}
Step 6: Configuring Webhooks
Webhooks require an HTTPS endpoint. For production, your server needs a valid SSL certificate. For local development, use ngrok.
Setting the Webhook
Run this once to register your webhook URL with Telegram:
<?php
$token = 'YOUR_TOKEN';
$webhookUrl = 'https://yourdomain.com/bot.php';
$url = "https://api.telegram.org/bot{$token}/setWebhook?url=" . urlencode($webhookUrl);
echo file_get_contents($url);
On success, Telegram returns: {"ok":true,"result":true,"description":"Webhook was set"}
Verifying the Webhook
$url = "https://api.telegram.org/bot{$token}/getWebhookInfo";
$info = json_decode(file_get_contents($url), true);
print_r($info['result']);
Check that pending_update_count is 0 and last_error_message is empty. If there are errors, check your PHP error log — the most common issues are JSON parse errors in your bot code.
Security: Validate Incoming Requests
Anyone who knows your webhook URL can POST fake updates to it. Add a secret token to protect it:
// When setting the webhook, add a secret_token parameter:
$url = "https://api.telegram.org/bot{$token}/setWebhook"
. "?url=" . urlencode($webhookUrl)
. "&secret_token=YOUR_RANDOM_SECRET";
// In bot.php, verify the header:
$secret = $_SERVER['HTTP_X_TELEGRAM_BOT_API_SECRET_TOKEN'] ?? '';
if ($secret !== 'YOUR_RANDOM_SECRET') {
http_response_code(403);
exit;
}
Step 7: Deployment on Shared Hosting
PHP bots are one of the few types that deploy trivially on cheap shared hosting because webhooks require no persistent process:
- Upload
bot.phpto your hosting's public web root (e.g.public_html/bot/bot.php). - Ensure HTTPS is active. Most shared hosts provide free Let's Encrypt certificates via cPanel. Telegram requires HTTPS for webhooks.
- Set the webhook URL to
https://yourdomain.com/bot/bot.phpusing the setWebhook call above. - Test by messaging your bot. If nothing happens, check cPanel's Error Logs for PHP errors.
Useful .htaccess rule to block direct browser access to bot.php while allowing Telegram's POST requests:
<Files "bot.php">
<RequireAll>
Require method POST
</RequireAll>
</Files>
Using a PHP Telegram Bot Library
For larger bots, using a library avoids reinventing the wheel. The most popular PHP Telegram bot package:
composer require telegram-bot/api
<?php
require_once 'vendor/autoload.php';
$bot = new \TelegramBot\Api\BotApi('YOUR_TOKEN');
$update = \TelegramBot\Api\Types\Update::fromResponse(
json_decode(file_get_contents('php://input'), true)
);
$message = $update->getMessage();
if ($message && $message->getText() === '/start') {
$bot->sendMessage($message->getChat()->getId(), 'Hello from the library!');
}
The library handles type mapping, reduces boilerplate, and includes helpers for inline keyboards, file uploads, and other complex operations.
FAQ
Can I build a PHP Telegram bot on shared hosting without SSH?
Yes. You only need FTP/SFTP access to upload bot.php and a way to run the setWebhook HTTP call once (paste the URL in any browser, or use cPanel's built-in URL fetch tool). No SSH required.
How do I store user data for my PHP bot?
Use a MySQL database (available on virtually all shared hosting plans) for persistent user data. Connect via PDO: new PDO('mysql:host=localhost;dbname=botdb', $user, $pass). For simple key-value storage, SQLite works well without a separate database server.
My bot receives updates but does not respond. What is wrong?
Check these in order: (1) PHP error log for fatal errors or warnings, (2) the getWebhookInfo response for last_error_message, (3) verify json_decode($input, true) is not returning null (add error_log($input) temporarily to see raw incoming data), (4) ensure curl extension is enabled in your PHP configuration.
How do I handle conversation state (multi-step flows) in PHP?
PHP on shared hosting is stateless per request. Store the current step in a database keyed by chat_id. When an update arrives, read the stored step, handle the response for that step, then save the next step. A simple user_states table with chat_id and state columns is sufficient for most flows.
Can a PHP Telegram bot handle high traffic?
For moderate traffic (thousands of users, hundreds of messages per minute), PHP on a decent VPS handles this comfortably. For very high traffic (millions of users), PHP is not the bottleneck — database I/O is. Optimize your database queries and consider adding a Redis cache layer for session data. At extreme scale, an async framework (Python asyncio, Node.js) is more efficient, but PHP is sufficient for the vast majority of real-world bots.
Share this article