How to Build a Telegram Bot with PHP and AI (2026)

Building a Telegram bot with PHP is a practical way to deploy an AI assistant — users don’t need a separate app, and the Telegram Bot API is simple to call with plain cURL or Guzzle. This guide builds a fully working bot: webhook setup, message handling, conversation history, and AI replies via the OpenAI API.

For a Python-based Telegram bot, see How to Build a Telegram Bot with Python and AI. For the OpenAI PHP SDK, see OpenAI PHP SDK Guide.


Prerequisites

  • PHP 8.1+, Composer
  • OpenAI API key
  • A Telegram Bot Token from @BotFather
  • A publicly accessible HTTPS URL (for webhooks) — use ngrok for local dev

Step 1: Create a Bot with BotFather

Open Telegram → search @BotFather → send /newbot → follow prompts. You’ll receive a token like 7123456789:AAFxxxxxxxx. Save it as TELEGRAM_BOT_TOKEN.


Step 2: Install Dependencies

composer require openai-php/client guzzlehttp/guzzle

Guzzle is used to call the Telegram API. The OpenAI client handles AI responses.


Step 3: Project Structure

telegram-bot/
├── vendor/
├── webhook.php      # Entry point — Telegram sends updates here
├── bot.php          # Bot logic class
├── history.php      # Conversation history storage
└── .env             # Tokens

Step 4: Conversation History

Store per-user conversation history in JSON files (use Redis or a DB for production):

<?php
// history.php

class ConversationHistory
{
    private string $dir;

    public function __construct(string $storageDir = '/tmp/tg_history')
    {
        $this->dir = $storageDir;
        if (!is_dir($this->dir)) {
            mkdir($this->dir, 0755, true);
        }
    }

    public function get(int $userId): array
    {
        $file = "{$this->dir}/{$userId}.json";
        if (!file_exists($file)) {
            return [['role' => 'system', 'content' => 'You are a helpful assistant. Be concise.']];
        }
        return json_decode(file_get_contents($file), true) ?? [];
    }

    public function append(int $userId, string $role, string $content): void
    {
        $messages   = $this->get($userId);
        $messages[] = ['role' => $role, 'content' => $content];

        // Keep system prompt + last 20 messages
        if (count($messages) > 22) {
            $system   = array_shift($messages);
            $messages = array_merge([$system], array_slice($messages, -20));
        }

        file_put_contents("{$this->dir}/{$userId}.json", json_encode($messages));
    }

    public function clear(int $userId): void
    {
        @unlink("{$this->dir}/{$userId}.json");
    }
}

Step 5: The Bot Class

<?php
// bot.php

require 'vendor/autoload.php';
require 'history.php';

class TelegramAIBot
{
    private \GuzzleHttp\Client $http;
    private ConversationHistory $history;
    private string $tgToken;
    private string $apiBase;

    public function __construct()
    {
        $this->tgToken = getenv('TELEGRAM_BOT_TOKEN');
        $this->apiBase = "https://api.telegram.org/bot{$this->tgToken}/";
        $this->http    = new \GuzzleHttp\Client(['timeout' => 30]);
        $this->history = new ConversationHistory();
    }

    public function handle(array $update): void
    {
        $message = $update['message'] ?? $update['edited_message'] ?? null;
        if (!$message) return;

        $chatId = $message['chat']['id'];
        $userId = $message['from']['id'];
        $text   = trim($message['text'] ?? '');

        if ($text === '') return;

        // Handle commands
        if (str_starts_with($text, '/')) {
            $this->handleCommand($chatId, $userId, $text);
            return;
        }

        // Show typing indicator
        $this->sendChatAction($chatId, 'typing');

        // Get AI reply
        $reply = $this->getAIReply($userId, $text);

        $this->sendMessage($chatId, $reply);
    }

    private function handleCommand(int $chatId, int $userId, string $command): void
    {
        $cmd = explode(' ', $command)[0];

        match ($cmd) {
            '/start' => $this->sendMessage($chatId,
                "👋 Hello! I'm an AI assistant powered by OpenAI.\n\n" .
                "Ask me anything. Use /clear to reset the conversation."),
            '/help'  => $this->sendMessage($chatId,
                "Commands:\n/start — welcome message\n/clear — reset conversation\n/help — this message"),
            '/clear' => (function () use ($chatId, $userId) {
                $this->history->clear($userId);
                $this->sendMessage($chatId, '🗑 Conversation cleared. Starting fresh!');
            })(),
            default  => $this->sendMessage($chatId, "Unknown command. Use /help."),
        };
    }

