diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt
index 2e5178a2e..3ad4fa5a8 100644
--- a/Telegram/CMakeLists.txt
+++ b/Telegram/CMakeLists.txt
@@ -246,6 +246,8 @@ PRIVATE
     boxes/peers/prepare_short_info_box.h
     boxes/peers/replace_boost_box.cpp
     boxes/peers/replace_boost_box.h
+    boxes/peers/verify_peers_box.cpp
+    boxes/peers/verify_peers_box.h
     boxes/about_box.cpp
     boxes/about_box.h
     boxes/about_sponsored_box.cpp
diff --git a/Telegram/Resources/langs/lang.strings b/Telegram/Resources/langs/lang.strings
index f1d8dbba5..64077c69a 100644
--- a/Telegram/Resources/langs/lang.strings
+++ b/Telegram/Resources/langs/lang.strings
@@ -1628,11 +1628,50 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 "lng_manage_peer_bot_star_ref" = "Affiliate Program";
 "lng_manage_peer_bot_star_ref_off" = "Off";
 "lng_manage_peer_bot_star_ref_about" = "Share a link to {bot} with your friends and earn {amount} of their spending there.";
+"lng_manage_peer_bot_verify" = "Verify Accounts";
 "lng_manage_peer_bot_edit_intro" = "Edit Intro";
 "lng_manage_peer_bot_edit_commands" = "Edit Commands";
 "lng_manage_peer_bot_edit_settings" = "Change Bot Settings";
 "lng_manage_peer_bot_about" = "Use {bot} to manage this bot.";
 
+"lng_bot_verify_title" = "Choose Chat to Verify";
+"lng_bot_verify_bot_title" = "Verify Bot";
+"lng_bot_verify_bot_text" = "Do you want to verify {name} with your verification mark and description?";
+"lng_bot_verify_bot_about" = "You can customize your description for each bot.";
+"lng_bot_verify_bot_submit" = "Verify Bot";
+"lng_bot_verify_bot_sent" = "{name} has been notified and will receive your verification mark and description upon accepting.";
+"lng_bot_verify_bot_remove" = "This bot is already verified by you. Do you want to remove verification?";
+"lng_bot_verify_bot_telegram" = "This bot is verified as official by the representatives of Telegram.";
+"lng_bot_verify_bot_company" = "This bot was verified by {company}.";
+"lng_bot_verify_user_title" = "Verify User";
+"lng_bot_verify_user_text" = "Do you want to verify {name} with your verification mark and description?";
+"lng_bot_verify_user_about" = "You can customize your description for each account.";
+"lng_bot_verify_user_submit" = "Verify User";
+"lng_bot_verify_user_sent" = "{name} has been notified and will receive your verification mark and description upon accepting.";
+"lng_bot_verify_user_remove" = "This account is already verified by you. Do you want to remove verification?";
+"lng_bot_verify_user_telegram" = "This account is verified as official by the representatives of Telegram.";
+"lng_bot_verify_user_company" = "This account was verified by {company}.";
+"lng_bot_verify_channel_title" = "Verify Channel";
+"lng_bot_verify_channel_text" = "Do you want to verify {name} with your verification mark and description?";
+"lng_bot_verify_channel_about" = "You can customize your description for each channel.";
+"lng_bot_verify_channel_submit" = "Verify Channel";
+"lng_bot_verify_channel_sent" = "{name} has been notified and will receive your verification mark and description upon accepting.";
+"lng_bot_verify_channel_remove" = "This channel is already verified by you. Do you want to remove verification?";
+"lng_bot_verify_channel_telegram" = "This channel is verified as official by the representatives of Telegram.";
+"lng_bot_verify_channel_company" = "This channel was verified by {company}.";
+"lng_bot_verify_group_title" = "Verify Group";
+"lng_bot_verify_group_text" = "Do you want to verify {name} with your verification mark and description?";
+"lng_bot_verify_group_about" = "You can customize your description for each group.";
+"lng_bot_verify_group_submit" = "Verify Group";
+"lng_bot_verify_group_sent" = "{name} has been notified and will receive your verification mark and description upon accepting.";
+"lng_bot_verify_group_remove" = "This group is already verified by you. Do you want to remove verification?";
+"lng_bot_verify_group_telegram" = "This community is verified as official by the representatives of Telegram.";
+"lng_bot_verify_group_company" = "This community was verified by {company}.";
+"lng_bot_verify_description_label" = "Description";
+"lng_bot_verify_remove_title" = "Remove verification";
+"lng_bot_verify_remove_submit" = "Remove";
+"lng_bot_verify_remove_done" = "You've removed this verification.";
+
 "lng_star_ref_title" = "Affiliate Program";
 "lng_star_ref_about" = "Reward those who help grow your user base.";
 "lng_star_ref_share_title" = "Share revenue with affiliates";
@@ -3484,8 +3523,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 "lng_verification_codes" = "Verification Codes";
 "lng_verification_codes_about" = "Third-party services, like websites and stores, can send verification codes to your phone number via Telegram instead of SMS. Such codes will appear in this chat.\n\nIf you didn't request any codes — don't worry! Most likely, someone made a mistake when entering their number.";
 
