Appearance
Events & Listeners
The package fires lifecycle events at every stage of update processing. You can listen to these events to add cross-cutting behaviour — usage history, analytics, affiliate tracking, notifications — without touching handler classes.
Quick Start
Register listeners in your AppServiceProvider::boot():
php
use TelegramBotEssentials\Essence\Events\BotDeepLinkReceived;
use TelegramBotEssentials\Essence\Events\BotCallbackQueryHandled;
public function boot(): void
{
botEventBus()->listen(BotDeepLinkReceived::class, AffiliateListener::class);
botEventBus()->listen(BotCallbackQueryHandled::class, UserActivityListener::class);
}Write a listener as a plain invokable class:
php
class AffiliateListener
{
public function handle(BotDeepLinkReceived $event): void
{
$user = $event->context->botUser; // BotUser model
$payload = $event->payload; // e.g. "ref_abc123"
Referral::record($user, $payload);
}
}The botEventBus() Helper
botEventBus() returns the BotEventBus singleton. It has two methods:
| Method | What it does |
|---|---|
listen(string $event, $listener) | Register a listener for a TBE event |
fire(BotEvent $event) | Dispatch an event (used internally by the package) |
listen() is chainable:
php
botEventBus()
->listen(BotUpdateReceived::class, LogAllUpdates::class)
->listen(BotUpdateUnhandled::class, AlertOnUnhandled::class);Built-in Events
Most events extend BotEvent, which carries a WebhookContext $context snapshot. The exception is BotUserStatusChanged, which carries its BotUser explicitly (see its section below).
BotUpdateReceived
Fires for every incoming update before routing begins. Useful for logging or rate-limiting.
namespace TelegramBotEssentials\Essence\Events
Properties:
WebhookContext $context
string $updateType // 'message', 'callback_query', etc.php
botEventBus()->listen(BotUpdateReceived::class, function (BotUpdateReceived $event) {
Log::info('update received', [
'type' => $event->updateType,
'user_id' => $event->context->botUserId,
]);
});BotWebhookInitialized
Fires from TelegramBotAuthentication once the bot tenant, Update, and BotUser are resolved — before BotUpdateReceived and routing. Extends BotEvent. Essence's own SetBotLocale listener uses it to set the per-bot locale via the ResolvesBotLocale contract; Settings rebinds that contract rather than adding its own listener here.
Properties:
WebhookContext $contextBotCommandHandled
Fires after a /command is dispatched via CommandBus — for a real incoming message, or when another handler triggers a command programmatically with commandBus()->route().
Properties:
WebhookContext $context
string $command // e.g. '/start', '/help'BotDeepLinkReceived
Fires when the user sends /start <payload> — i.e. followed a deep link. Fires in addition to BotCommandHandled, not instead of it.
Properties:
WebhookContext $context
string $payload // the text after '/start 'Affiliate system example:
php
botEventBus()->listen(BotDeepLinkReceived::class, function (BotDeepLinkReceived $event) {
[$type, $code] = explode('_', $event->payload, 2) + [null, null];
if ($type === 'ref') {
Referral::create([
'referrer_code' => $code,
'new_user_id' => $event->context->botUserId,
]);
}
});BotReplyKeyHandled
Fires after a keyboard button is matched and its handler runs.
Properties:
WebhookContext $context
string $keyText // the button text that was pressedBotStateAnswerHandled
Fires after a state answer handler runs. $state is the state that was active at the start of the request (before the handler may have cleared it).
Properties:
WebhookContext $context
string $state // encoded state string, e.g. 'PRODUCTS#add?target_field=title'BotCallbackQueryHandled
Fires after a callback query handler runs successfully.
Properties:
WebhookContext $context
string $type // callback type, e.g. 'BUYSVC'
string $method // callback method, e.g. 'pay'Usage history example:
php
botEventBus()->listen(BotCallbackQueryHandled::class, function (BotCallbackQueryHandled $event) {
UserActivity::log(
userId: $event->context->botUserId,
action: $event->type . '#' . $event->method,
);
});BotInlineQueryHandled
Fires after an inline query handler runs successfully. The $query is the text the user typed in the inline search box.
Properties:
WebhookContext $context
string $query // e.g. "iphone 14" (may be empty string for open-ended queries)php
botEventBus()->listen(BotInlineQueryHandled::class, function (BotInlineQueryHandled $event) {
SearchLog::record(
userId: $event->context->botUserId,
query: $event->query,
);
});BotUpdateUnhandled
Fires when an incoming message does not match any command, reply key, or state answer. The "request is invalid" message to the user is sent after this event.
Properties:
WebhookContext $contextBotUserStatusChanged
Fires whenever a user's reachability actually changes (no-op when the state is unchanged). Not a BotEvent — it also fires from queued broadcast sends, where the webhook context belongs to whoever started the broadcast rather than to the user whose status changed, so the affected user is carried explicitly.
namespace TelegramBotEssentials\Essence\Events
Properties:
BotUser $botUser
string $from // previous state: active | blocked | unreachable | deactivated
string $to // new state
string $source // BotUserStatusChanged::SOURCE_WEBHOOK | SOURCE_SEND_FAILURE | SOURCE_SEND_SUCCESSListen with Laravel's own Event::listen() / a listener class (not botEventBus()), since it isn't a BotEvent:
php
Event::listen(BotUserStatusChanged::class, function (BotUserStatusChanged $event) {
if ($event->to === BotUser::STATUS_BLOCKED) {
// ...
}
});The WebhookContext on Events
Every event carries a WebhookContext snapshot captured before routing starts. It gives you read access to the full request context:
| Property | Type | Description |
|---|---|---|
$context->botUser | BotUser | The user who sent the update |
$context->bot | Bot | The bot (tenant) that received it |
$context->botUserId | int | BotUser primary key |
$context->botId | int | Bot primary key |
$context->botToken | string | Bot API token |
$context->updatePayload | array | Raw Telegram update as array |
For a Telegram Api instance, call $event->context->api().
Queued Listeners
WebhookContext is fully serializable, so queued listeners work without extra setup. Implement Laravel's ShouldQueue interface:
php
use Illuminate\Contracts\Queue\ShouldQueue;
use TelegramBotEssentials\Essence\Events\BotCallbackQueryHandled;
class UserActivityListener implements ShouldQueue
{
public string $queue = 'low';
public function handle(BotCallbackQueryHandled $event): void
{
UserActivity::log(
userId: $event->context->botUserId,
action: $event->type . '#' . $event->method,
);
}
}Register as usual:
php
botEventBus()->listen(BotCallbackQueryHandled::class, UserActivityListener::class);If you need to call the Telegram API from a queued listener, restore the webhook context first:
php
public function handle(BotDeepLinkReceived $event): void
{
$event->context->apply(); // restores wHook() state
wHook()->api()->sendMessage([
'chat_id' => wHook()->user()->telegramUser->peer_id,
'text' => 'Welcome via referral!',
]);
}Firing Custom Events from a Package or Module
If you are building a companion package (billing, affiliate, etc.) and want to fire your own events that carry the webhook context, extend BotEvent:
php
use TelegramBotEssentials\Essence\Events\BotEvent;
use TelegramBotEssentials\Essence\Support\WebhookContext;
class ServicePurchased extends BotEvent
{
public function __construct(
WebhookContext $context,
public readonly VServiceOrder $order,
) {
parent::__construct($context);
}
}Fire it from a CallbackQuery or StateAnswer:
php
botEventBus()->fire(new ServicePurchased(
context: WebhookContext::capture(),
order: $order,
));Listen in any service provider:
php
botEventBus()->listen(ServicePurchased::class, ProvisionServiceListener::class);Event Firing Order
For a message update:
BotWebhookInitialized (from the auth middleware, before routing)
BotUpdateReceived
└── /command → BotCommandHandled
└── /start <payload> → BotDeepLinkReceived
└── keyboard text → BotReplyKeyHandled
└── text (has state) → BotStateAnswerHandled
└── nothing matched → BotUpdateUnhandledFor a callback_query update:
BotUpdateReceived
└── handled → BotCallbackQueryHandledFor an inline_query update:
BotUpdateReceived
└── handled → BotInlineQueryHandled