How Telegram Desktop Implements Chat Invite Links and Chatlist Invites

Telegram Desktop processes chat invite links and chatlist invites through parallel subsystems that share an MTProto API layer but diverge in UI handling, using messages.checkChatInvite for individual invites and chatlists.exportChatlistInvite for filtered chat lists.

The telegramdesktop/tdesktop codebase handles both individual chat invitations and filtered chat list sharing through a sophisticated multi-layered architecture. This implementation separates API communication, data persistence, and user interface concerns while maintaining strict adherence to the MTProto protocol. Understanding these internals reveals how the client manages permission checks, preview generation, and batch chat subscriptions.

Core API Architecture

Both subsystems funnel requests through ApiWrap::request() in apiwrap.cpp (lines 366-370), which provides generic error handling and authentication layers. The implementation maps MTProto TL methods to C++ wrappers that bridge the protocol buffer layer with the Qt-based UI framework.

The primary API distinctions include:

  • Individual invites: Use messages.checkChatInvite and messages.importChatInvite methods
  • Chatlist invites: Use chatlists.exportChatlistInvite, chatlists.editExportedInvite, and chatlists.joinChatlistInvite methods

Checking and Validating Invites

When a user triggers an invite link—either by clicking an external URL or pasting a hash—the client calls CheckChatInvite() in api/api_chat_invite.cpp (lines 61-73). This function sends the messages.checkChatInvite query to the server and handles three possible response constructors:

  1. messages.chatInvite - Standard invitation requiring user confirmation
  2. messages.chatInviteAlready - User is already a member
  3. messages.chatInvitePeek - Preview of a private invite

If the invite requires a subscription payment, the UI instantiates ConfirmSubscriptionBox (lines 19-46); otherwise, it creates ConfirmInviteBox (lines 104-112).

UI Presentation and Join Flow

The ConfirmInviteBox class in boxes/peers/edit_peer_invite_links.cpp (constructor at line 72) renders the invitation preview, displaying the chat title, avatar, participant count, and expiry information. When the user presses the join button, SubmitChatInvite() (lines 51-66 in api/api_chat_invite.cpp) dispatches messages.importChatInvite to the server.

// api/api_chat_invite.cpp:61-73
void CheckChatInvite(
        not_null<Window::SessionController*> controller,
        const QString &hash,
        ChannelData *invitePeekChannel,
        Fn<void()> loaded) {
    const auto session = &controller->session();
    const auto weak = base::make_weak(controller);
    session->api().checkChatInvite(hash,
        [=](const MTPChatInvite &result) {
            // Handle constructors and show ConfirmInviteBox
        },
        [=](const MTP::Error &error) { /* Error handling */ });
}

On successful joining, the client opens the chat history via Window::SessionController::showPeerHistory.

Chatlist invites (also called folder invites) allow users to share entire filtered sets of chats through a single link. Unlike individual invites, these operate on the Data::ChatFilterLink data structure defined in data/data_chat_filters.h.

Exporting Chatlist Invites

When a user clicks Export link in the filter editor, ExportFilterLink() in boxes/filters/edit_filter_links.cpp (lines 1080-1087) constructs an MTP_vector<MTPInputPeer> from the filter's chat list and calls chatlists.exportChatlistInvite.

// boxes/filters/edit_filter_links.cpp:1080-1087
void ExportFilterLink(
        FilterId id,
        const std::vector<not_null<PeerData*>> &peers,
        Fn<void(Data::ChatFilterLink)> done,
        Fn<void(QString)> fail) {
    const auto session = &peers.front()->session();
    auto mtpPeers = peers |
        ranges::views::transform([](not_null<PeerData*> p){ 
            return MTPInputPeer(p->input()); 
        })
        | ranges::to<QVector<MTPInputPeer>>();
    session->api().request(MTPchatlists_ExportChatlistInvite(
            MTP_inputChatlistDialogFilter(MTP_int(id)),
            MTP_string(), // Empty title for default
            MTP_vector<MTPInputPeer>(std::move(mtpPeers))))
        .done([=](const MTPchatlists_ExportedChatlistInvite &result) {
            // Store via Data::ChatFilters::add()
        })
        .fail([=](const MTP::Error &error) { fail(error.type()); })
        .send();
}

The server returns a ChatlistExportedInvite containing a unique slug, which the client persists through Data::ChatFilters::add() in data/data_chat_filters.cpp (lines 543-553).

