Blog How to Build a Telegram Bot with PHP (2026): Complete Guide
Editorial

How to Build a Telegram Bot with PHP (2026): Complete Guide

Admin {{ $post->author->username }} 8 min read

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.

  1. Open Telegram and search for @BotFather.
  2. Send /newbot.
  3. Choose a display name (e.g. "My PHP Bot").
  4. Choose a username — must be unique and end with "bot" (e.g. myphp_bot).
  5. 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 getUpdates repeatedly 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:

  1. Upload bot.php to your hosting's public web root (e.g. public_html/bot/bot.php).
  2. Ensure HTTPS is active. Most shared hosts provide free Let's Encrypt certificates via cPanel. Telegram requires HTTPS for webhooks.
  3. Set the webhook URL to https://yourdomain.com/bot/bot.php using the setWebhook call above.
  4. 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

Share on X