Building a Telegram bot that actually does something useful — like pushing Bitcoin price alerts to subscribers — sounds simple until you hit webhook SSL errors, silent update failures, and rate limits. This guide walks through building a production-ready Laravel Telegram bot with the irazasyed/telegram-bot-sdk package, using a real use case: a crypto tips and Bitcoin trading alert bot.
By the end you’ll have a working Telegram bot Laravel 13 project that handles commands, stores subscribers, fetches live price data, and sends scheduled alerts via queues.
Why Laravel 13 + irazasyed/telegram-bot-sdk
You can talk to the Telegram Bot API with raw cURL, but you’ll reinvent update parsing, keyboard builders, and error handling. The Telegram bot SDK PHP package irazasyed/telegram-bot-sdk wraps the entire Bot API with a clean facade, ships a Laravel service provider, and integrates with config publishing, DI, and Laravel’s HTTP client.
Laravel 13 gives you first-class task scheduling and queues — exactly what a broadcast-heavy crypto bot needs. Combine the two and you get a maintainable Laravel Telegram bot tutorial stack that scales past a toy project.
1. Prerequisites
- PHP 8.2+ (Laravel 13 requires it)
- Composer 2.x
- A fresh Laravel 13 app:
composer create-project laravel/laravel crypto-bot - A public HTTPS endpoint for webhooks (ngrok works in development)
Creating the bot with BotFather
Open Telegram, search for @BotFather, and send /newbot. Choose a display name and a unique username ending in bot. BotFather returns an API token that looks like 123456789:AAH...xyz. Treat this token like a database password — anyone with it controls your bot.
2. Installing irazasyed/telegram-bot-sdk
Pull in the package:
composer require irazasyed/telegram-bot-sdk
Publish the config file:
php artisan vendor:publish --tag="telegram-config"
Add your token to .env:
TELEGRAM_BOT_TOKEN=123456789:AAH...xyz
Gotcha: if vendor:publish shows no matching tag, run php artisan config:clear and confirm the service provider auto-discovered. On Laravel 13 auto-discovery is automatic, but a stale bootstrap cache can hide it — php artisan optimize:clear fixes that.
3. Basic Bot Setup
Open config/telegram.php and confirm the default bot reads the token from env:
'bots' => [
'mybot' => [
'token' => env('TELEGRAM_BOT_TOKEN'),
],
],
'default' => 'mybot',
Test the connection with a quick tinker session using getMe():
php artisan tinker
>>> Telegram\Bot\Laravel\Facades\Telegram::getMe();
A successful call returns a User object with your bot’s id and username. If you get a TelegramSDKException: Bot token not provided, your .env value isn’t loading — run php artisan config:clear and try again.
4. Setting Up the Webhook
Webhook vs long polling
Long polling (getUpdates) is fine for local testing, but for production you want a Laravel Telegram webhook: Telegram pushes each update to your HTTPS URL instantly, no daemon required. Telegram requires a valid HTTPS certificate — self-signed works only if you upload the cert, so a real TLS cert (Let’s Encrypt) is the path of least resistance.
Creating the webhook route and controller
Add a POST route in routes/web.php. Note: Telegram won’t send a CSRF token, so exclude this route from CSRF verification (in Laravel 13, add the URI to the validateCsrfTokens exception list in bootstrap/app.php).
use App\Http\Controllers\TelegramWebhookController;
Route::post('/telegram/webhook/{token}', [TelegramWebhookController::class, 'handle']);
Including a secret {token} segment in the path stops random bots from hitting the endpoint. Now register the webhook with Telegram:
>>> Telegram\Bot\Laravel\Facades\Telegram::setWebhook([
'url' => 'https://yourdomain.com/telegram/webhook/YOUR_SECRET'
]);
Verify with Telegram::getWebhookInfo(). The last_error_message field is your best friend when things break — it usually tells you exactly why Telegram couldn’t reach you.
5. Handling Commands and Messages
The controller receives the update, parses it, and replies. Here’s a minimal handler covering /start and /help:
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Telegram\Bot\Laravel\Facades\Telegram;
class TelegramWebhookController extends Controller
{
public function handle(Request $request, string $token)
{
abort_unless($token === config('services.telegram.secret'), 403);
$update = Telegram::getWebhookUpdate();
$message = $update->getMessage();
$chatId = $message->chat->id;
$text = $message->text ?? '';
match (true) {
str_starts_with($text, '/start') => $this->onStart($chatId),
str_starts_with($text, '/help') => $this->onHelp($chatId),
str_starts_with($text, '/price') => $this->onPrice($chatId),
default => Telegram::sendMessage([
'chat_id' => $chatId,
'text' => 'Unknown command. Try /help',
]),
};
return response()->json(['ok' => true]);
}
private function onStart(int $chatId): void
{
Telegram::sendMessage([
'chat_id' => $chatId,
'text' => 'Welcome to CryptoTips! Use /price for the latest BTC price.',
]);
}
}
Always return a 200 response quickly. If your handler is slow or errors out, Telegram retries the update, and you’ll get duplicate messages. Offload heavy work to queues (covered below).
Inline buttons and keyboards
Give users tappable options with an inline keyboard:
use Telegram\Bot\Keyboard\Keyboard;
$keyboard = Keyboard::make()
->inline()
->row([
Keyboard::inlineButton(['text' => 'BTC Price', 'callback_data' => 'price_btc']),
Keyboard::inlineButton(['text' => 'Subscribe', 'callback_data' => 'sub_alerts']),
]);
Telegram::sendMessage([
'chat_id' => $chatId,
'text' => 'Choose an option:',
'reply_markup' => $keyboard,
]);
Callback button presses arrive as callback_query updates — check $update->getCallbackQuery() and answer them with Telegram::answerCallbackQuery() so the loading spinner clears on the user’s device.
6. Practical Example: Bitcoin Trading Bot
Fetching live price data
Use a free market API such as CoinGecko. Laravel’s HTTP client keeps this clean:
use Illuminate\Support\Facades\Http;
class PriceService
{
public function btcUsd(): float
{
$response = Http::retry(3, 200)
->get('https://api.coingecko.com/api/v3/simple/price', [
'ids' => 'bitcoin',
'vs_currencies' => 'usd',
]);
return (float) $response->json('bitcoin.usd');
}
}
Wire it into the /price command so users get an on-demand quote. This is the core of a useful PHP Telegram bot example — real data, delivered instantly.
Scheduled crypto tips
Broadcast a daily tip using Laravel Task Scheduling. Create a command SendDailyTip and register it in routes/console.php:
use Illuminate\Support\Facades\Schedule;
Schedule::command('bot:daily-tip')->dailyAt('09:00');
Schedule::command('bot:check-alerts')->everyFiveMinutes();
Make sure your cron runs the scheduler on the server:
* * * * * cd /var/www/crypto-bot && php artisan schedule:run >> /dev/null 2>&1
Price threshold alerts
The bot:check-alerts command compares the current price against each subscriber’s target and pushes an alert when the threshold is crossed. Store the last notified price to avoid spamming users every five minutes.
7. Storing User Data
Persist subscribers with an Eloquent model so you can broadcast and manage preferences. Create a migration:
Schema::create('subscribers', function (Blueprint $table) {
$table->id();
$table->bigInteger('chat_id')->unique();
$table->string('username')->nullable();
$table->decimal('alert_above', 20, 2)->nullable();
$table->decimal('alert_below', 20, 2)->nullable();
$table->boolean('daily_tips')->default(true);
$table->timestamps();
});
On /start, upsert the subscriber:
Subscriber::updateOrCreate(
['chat_id' => $chatId],
['username' => $message->from->username]
);
Storing chat_id as the unique key is essential — it’s how Telegram identifies where to deliver every future message.
8. Error Handling and Logging
The SDK throws Telegram\Bot\Exceptions\TelegramResponseException when the API rejects a call — a common cause is trying to message a user who blocked the bot (403 Forbidden: bot was blocked). Catch it and clean up:
use Telegram\Bot\Exceptions\TelegramResponseException;
try {
Telegram::sendMessage(['chat_id' => $chatId, 'text' => $text]);
} catch (TelegramResponseException $e) {
Log::warning('Telegram send failed', [
'chat_id' => $chatId,
'error' => $e->getMessage(),
]);
if (str_contains($e->getMessage(), 'blocked')) {
Subscriber::where('chat_id', $chatId)->delete();
}
}
Wrap the webhook controller in a try/catch and log the raw payload on failure. When updates silently vanish, that log is the first place to look.
9. Deployment
In production, set the webhook once against your live HTTPS domain and confirm with getWebhookInfo(). Common deployment mistakes:
- SSL chain incomplete — Telegram is strict. Test with
curl -v https://yourdomain.comand make sure the full certificate chain is served. - Route excluded incorrectly from CSRF — a 419 response means Telegram’s POST is being rejected.
- Blocking broadcasts — sending to thousands of subscribers synchronously will time out.
Queue workers for async sending
Dispatch each outbound message as a queued job so broadcasts don’t block the request or the scheduler:
class SendTelegramMessage implements ShouldQueue
{
use Queueable;
public function __construct(public int $chatId, public string $text) {}
public function handle(): void
{
Telegram::sendMessage([
'chat_id' => $this->chatId,
'text' => $this->text,
]);
}
}
Run a worker under Supervisor: php artisan queue:work --tries=3 --backoff=5. The --backoff flag spaces out retries, which helps you respect Telegram’s rate limit of roughly 30 messages per second for broadcasts.
10. Conclusion
You now know how to create a Telegram bot with Laravel 13 end to end: install irazasyed/telegram-bot-sdk, wire a secure webhook, handle commands, pull live Bitcoin data, store subscribers, and broadcast alerts through queues. From here, add payment integration (Telegram Payments or Stripe) for premium tiers, richer crypto analytics, or multi-coin support. The architecture scales cleanly because the heavy lifting lives in queues and scheduled commands.
FAQ
Why is my Telegram webhook not firing?
Call Telegram::getWebhookInfo() and read last_error_message. The usual culprits are an incomplete SSL chain, a 419 CSRF rejection, or your controller returning a non-200 status. Fix the response first, then re-set the webhook.
Do I need HTTPS for a Laravel Telegram bot?
Yes. Telegram only delivers webhook updates to HTTPS endpoints with a valid certificate. Use Let’s Encrypt in production; in development, ngrok provides a trusted HTTPS tunnel to your local app.
How do I avoid Telegram rate limits when broadcasting?
Send messages through a queue instead of a loop, keep broadcasts under ~30 messages per second, and add retry backoff. Remove subscribers who blocked the bot to avoid wasted, failing requests.
Can I use webhook and long polling at the same time?
No. Setting a webhook disables getUpdates. To switch back to long polling for local debugging, call Telegram::deleteWebhook() first.
Is irazasyed/telegram-bot-sdk compatible with Laravel 13?
Yes. The SDK supports current Laravel versions and PHP 8.2+, with auto-discovery of its service provider and facade. Clear your config and bootstrap cache if the facade isn’t recognized after installation.