The EditLinkChats() function (lines 1040-1064 in boxes/filters/edit_filter_links.cpp) handles modifications to existing chatlist invites. It sends chatlists.editExportedInvite with updated peer vectors while preserving the original URL slug.

// boxes/filters/edit_filter_links.cpp:1040-1064
void EditLinkChats(
        const Data::ChatFilterLink &link,
        base::flat_set<not_null<PeerData*>> peers,
        Fn<void(QString)> done) {
    const auto session = &peers.front()->session();
    auto mtpPeers = peers |
        ranges::views::transform([](not_null<PeerData*> p){ 
            return MTPInputPeer(p->input()); 
        })
        | ranges::to<QVector<MTPInputPeer>>();
    session->api().request(MTPchatlists_EditExportedInvite(
            MTP_flags(MTPchatlists_EditExportedInvite::Flag::f_peers),
            MTP_inputChatlistDialogFilter(MTP_int(link.id)),
            MTP_string(link.url),
            MTPstring(), // Title unchanged
            MTP_vector<MTPInputPeer>(std::move(mtpPeers))))
        .done([=](const MTPExportedChatlistInvite &) { done(QString()); })
        .fail([=](const MTP::Error &error) { done(error.type()); })
        .send();
}

Error Handling and UI Feedback

Both subsystems rely on ApiWrap::request() for error propagation. Specific error strings like INVITE_REQUEST_SENT, USERS_TOO_MUCH, and generic "bad link" responses are mapped to localized toast notifications in CheckChatInvite (lines 93-115) and through ShowSaveError / ShowEmptyLinkError for chatlist operations (lines 108-115).

The UI components for displaying links include:

  • InviteLinkLabel and InviteLinkButtons in ui/controls/invite_link_label.h/.cpp and ui/controls/invite_link_buttons.h/.cpp
  • LinkRow in boxes/filters/edit_filter_links.cpp (lines 35-53) for chatlist link listings

Key Files and Architecture

The implementation maintains strict separation across three layers:

  • API Layer: api/api_chat_invite.cpp, apiwrap.cpp, and mtproto/scheme/api.tl handle protocol communication
  • Data Layer: data/data_chat_filters.cpp persists chatlist invite state
  • UI Layer: boxes/peers/edit_peer_invite_links.cpp and boxes/filters/edit_filter_links.cpp manage user interactions

Summary

  • Telegram Desktop implements chat invite links and chatlist invites as parallel subsystems with distinct MTProto methods but shared error handling infrastructure
  • Individual invites use messages.checkChatInvite and messages.importChatInvite, processed through CheckChatInvite() and SubmitChatInvite() in api/api_chat_invite.cpp
  • Chatlist invites rely on chatlists.exportChatlistInvite and chatlists.editExportedInvite, implemented in boxes/filters/edit_filter_links.cpp
  • The Data::ChatFilterLink structure in data/data_chat_filters.h persists chatlist invite metadata including slugs and peer lists
  • All API requests flow through ApiWrap::request() in apiwrap.cpp, which attaches standardized error handling and authentication

Frequently Asked Questions

Chat invite links provide access to individual groups or channels through messages.checkChatInvite, while chatlist invites allow sharing entire filtered chat collections via chatlists.exportChatlistInvite. The former uses the ConfirmInviteBox UI component, whereas the latter utilizes Data::ChatFilterLink structures and the LinkRow class for management.

Invalid links trigger error responses through ApiWrap::request() that are caught in CheckChatInvite (lines 93-115) and translated to localized toast notifications. Specific error types like INVITE_REQUEST_SENT or USERS_TOO_MUCH generate distinct user-facing messages without crashing the application.

Which data structure stores chatlist invite information?

The Data::ChatFilterLink structure defined in data/data_chat_filters.h stores chatlist invite metadata including the URL slug, title, and associated chat list. The Data::ChatFilters manager class maintains the mapping between filter IDs and these link objects in data/data_chat_filters.cpp.

Where does the actual API request construction happen for joining a chat?

The SubmitChatInvite() function in api/api_chat_invite.cpp (lines 51-66) constructs the MTPmessages_ImportChatInvite request using the invite hash, while chatlist modifications use EditLinkChats() in boxes/filters/edit_filter_links.cpp to build MTPchatlists_EditExportedInvite requests with updated peer vectors.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →