MTProto: Listening
- Introduction
- Basic Listening
- Available Listener Verbs
- Pattern Parameters
- Scoping Listeners
- Middleware
- Conversation Steps
- The Client Request
- Sending Outside a Handler
Introduction
Listening for MTProto updates works exactly like listening for Bot updates — you register listeners with a facade and a closure, and each listener receives a Request object. The only differences are the facade (Client instead of Bot), the request type (ClientRequest), and a much larger catalog of update verbs, because MTProto exposes hundreds of update types the Bot API never sends.
use LaraGram\MTProto\Facades\Client;
use LaraGram\MTProto\Foundation\ClientRequest;
Client::onText('hello', function (ClientRequest $request) {
$request->sendMessage(peer: $request->chatId(), message: 'hi 👋');
});Basic Listening
Where Listeners Live
MTProto listeners are defined in listen files registered under the client: slot (for user clients) or bound to a session name under the bot: slot (for MTProto bots) in your bootstrap/app.php. See Registering Listen Files for the wiring.
// listens/client.php
use LaraGram\MTProto\Facades\Client;
use LaraGram\MTProto\Foundation\ClientRequest;
Client::onCommand('start', function (ClientRequest $request) {
$request->sendMessage(peer: $request->chatId(), message: 'Welcome!');
});The Client Facade
The LaraGram\MTProto\Facades\Client facade is the entry point for registering listeners and for reaching a live session. Its verbs mirror the Bot facade wherever the concept overlaps (onText, onCommand, onPhoto, …) and add MTProto-only verbs on top.
Available Listener Verbs
Every verb accepts a closure, a [Controller::class, 'method'] pair, or an invokable class string as its action — identical to Bot listeners. The most common verbs are grouped below; the full set covers well over two hundred update types.
Messages
Client::on($pattern, $action); // Alias of onText
Client::onText($pattern, $action); // Message text pattern
Client::onCommand($command, $action); // Slash command (captures {args?})
Client::onReferral($pattern, $action); // /start deep-link payload
Client::onMessageType($type, $action); // One or more content types
Client::onMessage($action); // All new messages
Client::onEditedMessage($action); // Edited messages
Client::onSentMessage($action); // Own sent-message confirmations
Client::onDeletedMessages($action); // Deleted messages
Client::onPinnedMessages($action); // Pinned-message events
Client::onScheduledMessage($action); // New scheduled messages
Client::onMessageViews($action); // Channel view-count changes
Client::onMessageForwards($action); // Channel forward-count changes
Client::onTranscribedAudio($action); // Audio transcription resultsMedia
Client::onPhoto($action);
Client::onVideo($action);
Client::onAnimation($action); // GIFs
Client::onSticker($action);
Client::onDocument($action);
Client::onAudio($action);
Client::onVoice($action);
Client::onVideoNote($action); // Round videos
Client::onContact($action);
Client::onLocation($action); // And live locations
Client::onVenue($action);
Client::onGame($action);
Client::onInvoice($action);
Client::onPoll($action);
Client::onGiveaway($action);
Client::onPaidMedia($action);
Client::onDice($action, $emoji = 'any', $value = 0);Text Entities
Client::onHashtag($action); // #hashtag
Client::onCashtag($action); // $CASHTAG
Client::onMention($action); // @mention
Client::onUrl($action);
Client::onEmail($action);
Client::onBotCommandEntity($action); // /command entityCallback & Inline Queries
Client::onCallbackQuery($action); // All callback queries
Client::onCallbackQueryData($pattern, $action); // Matching a data pattern
Client::onInlineQuery($action);
Client::onChosenInlineResult($action);
Client::onPreCheckoutQuery($action);
Client::onShippingQuery($action);Chats, Channels & Participants
Client::onChat($action);
Client::onChannel($action);
Client::onChatParticipant($action); // join/leave/promote/ban
Client::onChatParticipantAdd($action);
Client::onChatParticipantDelete($action);
Client::onChatParticipantAdmin($action); // Admin rights changed
Client::onChatJoinRequest($action);
Client::onChatBoost($action);
Client::onForumTopic($action); // Forum topic pinned/unpinnedReactions & Polls
Client::onReactions($action); // Message reaction changes
Client::onBotReaction($action);
Client::onPollVote($action);
Client::onPollResults($action);Users
Client::onUserStatus($action); // Online/offline
Client::onUserName($action); // Username changes
Client::onUserPhone($action);
Client::onUserEmojiStatus($action);
Client::onTyping($action); // Typing indicators
Client::onReadHistory($action); // Read receiptsStories, Stars & More
Client::onStory($action); // New/updated stories
Client::onStoryReaction($action);
Client::onStarsBalance($action); // Stars balance changes
Client::onEncryptedMessage($action); // Secret-chat messages
Client::onEncryption($action); // Secret-chat state
Client::onBusinessMessage($action); // Telegram Business messages
Client::onDraft($action); // Draft changes
Client::onPhoneCall($action);
Client::onGroupCall($action);NOTE
This is a representative selection. Business connections, group calls, dialog filters (folders), sticker sets, quick replies, notify settings, privacy rules, bot management, and many more verbs are all available. Your IDE will autocomplete the full list from the Client facade.
Catch-All & Fallback
Handle any update that no other listener matched, or register a true catch-all:
Client::onUpdate(function (ClientRequest $request) {
logger()->info('Update: ' . $request->type());
});
Client::fallback(function (ClientRequest $request) {
// Runs when nothing else matched
});Pattern Parameters
Text patterns support the same placeholder syntax as Bot listeners, and captured parameters are injected into your handler after the request. Command arguments are captured automatically:
Client::onText('rate {product} {stars}', function (ClientRequest $request, $product, $stars) {
$request->sendMessage(peer: $request->chatId(), message: "You rated {$product}: {$stars}★");
});
Client::onCommand('echo', function (ClientRequest $request, $args) {
$request->sendMessage(peer: $request->chatId(), message: $args ?? '(nothing to echo)');
});For the complete pattern syntax — optional parameters, constraints via where, and regular expressions — see the Listening reference; it applies to MTProto listeners unchanged.
Scoping Listeners
Direction (Incoming / Outgoing)
Because a user client sees its own outgoing messages as updates too, you can scope listeners to one direction. incomming() matches only messages received by the session; outgoing() matches only messages sent by it:
Client::incomming()->onText('ping', function (ClientRequest $request) {
$request->sendMessage(peer: $request->chatId(), message: 'pong');
});
Client::outgoing()->onText('.note {text}', function (ClientRequest $request, $text) {
// A self-command you type from your own account
});You can also test direction inside a handler with $request->isOutgoing().
Sessions
In a multi-account setup, scope listeners to one or more sessions with forSessions(). Listeners not scoped run for every session:
Client::forSessions('support')->onText('hours', function (ClientRequest $request) {
$request->sendMessage(peer: $request->chatId(), message: 'We are open 9–5.');
});
Client::forSessions(['support', 'sales'])->onCommand('help', HelpController::class);Groups
Group listeners to share attributes — middleware, session scope, or direction — across many registrations at once:
Client::forSessions('support')->group([], function () {
Client::onCommand('open', [TicketController::class, 'open']);
Client::onCommand('close', [TicketController::class, 'close']);
});Middleware
MTProto listeners run through the LaraGram middleware pipeline, so everything you know about middleware applies. Attach middleware to a listener or a group with middleware():
Client::middleware('throttle')->onCommand('report', ReportController::class);
Client::middleware(['auth.admin'])->group([], function () {
Client::onCommand('ban', [ModController::class, 'ban']);
Client::onCommand('unban', [ModController::class, 'unban']);
});The direction:in / direction:out filters used by incomming() / outgoing() are themselves middleware, so they compose naturally with your own.
Conversation Steps
Multi-step conversations are supported through the same Step system as Bot listeners, via onStep:
Client::onStep('await_name', function (ClientRequest $request) {
$request->sendMessage(peer: $request->chatId(), message: "Hi {$request->text()}!");
});The Client Request
Every listener receives a LaraGram\MTProto\Foundation\ClientRequest. It exposes the update's fields as read-only properties, a set of convenience helpers, and — by forwarding to the underlying client — the entire high-level API for replying.
Reading the Update
Access any field of the raw update as a property. Nested TL objects are hydrated into objects you can walk with ->:
Client::onMessage(function (ClientRequest $request) {
$message = $request->message; // The Message object
$fromId = $request->message->peer_id->user_id;
$date = $request->date;
$entities = $request->entities; // MessageEntity[]
});Commonly available properties include message, peer_id, from_id, media, entities, date, user_id, chat_id, channel_id, query, query_id, data (callback data), reactions, story, poll, and many more — one per update field. Missing fields simply return null.
Helper Methods
The request also provides shortcut methods for the values you reach for most:
$request->text(); // Message text (or null)
$request->callbackData(); // Callback query data (or null)
$request->inlineQuery(); // Inline query string (or null)
$request->chatId(); // Resolved chat/peer id
$request->messageId(); // Message id
$request->entities(); // Parsed entities
$request->type(); // Update type string, e.g. "updateNewMessage"
$request->session(); // Originating session name
$request->isOutgoing(); // Was this sent by our own account?
$request->toArray(); // The whole update as an array
$request->toJson(); // ...as JSONMedia helpers download the update's attachment directly (see Media):
$path = $request->download(); // To a temp file, returns the path
$request->downloadMediaToFile('img.jpg');
$info = $request->getMediaInfo(); // Size, mime, dimensions...Replying
Because the request forwards unknown method calls to the client, the full high-level API is available directly on $request. Replies automatically go out through the session that received the update:
Client::onPhoto(function (ClientRequest $request) {
$request->sendMessage(peer: $request->chatId(), message: 'Nice photo!');
$request->markAsRead($request->chatId());
});You can also reach a specific API namespace through the request — see Requests for the full high-level catalog:
$request->messages->sendMessage(
peer: $request->message->peer_id->user_id,
message: 'Sent via the messages namespace',
);Sending Outside a Handler
To send from outside an update handler — a scheduled task, a queue job, a controller, or to a different account than the one that received the update — resolve a live session with Client::session():
use LaraGram\MTProto\Facades\Client;
// Default session
Client::session()->sendMessage(peer: '@durov', message: 'Hello from a job!');
// A specific account
Client::session('announcer')->sendMessage(peer: -1001234567890, message: 'Daily digest is out.');The object returned by Client::session() is the live client, exposing the same high-level API documented in Requests, Chats & Channels, and Media.