-"lng_verified_by_telegram" = "This community is verified as official by the representatives of Telegram.";
-
 "lng_archived_name" = "Archived chats";
 "lng_archived_add" = "Archive";
 "lng_archived_remove" = "Unarchive";
diff --git a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp
index f80f4e73d..7fea20e34 100644
--- a/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp
+++ b/Telegram/SourceFiles/boxes/peers/edit_peer_info_box.cpp
@@ -26,6 +26,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "boxes/peers/edit_peer_requests_box.h"
 #include "boxes/peers/edit_peer_reactions.h"
 #include "boxes/peers/replace_boost_box.h"
+#include "boxes/peers/verify_peers_box.h"
 #include "boxes/peer_list_controllers.h"
 #include "boxes/stickers_box.h"
 #include "boxes/username_box.h"
@@ -366,6 +367,7 @@ private:
 	void fillBotEditIntroButton();
 	void fillBotEditCommandsButton();
 	void fillBotEditSettingsButton();
+	void fillBotVerifyAccounts();
 
 	void submitTitle();
 	void submitDescription();
@@ -1206,6 +1208,7 @@ void Controller::fillManageSection() {
 					Ui::Text::RichLangValue),
 				st::boxDividerLabel),
 			st::defaultBoxDividerLabelPadding));
+		fillBotVerifyAccounts();
 		return;
 	}
 
@@ -1796,6 +1799,39 @@ void Controller::fillBotEditSettingsButton() {
 		{ &st::menuIconSettings });
 }
 
