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/guzzleGuzzle 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 # TokensStep 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.phpAdding 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://inputinwebhook.php - Store per-user conversation history in files/Redis; trim to last 20 messages
- Send typing indicator with
sendChatActionbefore 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.