    private function getAIReply(int $userId, string $input): string
    {
        $this->history->append($userId, 'user', $input);

        try {
            $client   = \OpenAI::client(getenv('OPENAI_API_KEY'));
            $response = $client->chat()->create([
                'model'      => 'gpt-4o-mini',
                'messages'   => $this->history->get($userId),
                'max_tokens' => 1024,
            ]);

            $reply = $response->choices[0]->message->content;
            $this->history->append($userId, 'assistant', $reply);
            return $reply;

        } catch (\Exception $e) {
            return 'Sorry, I encountered an error. Please try again.';
        }
    }

    public function sendMessage(int $chatId, string $text): void
    {
        $this->http->post($this->apiBase . 'sendMessage', [
            'json' => [
                'chat_id'    => $chatId,
                'text'       => $text,
                'parse_mode' => 'HTML',
            ],
        ]);
    }

    private function sendChatAction(int $chatId, string $action): void
    {
        $this->http->post($this->apiBase . 'sendChatAction', [
            'json' => ['chat_id' => $chatId, 'action' => $action],
        ]);
    }
}

Step 6: Webhook Entry Point

<?php
// webhook.php

require 'bot.php';

$update = json_decode(file_get_contents('php://input'), true);

if ($update) {
    $bot = new TelegramAIBot();
    $bot->handle($update);
}

http_response_code(200);
echo 'OK';

Step 7: Register the Webhook

Tell Telegram to send updates to your URL:

curl -X POST "https://api.telegram.org/bot<YOUR_TOKEN>/setWebhook" \
  -d "url=https://yourdomain.com/webhook.php"
# Response: {"ok":true,"result":true,"description":"Webhook was set"}

For local development with ngrok:

ngrok http 8000
# Copy the HTTPS URL, e.g. https://abc123.ngrok.io
# Then set: url=https://abc123.ngrok.io/webhook.php

Adding Inline Keyboards

Guide users with interactive buttons:

public function sendMenu(int $chatId): void
{
    $this->http->post($this->apiBase . 'sendMessage', [
        'json' => [
            'chat_id' => $chatId,
            'text'    => 'What would you like to do?',
            'reply_markup' => [
                'inline_keyboard' => [
                    [
                        ['text' => '📝 Summarize text', 'callback_data' => 'action_summarize'],
                        ['text' => '🌍 Translate',       'callback_data' => 'action_translate'],
                    ],
                    [
                        ['text' => '💡 Explain code',   'callback_data' => 'action_explain'],
                        ['text' => '🗑 Clear history',   'callback_data' => 'action_clear'],
                    ],
                ],
            ],
        ],
    ]);
}

// Handle callback queries in the handle() method:
$callback = $update['callback_query'] ?? null;
if ($callback) {
    $chatId = $callback['message']['chat']['id'];
    $userId = $callback['from']['id'];
    $data   = $callback['data'];

    // Answer callback to remove loading indicator
    $this->http->post($this->apiBase . 'answerCallbackQuery', [
        'json' => ['callback_query_id' => $callback['id']],
    ]);

    match ($data) {
        'action_clear'     => $this->history->clear($userId) ?: $this->sendMessage($chatId, 'Cleared!'),
        'action_summarize' => $this->sendMessage($chatId, 'Send me the text to summarize.'),
        default            => null,
    };
}

Summary

  • Register webhook with setWebhook — Telegram POSTs updates to your URL
  • Read the update from php://input in webhook.php
  • Store per-user conversation history in files/Redis; trim to last 20 messages
  • Send typing indicator with sendChatAction before long AI requests
  • Handle commands (/start, /clear) and inline keyboards for a polished UX
  • Always return HTTP 200 or Telegram will retry the update

Subscribe to my newsletter — practical guides on Claude API, AI agents, RAG, and automation.

Subscribe