+void Controller::fillBotVerifyAccounts() {
+	Expects(_isBot);
+
+	const auto user = _peer->asUser();
+	const auto wrap = _controls.buttonsLayout->add(
+		object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
+			_controls.buttonsLayout,
+			object_ptr<Ui::VerticalLayout>(
+				_controls.buttonsLayout)));
+	wrap->toggleOn(rpl::single(
+		rpl::empty
+	) | rpl::then(user->owner().botCommandsChanges(
+	) | rpl::filter(
+		rpl::mappers::_1 == _peer
+	) | rpl::to_empty) | rpl::map([=] {
+		const auto info = user->botInfo.get();
+		return info && info->verifierSettings;
+	}));
+
+	const auto inner = wrap->entity();
+	Ui::AddSkip(inner);
+	AddButtonWithCount(
+		inner,
+		tr::lng_manage_peer_bot_verify(),
+		rpl::never<QString>(),
+		[controller = _navigation->parentController(), user] {
+			controller->show(MakeVerifyPeersBox(controller, user));
+		},
+		{ &st::menuIconFactcheck });
+	Ui::AddSkip(inner);
+	Ui::AddDivider(inner);
+}
+
 void Controller::submitTitle() {
 	Expects(_controls.title != nullptr);
 
diff --git a/Telegram/SourceFiles/boxes/peers/verify_peers_box.cpp b/Telegram/SourceFiles/boxes/peers/verify_peers_box.cpp
new file mode 100644
index 000000000..cafc5d3b0
--- /dev/null
+++ b/Telegram/SourceFiles/boxes/peers/verify_peers_box.cpp
@@ -0,0 +1,300 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#include "boxes/peers/verify_peers_box.h"
+
+#include "apiwrap.h"
+#include "boxes/peer_list_controllers.h"
+#include "data/data_user.h"
+#include "history/history.h"
+#include "main/main_app_config.h"
+#include "main/main_session.h"
+#include "ui/boxes/confirm_box.h"
+#include "ui/text/text_utilities.h"
+#include "ui/toast/toast.h"
+#include "ui/widgets/fields/input_field.h"
+#include "ui/vertical_list.h"
+#include "window/window_session_controller.h"
+#include "styles/style_boxes.h"
+#include "styles/style_layers.h"
+
+namespace {
+
+constexpr auto kSetupVerificationToastDuration = 4 * crl::time(1000);
+
+class Controller final : public ChatsListBoxController {
+public:
+	Controller(not_null<Main::Session*> session, not_null<UserData*> bot)
+	: ChatsListBoxController(session)
+	, _bot(bot) {
+	}
+
+	Main::Session &session() const override;
+
+	void rowClicked(gsl::not_null<PeerListRow*> row) override;
+
+private:
+	std::unique_ptr<Row> createRow(not_null<History*> history) override;
+	void prepareViewHook() override;
+
+	void confirmAdd(not_null<PeerData*> peer);
+	void confirmRemove(not_null<PeerData*> peer);
+
+	const not_null<UserData*> _bot;
+
+};
+
+void Setup(
+		not_null<UserData*> bot,
+		not_null<PeerData*> peer,
+		QString description,
+		Fn<void(QString)> done) {
+	using Flag = MTPbots_SetCustomVerification::Flag;
+	bot->session().api().request(MTPbots_SetCustomVerification(
+		MTP_flags(Flag::f_bot
+			| Flag::f_enabled
+			| (description.isEmpty() ? Flag() : Flag::f_custom_description)),
+		bot->inputUser,
+		peer->input,
+		MTP_string(description)
+	)).done([=] {
+		done(QString());
+	}).fail([=](const MTP::Error &error) {
+		done(error.type());
+	}).send();
+}
+
+void Remove(
+		not_null<UserData*> bot,
+		not_null<PeerData*> peer,
+		Fn<void(QString)> done) {
+	bot->session().api().request(MTPbots_SetCustomVerification(
+		MTP_flags(MTPbots_SetCustomVerification::Flag::f_bot),
+		bot->inputUser,
+		peer->input,
+		MTPstring()
+	)).done([=] {
+		done(QString());
+	}).fail([=](const MTP::Error &error) {
+		done(error.type());
+	}).send();
+}
+
+Main::Session &Controller::session() const {
+	return _bot->session();
+}
+
+void Controller::rowClicked(gsl::not_null<PeerListRow*> row) {
+	const auto peer = row->peer();
+	const auto details = peer->verifyDetails();
+	const auto already = details && (details->botId == peerToUser(_bot->id));
+	if (already) {
+		confirmRemove(peer);
+	} else {
+		confirmAdd(peer);
+	}
+}
+
+void Controller::confirmAdd(not_null<PeerData*> peer) {
+	const auto bot = _bot;
+	const auto show = delegate()->peerListUiShow();
+	show->show(Box([=](not_null<Ui::GenericBox*> box) {
+		struct State {
+			Ui::InputField *field = nullptr;
+			QString description;
+			bool sent = false;
+		};
+		const auto settings = bot->botInfo
+			? bot->botInfo->verifierSettings.get()
+			: nullptr;
+		const auto state = std::make_shared<State>(State{
+			.description = settings ? settings->customDescription : QString()
+		});
+
+		const auto limit = session().appConfig().get<int>(
+			u"bot_verification_description_length_limit"_q,
+			70);
+		const auto send = [=] {
+			if (state->description.size() > limit) {
+				state->field->showError();
+				return;
+			} else if (state->sent) {
+				return;
+			}
+			state->sent = true;
+			const auto weak = Ui::MakeWeak(box);
+			Setup(bot, peer, state->description, [=](QString error) {
+				if (error.isEmpty()) {
+					if (const auto strong = weak.data()) {
+						strong->closeBox();
+					}
+					show->showToast({
+						.text = PeerVerifyPhrases(peer).sent(
+							tr::now,
+							lt_name,
+							Ui::Text::Bold(peer->shortName()),
+							Ui::Text::WithEntities),
+						.duration = kSetupVerificationToastDuration,
+						});
+				} else {
+					state->sent = false;
+					show->showToast(error);
+				}
+			});
+		};
+
+		const auto phrases = PeerVerifyPhrases(peer);
+		Ui::ConfirmBox(box, {
+			.text = phrases.text(
+				lt_name,
+				rpl::single(Ui::Text::Bold(peer->shortName())),
+				Ui::Text::WithEntities),
+			.confirmed = send,
+			.confirmText = phrases.submit(),
+			.title = phrases.title(),
+		});
+
+		Ui::AddSubsectionTitle(
+			box->verticalLayout(),
+			tr::lng_bot_verify_description_label(),
+			QMargins(0, 0, 0, -st::defaultSubsectionTitlePadding.bottom()));
+
+		const auto field = box->addRow(object_ptr<Ui::InputField>(
+			box,
+			st::createPollField,
+			Ui::InputField::Mode::NoNewlines,
+			(settings
+				? phrases.company(lt_company, rpl::single(settings->company))
+				: rpl::single(QString())),
+			state->description
+		), st::createPollFieldPadding);
+		state->field = field;
+
+		box->setFocusCallback([=] {
+			field->setFocusFast();
+		});
+
+		Ui::AddSkip(box->verticalLayout());
+
+		field->changes() | rpl::start_with_next([=] {
+			state->description = field->getLastText();
+		}, field->lifetime());
+
+		field->setMaxLength(limit * 2);
+		Ui::AddLengthLimitLabel(field, limit, std::nullopt);
+
+		Ui::AddDividerText(box->verticalLayout(), phrases.about());
+	}));
+}
+
+void Controller::confirmRemove(not_null<PeerData*> peer) {
+	const auto bot = _bot;
+	const auto show = delegate()->peerListUiShow();
+	show->show(Box([=](not_null<Ui::GenericBox*> box) {
+		const auto sent = std::make_shared<bool>();
+		const auto send = [=] {
+			if (*sent) {
+				return;
+			}
+			*sent = true;
+			const auto weak = Ui::MakeWeak(box);
+			Remove(bot, peer, [=](QString error) {
+				if (error.isEmpty()) {
+					if (const auto strong = weak.data()) {
+						strong->closeBox();
+					}
+					show->showToast(tr::lng_bot_verify_remove_done(tr::now));
+				} else {
+					*sent = false;
+					show->showToast(error);
+				}
+			});
+		};
+		Ui::ConfirmBox(box, {
+			.text = PeerVerifyPhrases(peer).remove(),
+			.confirmed = send,
+			.confirmText = tr::lng_bot_verify_remove_submit(),
+			.confirmStyle = &st::attentionBoxButton,
+			.title = tr::lng_bot_verify_remove_title(),
+		});
+	}));
+}
+
+auto Controller::createRow(not_null<History*> history)
+-> std::unique_ptr<Row> {
+	const auto peer = history->peer;
+	const auto may = peer->isUser() || peer->isChannel();
+	return may ? std::make_unique<Row>(history) : nullptr;
+}
+
+void Controller::prepareViewHook() {
+}
+
+} // namespace
+
+object_ptr<Ui::BoxContent> MakeVerifyPeersBox(
+		not_null<Window::SessionController*> window,
+		not_null<UserData*> bot) {
+	const auto session = &window->session();
+	auto controller = std::make_unique<Controller>(session, bot);
+	auto init = [=](not_null<PeerListBox*> box) {
+		box->setTitle(tr::lng_bot_verify_title());
+		box->addButton(tr::lng_box_done(), [=] {
+			box->closeBox();
+		});
+	};
+	return Box<PeerListBox>(std::move(controller), std::move(init));
+}
+
+VerifyPhrases PeerVerifyPhrases(not_null<PeerData*> peer) {
+	if (const auto user = peer->asUser()) {
+		if (user->isBot()) {
+			return {
+				.title = tr::lng_bot_verify_bot_title,
+				.text = tr::lng_bot_verify_bot_text,
+				.about = tr::lng_bot_verify_bot_about,
+				.submit = tr::lng_bot_verify_bot_submit,
+				.sent = tr::lng_bot_verify_bot_sent,
+				.remove = tr::lng_bot_verify_bot_remove,
+				.telegram = tr::lng_bot_verify_bot_telegram,
+				.company = tr::lng_bot_verify_bot_company,
+			};
+		} else {
+			return {
+				.title = tr::lng_bot_verify_user_title,
+				.text = tr::lng_bot_verify_user_text,
+				.about = tr::lng_bot_verify_user_about,
+				.submit = tr::lng_bot_verify_user_submit,
+				.sent = tr::lng_bot_verify_user_sent,
+				.remove = tr::lng_bot_verify_user_remove,
+				.telegram = tr::lng_bot_verify_user_telegram,
+				.company = tr::lng_bot_verify_user_company,
+			};
+		}
+	} else if (peer->isBroadcast()) {
+		return {
+			.title = tr::lng_bot_verify_channel_title,
+			.text = tr::lng_bot_verify_channel_text,
+			.about = tr::lng_bot_verify_channel_about,
+			.submit = tr::lng_bot_verify_channel_submit,
+			.sent = tr::lng_bot_verify_channel_sent,
+			.remove = tr::lng_bot_verify_channel_remove,
+			.telegram = tr::lng_bot_verify_channel_telegram,
+			.company = tr::lng_bot_verify_channel_company,
+		};
+	}
+	return {
+		.title = tr::lng_bot_verify_group_title,
+		.text = tr::lng_bot_verify_group_text,
+		.about = tr::lng_bot_verify_group_about,
+		.submit = tr::lng_bot_verify_group_submit,
+		.sent = tr::lng_bot_verify_group_sent,
+		.remove = tr::lng_bot_verify_group_remove,
+		.telegram = tr::lng_bot_verify_group_telegram,
+		.company = tr::lng_bot_verify_group_company,
+	};
+}
diff --git a/Telegram/SourceFiles/boxes/peers/verify_peers_box.h b/Telegram/SourceFiles/boxes/peers/verify_peers_box.h
new file mode 100644
index 000000000..5e2a1b3fb
--- /dev/null
+++ b/Telegram/SourceFiles/boxes/peers/verify_peers_box.h
@@ -0,0 +1,38 @@
+/*
+This file is part of Telegram Desktop,
+the official desktop application for the Telegram messaging service.
+
+For license and copyright information please follow this link:
+https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
+*/
+#pragma once
+
+#include "base/object_ptr.h"
+#include "lang/lang_keys.h"
+
+class PeerData;
+class UserData;
+
+namespace Ui {
+class BoxContent;
+} // namespace Ui
+
+namespace Window {
+class SessionController;
+} // namespace Window
+
+[[nodiscard]] object_ptr<Ui::BoxContent> MakeVerifyPeersBox(
+	not_null<Window::SessionController*> window,
+	not_null<UserData*> bot);
+
+struct VerifyPhrases {
+	tr::phrase<> title;
+	tr::phrase<lngtag_name> text;
+	tr::phrase<> about;
+	tr::phrase<> submit;
+	tr::phrase<lngtag_name> sent;
+	tr::phrase<> remove;
+	tr::phrase<> telegram;
+	tr::phrase<lngtag_company> company;
+};
+[[nodiscard]] VerifyPhrases PeerVerifyPhrases(not_null<PeerData*> peer);
diff --git a/Telegram/SourceFiles/data/data_session.cpp b/Telegram/SourceFiles/data/data_session.cpp
index e8173de68..42735d270 100644
--- a/Telegram/SourceFiles/data/data_session.cpp
+++ b/Telegram/SourceFiles/data/data_session.cpp
@@ -142,7 +142,7 @@ void CheckForSwitchInlineButton(not_null<HistoryItem*> item) {
 	const auto flags = TextParseLinks;
 	return {
 		.botId = UserId(data.vbot_id().v),
-		.iconBgId = SerializeCustomEmojiId(DocumentId(data.vicon().v)),
+		.iconBgId = DocumentId(data.vicon().v),
 		.company = qs(data.vcompany()),
 		.description = TextUtilities::ParseEntities(description, flags),
 	};
@@ -353,7 +353,7 @@ Session::Session(not_null<Main::Session*> session)
 
 
 Ui::VerifyDetails Session::verifiedByTelegram() {
-	if (_verifiedByTelegramIconBgId.isEmpty()) {
+	if (!_verifiedByTelegramIconBgId) {
 		const auto bg = ChatHelpers::GenerateLocalSticker(
 			_session,
 			u":/gui/art/verified_bg.webp"_q);
@@ -362,14 +362,13 @@ Ui::VerifyDetails Session::verifiedByTelegram() {
 			_session,
 			u":/gui/art/verified_fg.webp"_q);
 		fg->overrideEmojiUsesTextColor(true);
-		_verifiedByTelegramIconBgId = Data::SerializeCustomEmojiId(bg);
-		_verifiedByTelegramIconFgId = Data::SerializeCustomEmojiId(fg);
+		_verifiedByTelegramIconBgId = bg->id;
+		_verifiedByTelegramIconFgId = fg->id;
 	}
 	return {
 		.iconBgId = _verifiedByTelegramIconBgId,
 		.iconFgId = _verifiedByTelegramIconFgId,
 		.company = u"Telegram"_q,
-		.description = { tr::lng_verified_by_telegram(tr::now) },
 	};
 }
 
diff --git a/Telegram/SourceFiles/data/data_session.h b/Telegram/SourceFiles/data/data_session.h
index f24190c40..f0aaed6a4 100644
--- a/Telegram/SourceFiles/data/data_session.h
+++ b/Telegram/SourceFiles/data/data_session.h
@@ -1142,8 +1142,8 @@ private:
 	const std::unique_ptr<BusinessInfo> _businessInfo;
 	std::unique_ptr<ShortcutMessages> _shortcutMessages;
 
-	QString _verifiedByTelegramIconBgId;
-	QString _verifiedByTelegramIconFgId;
+	DocumentId _verifiedByTelegramIconBgId = 0;
+	DocumentId _verifiedByTelegramIconFgId = 0;
 
 	MsgId _nonHistoryEntryId = ShortcutMaxMsgId;
 
diff --git a/Telegram/SourceFiles/data/data_user.cpp b/Telegram/SourceFiles/data/data_user.cpp
index 01b51ec46..ba0cc0f1b 100644
--- a/Telegram/SourceFiles/data/data_user.cpp
+++ b/Telegram/SourceFiles/data/data_user.cpp
@@ -36,6 +36,30 @@ constexpr auto kSetOnlineAfterActivity = TimeId(30);
 
 using UpdateFlag = Data::PeerUpdate::Flag;
 
+bool ApplyBotVerifierSettings(
+		not_null<BotInfo*> info,
+		const MTPBotVerifierSettings *settings) {
+	if (!settings) {
+		const auto taken = base::take(info->verifierSettings);
+		return taken != nullptr;
+	}
+	const auto &data = settings->data();
+	const auto parsed = BotVerifierSettings{
+		.iconId = DocumentId(data.vicon().v),
+		.company = qs(data.vcompany()),
+		.customDescription = qs(data.vcustom_description().value_or_empty()),
+	};
+	if (!info->verifierSettings) {
+		info->verifierSettings = std::make_unique<BotVerifierSettings>(
+			parsed);
+		return true;
+	} else if (*info->verifierSettings != parsed) {
+		*info->verifierSettings = parsed;
+		return true;
+	}
+	return false;
+}
+
 } // namespace
 
 BotInfo::BotInfo() = default;
@@ -232,7 +256,11 @@ void UserData::setPersonalChannel(ChannelId channelId, MsgId messageId) {
 	}
 }
 
-void UserData::setName(const QString &newFirstName, const QString &newLastName, const QString &newPhoneName, const QString &newUsername) {
+void UserData::setName(
+		const QString &newFirstName,
+		const QString &newLastName,
+		const QString &newPhoneName,
+		const QString &newUsername) {
 	bool changeName = !newFirstName.isEmpty() || !newLastName.isEmpty();
 
 	QString newFullName;
@@ -245,7 +273,14 @@ void UserData::setName(const QString &newFirstName, const QString &newLastName,
 			firstName = newFirstName;
 			lastName = newLastName;
 		}
-		newFullName = lastName.isEmpty() ? firstName : tr::lng_full_name(tr::now, lt_first_name, firstName, lt_last_name, lastName);
+		newFullName = lastName.isEmpty()
+			? firstName
+			: tr::lng_full_name(
+				tr::now,
+				lt_first_name,
+				firstName,
+				lt_last_name,
+				lastName);
 	}
 	updateNameDelayed(newFullName, newPhoneName, newUsername);
 }
@@ -372,8 +407,14 @@ void UserData::setBotInfo(const MTPBotInfo &info) {
 				= botInfo->botAppColorBodyNight
 				= QColor(0, 0, 0, 0);
 		}
+		const auto changedVerifierSettings = ApplyBotVerifierSettings(
+			botInfo.get(),
+			d.vverifier_settings());
 
-		if (changedCommands || changedButton || privacyChanged) {
+		if (changedCommands
+			|| changedButton
+			|| privacyChanged
+			|| changedVerifierSettings) {
 			owner().botCommandsChanged(this);
 		}
 	} break;
diff --git a/Telegram/SourceFiles/data/data_user.h b/Telegram/SourceFiles/data/data_user.h
index b599f1ed3..5be906976 100644
--- a/Telegram/SourceFiles/data/data_user.h
+++ b/Telegram/SourceFiles/data/data_user.h
@@ -31,6 +31,20 @@ struct StarRefProgram {
 		StarRefProgram) = default;
 };
 
+struct BotVerifierSettings {
+	DocumentId iconId = 0;
+	QString company;
+	QString customDescription;
+
+	explicit operator bool() const {
+		return iconId != 0;
+	}
+
+	friend inline bool operator==(
+		const BotVerifierSettings &a,
+		const BotVerifierSettings &b) = default;
+};
+
 struct BotInfo {
 	BotInfo();
 
@@ -57,6 +71,7 @@ struct BotInfo {
 	ChatAdminRights channelAdminRights;
 
 	StarRefProgram starRefProgram;
+	std::unique_ptr<BotVerifierSettings> verifierSettings;
 
 	int version = 0;
 	int descriptionVersion = 0;
diff --git a/Telegram/SourceFiles/info/info.style b/Telegram/SourceFiles/info/info.style
index 1ae86e9d3..ffcd02f46 100644
--- a/Telegram/SourceFiles/info/info.style
+++ b/Telegram/SourceFiles/info/info.style
@@ -508,7 +508,7 @@ infoOpenApp: RoundButton(defaultActiveButton) {
 }
 infoOpenAppMargin: margins(16px, 12px, 16px, 12px);
 
-infoPersonalChannelIconPosition: point(25px, 20px);
+infoPersonalChannelIconPosition: point(25px, 12px);
 infoPersonalChannelNameLabel: FlatLabel(infoProfileStatus) {
 	textFg: windowBoldFg;
 	style: semiboldTextStyle;
diff --git a/Telegram/SourceFiles/info/profile/info_profile_actions.cpp b/Telegram/SourceFiles/info/profile/info_profile_actions.cpp
index a6716aa91..c6085492c 100644
--- a/Telegram/SourceFiles/info/profile/info_profile_actions.cpp
+++ b/Telegram/SourceFiles/info/profile/info_profile_actions.cpp
@@ -19,6 +19,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
 #include "boxes/peers/edit_contact_box.h"
 #include "boxes/peers/edit_participants_box.h"
 #include "boxes/peers/edit_peer_info_box.h"
+#include "boxes/peers/verify_peers_box.h"
 #include "boxes/report_messages_box.h"
 #include "boxes/share_box.h"
 #include "boxes/star_gift_box.h"
@@ -960,6 +961,7 @@ private:
 	object_ptr<Ui::RpWidget> setupPersonalChannel(not_null<UserData*> user);
 	object_ptr<Ui::RpWidget> setupInfo();
 	object_ptr<Ui::RpWidget> setupMuteToggle();
+	void setupAboutVerification();
 	void setupMainApp();
 	void setupBotPermissions();
 	void setupMainButtons();
@@ -1560,8 +1562,6 @@ object_ptr<Ui::RpWidget> DetailsFiller::setupPersonalChannel(
 		}));
 		onlyChannelWrap->finishAnimating();
 
-		Ui::AddDivider(onlyChannelWrap->entity());
-
 		auto text = rpl::duplicate(
 			channel
 		) | rpl::map([=](ChannelData *channel) {
@@ -1591,6 +1591,8 @@ object_ptr<Ui::RpWidget> DetailsFiller::setupPersonalChannel(
 			onlyChannelWrap,
 			st::infoIconMediaChannel,
 			st::infoPersonalChannelIconPosition);
+
+		Ui::AddDivider(onlyChannelWrap->entity());
 	}
 
 	{
@@ -1620,7 +1622,7 @@ object_ptr<Ui::RpWidget> DetailsFiller::setupPersonalChannel(
 
 			messageChannelWrap->toggle(false, anim::type::instant);
 			clear();
-			Ui::AddDivider(messageChannelWrap->entity());
+
 			Ui::AddSkip(messageChannelWrap->entity());
 
 			const auto inner = messageChannelWrap->entity()->add(
@@ -1751,6 +1753,7 @@ object_ptr<Ui::RpWidget> DetailsFiller::setupPersonalChannel(
 			}
 			inner->setAttribute(Qt::WA_TransparentForMouseEvents);
 			Ui::AddSkip(messageChannelWrap->entity());
+			Ui::AddDivider(messageChannelWrap->entity());
 
 			Ui::ToggleChildrenVisibility(messageChannelWrap->entity(), true);
 			Ui::ToggleChildrenVisibility(line, true);
@@ -1835,6 +1838,35 @@ object_ptr<Ui::RpWidget> DetailsFiller::setupMuteToggle() {
 	return result;
 }
 
+void DetailsFiller::setupAboutVerification() {
+	const auto peer = _peer;
+	const auto inner = _wrap->add(object_ptr<Ui::VerticalLayout>(_wrap));
+	peer->session().changes().peerFlagsValue(
+		peer,
+		Data::PeerUpdate::Flag::VerifyInfo
+	) | rpl::start_with_next([=] {
+		const auto info = peer->verifyDetails();
+		while (inner->count()) {
+			delete inner->widgetAt(0);
+		}
+		if (!info) {
+			Ui::AddDivider(inner);
+		} else if (!info->description.empty()) {
+			Ui::AddDividerText(inner, rpl::single(info->description));
+		} else {
+			const auto phrases = PeerVerifyPhrases(peer);
+			Ui::AddDividerText(
+				inner,
+				(_peer->verifiedByTelegram()
+					? phrases.telegram()
+					: phrases.company(
+						lt_company,
+						rpl::single(info->company))));
+		}
+		inner->resizeToWidth(inner->width());
+	}, inner->lifetime());
+}
+
 void DetailsFiller::setupMainApp() {
 	const auto button = _wrap->add(
 		object_ptr<Ui::RoundButton>(
@@ -2103,10 +2135,14 @@ Ui::MultiSlideTracker DetailsFiller::fillDiscussionButtons(
 object_ptr<Ui::RpWidget> DetailsFiller::fill() {
 	Expects(!_topic || !_topic->creating());
 
+	if (!_topic) {
+		setupAboutVerification();
+	} else {
+		add(object_ptr<Ui::BoxContentDivider>(_wrap));
+	}
 	if (const auto user = _peer->asUser()) {
 		add(setupPersonalChannel(user));
 	}
-	add(object_ptr<Ui::BoxContentDivider>(_wrap));
 	add(CreateSkipWidget(_wrap));
 	add(setupInfo());
 	if (const auto user = _peer->asUser()) {
diff --git a/Telegram/SourceFiles/info/profile/info_profile_badge.cpp b/Telegram/SourceFiles/info/profile/info_profile_badge.cpp
index ee2c1a035..b8c74f4c1 100644
--- a/Telegram/SourceFiles/info/profile/info_profile_badge.cpp
+++ b/Telegram/SourceFiles/info/profile/info_profile_badge.cpp
@@ -37,9 +37,7 @@ namespace {
 		}
 		return Badge::Content{
 			badge,
-			(emojiStatusId
-				? Data::SerializeCustomEmojiId(emojiStatusId)
-				: QString()),
+			emojiStatusId ? emojiStatusId : DocumentId(),
 		};
 	});
 }
@@ -120,14 +118,14 @@ void Badge::setContent(Content content) {
 	case BadgeType::Premium: {
 		const auto id = _content.emojiStatusId;
 		const auto innerId = _content.emojiStatusInnerId;
-		if (!id.isEmpty() || !innerId.isEmpty()) {
-			_emojiStatus = !id.isEmpty()
+		if (id || innerId) {
+			_emojiStatus = id
 				? _session->data().customEmojiManager().create(
 					id,
 					[raw = _view.data()] { raw->update(); },
 					sizeTag())
 				: nullptr;
-			_statusInner = !innerId.isEmpty()
+			_statusInner = innerId
 				? _session->data().customEmojiManager().create(
 					innerId,
 					[raw = _view.data()] { raw->update(); },
diff --git a/Telegram/SourceFiles/info/profile/info_profile_badge.h b/Telegram/SourceFiles/info/profile/info_profile_badge.h
index ea24f2fb6..efff823cb 100644
--- a/Telegram/SourceFiles/info/profile/info_profile_badge.h
+++ b/Telegram/SourceFiles/info/profile/info_profile_badge.h
@@ -58,8 +58,8 @@ public:
 
 	struct Content {
 		BadgeType badge = BadgeType::None;
-		QString emojiStatusId;
-		QString emojiStatusInnerId;
+		DocumentId emojiStatusId = 0;
+		DocumentId emojiStatusInnerId = 0;
 
 		friend inline constexpr bool operator==(Content, Content) = default;
 	};
diff --git a/Telegram/SourceFiles/info/profile/info_profile_cover.cpp b/Telegram/SourceFiles/info/profile/info_profile_cover.cpp
index 0b69500b4..3934cbb7b 100644
--- a/Telegram/SourceFiles/info/profile/info_profile_cover.cpp
+++ b/Telegram/SourceFiles/info/profile/info_profile_cover.cpp
@@ -94,6 +94,16 @@ auto ChatStatusText(int fullCount, int onlineCount, bool isGroup) {
 		: st::infoProfileCover;
 }
 
+[[nodiscard]] QMargins LargeCustomEmojiMargins() {
+	const auto ratio = style::DevicePixelRatio();
+	const auto emoji = Ui::Emoji::GetSizeLarge() / ratio;
+	const auto size = Data::FrameSizeFromTag(Data::CustomEmojiSizeTag::Large)
+		/ ratio;
+	const auto left = (size - emoji) / 2;
+	const auto right = size - emoji - left;
+	return { left, left, right, right };
+}
+
 } // namespace
 
 TopicIconView::TopicIconView(
@@ -302,8 +312,8 @@ Cover::Cover(
 		const auto details = peer->verifyDetails();
 		return Badge::Content{
 			.badge = details ? BadgeType::Verified : BadgeType::None,
-			.emojiStatusId = details ? details->iconBgId : QString(),
-			.emojiStatusInnerId = details ? details->iconFgId : QString(),
+			.emojiStatusId = details ? details->iconBgId : DocumentId(),
+			.emojiStatusInnerId = details ? details->iconFgId : DocumentId(),
 		};
 	});
 }
@@ -731,10 +741,16 @@ void Cover::refreshNameGeometry(int newWidth) {
 	auto nameLeft = _st.nameLeft;
 	const auto badgeTop = _st.nameTop;
 	const auto badgeBottom = _st.nameTop + _name->height();
-	_verify->move(nameLeft, badgeTop, badgeBottom);
+	const auto margins = LargeCustomEmojiMargins();
+
+	_verify->move(nameLeft - margins.left(), badgeTop, badgeBottom);
 	if (const auto widget = _verify->widget()) {
-		nameLeft += widget->width() + st::infoVerifiedCheckPosition.x();
-		nameWidth -= widget->width() + st::infoVerifiedCheckPosition.x();
+		const auto skip = widget->width()
+			+ st::infoVerifiedCheckPosition.x()
+			- margins.left()
+			- margins.right();
+		nameLeft += skip;
+		nameWidth -= skip;
 	}
 	_name->resizeToNaturalWidth(nameWidth);
 	_name->moveToLeft(nameLeft, _st.nameTop, newWidth);
diff --git a/Telegram/SourceFiles/ui/unread_badge.cpp b/Telegram/SourceFiles/ui/unread_badge.cpp
index dee9af528..30956a01e 100644
--- a/Telegram/SourceFiles/ui/unread_badge.cpp
+++ b/Telegram/SourceFiles/ui/unread_badge.cpp
@@ -225,16 +225,18 @@ bool PeerBadge::ready(const VerifyDetails *details) const {
 	} else if (!_verifiedData) {
 		return false;
 	}
-	if (details->iconBgId.isEmpty()) {
+	if (!details->iconBgId) {
 		_verifiedData->bg = nullptr;
 	} else if (!_verifiedData->bg
-		|| _verifiedData->bg->entityData() != details->iconBgId) {
+		|| (_verifiedData->bg->entityData()
+			!= Data::SerializeCustomEmojiId(details->iconBgId))) {
 		return false;
 	}
-	if (details->iconFgId.isEmpty()) {
+	if (!details->iconFgId) {
 		_verifiedData->fg = nullptr;
 	} else if (!_verifiedData->fg
-		|| _verifiedData->fg->entityData() != details->iconFgId) {
+		|| (_verifiedData->fg->entityData()
+			!= Data::SerializeCustomEmojiId(details->iconFgId))) {
 		return false;
 	}
 	return true;
@@ -247,11 +249,15 @@ void PeerBadge::set(
 	if (!_verifiedData) {
 		_verifiedData = std::make_unique<VerifiedData>();
 	}
-	if (!details->iconBgId.isEmpty()) {
-		_verifiedData->bg = factory(details->iconBgId, repaint);
+	if (details->iconBgId) {
+		_verifiedData->bg = factory(
+			Data::SerializeCustomEmojiId(details->iconBgId),
+			repaint);
 	}
-	if (!details->iconFgId.isEmpty()) {
-		_verifiedData->fg = factory(details->iconFgId, repaint);
+	if (details->iconFgId) {
+		_verifiedData->fg = factory(
+			Data::SerializeCustomEmojiId(details->iconFgId),
+			repaint);
 	}
 }
 
diff --git a/Telegram/SourceFiles/ui/unread_badge.h b/Telegram/SourceFiles/ui/unread_badge.h
index 2e68869ad..cc8b4ce13 100644
--- a/Telegram/SourceFiles/ui/unread_badge.h
+++ b/Telegram/SourceFiles/ui/unread_badge.h
@@ -34,13 +34,13 @@ private:
 
 struct VerifyDetails {
 	UserId botId = 0;
-	QString iconBgId;
-	QString iconFgId;
+	DocumentId iconBgId = 0;
+	DocumentId iconFgId = 0;
 	QString company;
 	TextWithEntities description;
 
 	explicit operator bool() const {
-		return !iconBgId.isEmpty() || !iconFgId.isEmpty();
+		return iconBgId || iconFgId;
 	}
 	friend inline bool operator==(
 		const VerifyDetails &,