Repository: aiogram/aiogram
Pull request #890 (2022-04-16 21:07:30)

[3.x] Bot API 6.0
# Description

Added full support of Bot API 6.0

Without backward compatibility with renamed `voice_` to `video_` fields.

## Type of change

- [x] Documentation (typos, code examples or any documentation update)
- [x] New feature (non-breaking change which adds functionality)
- [x] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [x] This change requires a documentation update

# Checklist:

- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my own code
- [x] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my feature works
- [x] New and existing unit tests pass locally with my changes



Patch:
diff --git a/.readthedocs.yml b/.readthedocs.yml
index 1efe11cb..e03323e6 100644
--- a/.readthedocs.yml
+++ b/.readthedocs.yml
@@ -12,3 +12,4 @@ python:
       path: .
       extra_requirements:
         - docs
+        - redis
diff --git a/CHANGES/890.feature.rst b/CHANGES/890.feature.rst
new file mode 100644
index 00000000..10c60c05
--- /dev/null
+++ b/CHANGES/890.feature.rst
@@ -0,0 +1,1 @@
+Added full support of `Telegram Bot API 6.0 <https://core.telegram.org/bots/api-changelog#april-16-2022>`_
diff --git a/LICENSE b/LICENSE
index 872283c6..f9721b14 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2017-2019 Alex Root Junior
+Copyright (c) 2017-2022 Alex Root Junior
 
 Permission is hereby granted, free of charge, to any person obtaining a copy of this
 software and associated documentation files (the "Software"), to deal in the Software
diff --git a/aiogram/client/bot.py b/aiogram/client/bot.py
index 94feb7b8..16453574 100644
--- a/aiogram/client/bot.py
+++ b/aiogram/client/bot.py
@@ -27,6 +27,7 @@ from ..methods import (
     AnswerInlineQuery,
     AnswerPreCheckoutQuery,
     AnswerShippingQuery,
+    AnswerWebAppQuery,
     ApproveChatJoinRequest,
     BanChatMember,
     BanChatSenderChat,
@@ -54,10 +55,12 @@ from ..methods import (
     GetChatMember,
     GetChatMemberCount,
     GetChatMembersCount,
+    GetChatMenuButton,
     GetFile,
     GetGameHighScores,
     GetMe,
     GetMyCommands,
+    GetMyDefaultAdministratorRights,
     GetStickerSet,
     GetUpdates,
     GetUserProfilePhotos,
@@ -89,12 +92,14 @@ from ..methods import (
     SendVoice,
     SetChatAdministratorCustomTitle,
     SetChatDescription,
+    SetChatMenuButton,
     SetChatPermissions,
     SetChatPhoto,
     SetChatStickerSet,
     SetChatTitle,
     SetGameScore,
     SetMyCommands,
+    SetMyDefaultAdministratorRights,
     SetPassportDataErrors,
     SetStickerPositionInSet,
     SetStickerSetThumb,
@@ -113,6 +118,7 @@ from ..types import (
     BotCommand,
     BotCommandScope,
     Chat,
+    ChatAdministratorRights,
     ChatInviteLink,
     ChatMemberAdministrator,
     ChatMemberBanned,
@@ -135,6 +141,7 @@ from ..types import (
     InputMediaVideo,
     LabeledPrice,
     MaskPosition,
+    MenuButton,
     Message,
     MessageEntity,
     MessageId,
@@ -142,6 +149,7 @@ from ..types import (
     Poll,
     ReplyKeyboardMarkup,
     ReplyKeyboardRemove,
+    SentWebAppMessage,
     ShippingOption,
     StickerSet,
     Update,
@@ -1618,7 +1626,7 @@ class Bot(ContextInstanceMixin["Bot"]):
         can_post_messages: Optional[bool] = None,
         can_edit_messages: Optional[bool] = None,
         can_delete_messages: Optional[bool] = None,
-        can_manage_voice_chats: Optional[bool] = None,
+        can_manage_video_chats: Optional[bool] = None,
         can_restrict_members: Optional[bool] = None,
         can_promote_members: Optional[bool] = None,
         can_change_info: Optional[bool] = None,
@@ -1638,7 +1646,7 @@ class Bot(ContextInstanceMixin["Bot"]):
         :param can_post_messages: Pass :code:`True`, if the administrator can create channel posts, channels only
         :param can_edit_messages: Pass :code:`True`, if the administrator can edit messages of other users and can pin messages, channels only
         :param can_delete_messages: Pass :code:`True`, if the administrator can delete messages of other users
-        :param can_manage_voice_chats: Pass :code:`True`, if the administrator can manage voice chats
+        :param can_manage_video_chats: Pass :code:`True`, if the administrator can manage video chats
         :param can_restrict_members: Pass :code:`True`, if the administrator can restrict, ban or unban chat members
         :param can_promote_members: Pass :code:`True`, if the administrator can add new administrators with a subset of their own privileges or demote administrators that he has promoted, directly or indirectly (promoted by administrators that were appointed by him)
         :param can_change_info: Pass :code:`True`, if the administrator can change chat title, photo and other settings
@@ -1655,7 +1663,7 @@ class Bot(ContextInstanceMixin["Bot"]):
             can_post_messages=can_post_messages,
             can_edit_messages=can_edit_messages,
             can_delete_messages=can_delete_messages,
-            can_manage_voice_chats=can_manage_voice_chats,
+            can_manage_video_chats=can_manage_video_chats,
             can_restrict_members=can_restrict_members,
             can_promote_members=can_promote_members,
             can_change_info=can_change_info,
@@ -2344,6 +2352,88 @@ class Bot(ContextInstanceMixin["Bot"]):
         )
         return await self(call, request_timeout=request_timeout)
 
+    async def set_chat_menu_button(
+        self,
+        chat_id: Optional[int] = None,
+        menu_button: Optional[MenuButton] = None,
+        request_timeout: Optional[int] = None,
+    ) -> bool:
+        """
+        Use this method to change the bot's menu button in a private chat, or the default menu button. Returns :code:`True` on success.
+
+        Source: https://core.telegram.org/bots/api#setchatmenubutton
+
+        :param chat_id: Unique identifier for the target private chat. If not specified, default bot's menu button will be changed
+        :param menu_button: A JSON-serialized object for the new bot's menu button. Defaults to :class:`aiogram.types.menu_button_default.MenuButtonDefault`
+        :param request_timeout: Request timeout
+        :return: Returns True on success.
+        """
+        call = SetChatMenuButton(
+            chat_id=chat_id,
+            menu_button=menu_button,
+        )
+        return await self(call, request_timeout=request_timeout)
+
+    async def get_chat_menu_button(
+        self,
+        chat_id: Optional[int] = None,
+        request_timeout: Optional[int] = None,
+    ) -> MenuButton:
+        """
+        Use this method to get the current value of the bot's menu button in a private chat, or the default menu button. Returns :class:`aiogram.types.menu_button.MenuButton` on success.
+
+        Source: https://core.telegram.org/bots/api#getchatmenubutton
+
+        :param chat_id: Unique identifier for the target private chat. If not specified, default bot's menu button will be returned
+        :param request_timeout: Request timeout
+        :return: Returns MenuButton on success.
+        """
+        call = GetChatMenuButton(
+            chat_id=chat_id,
+        )
+        return await self(call, request_timeout=request_timeout)
+
+    async def set_my_default_administrator_rights(
+        self,
+        rights: Optional[ChatAdministratorRights] = None,
+        for_channels: Optional[bool] = None,
+        request_timeout: Optional[int] = None,
+    ) -> bool:
+        """
+        Use this method to change the default administrator rights requested by the bot when it's added as an administrator to groups or channels. These rights will be suggested to users, but they are are free to modify the list before adding the bot. Returns :code:`True` on success.
+
+        Source: https://core.telegram.org/bots/api#setmydefaultadministratorrights
+
+        :param rights: A JSON-serialized object describing new default administrator rights. If not specified, the default administrator rights will be cleared.
+        :param for_channels: Pass :code:`True` to change the default administrator rights of the bot in channels. Otherwise, the default administrator rights of the bot for groups and supergroups will be changed.
+        :param request_timeout: Request timeout
+        :return: Returns True on success.
+        """
+        call = SetMyDefaultAdministratorRights(
+            rights=rights,
+            for_channels=for_channels,
+        )
+        return await self(call, request_timeout=request_timeout)
+
+    async def get_my_default_administrator_rights(
+        self,
+        for_channels: Optional[bool] = None,
+        request_timeout: Optional[int] = None,
+    ) -> ChatAdministratorRights:
+        """
+        Use this method to get the current default administrator rights of the bot. Returns :class:`aiogram.types.chat_administrator_rights.ChatAdministratorRights` on success.
+
+        Source: https://core.telegram.org/bots/api#getmydefaultadministratorrights
+
+        :param for_channels: Pass :code:`True` to get default administrator rights of the bot in channels. Otherwise, default administrator rights of the bot for groups and supergroups will be returned.
+        :param request_timeout: Request timeout
+        :return: Returns ChatAdministratorRights on success.
+        """
+        call = GetMyDefaultAdministratorRights(
+            for_channels=for_channels,
+        )
+        return await self(call, request_timeout=request_timeout)
+
     # =============================================================================================
     # Group: Updating messages
     # Source: https://core.telegram.org/bots/api#updating-messages
@@ -2656,7 +2746,7 @@ class Bot(ContextInstanceMixin["Bot"]):
         Source: https://core.telegram.org/bots/api#createnewstickerset
 
         :param user_id: User identifier of created sticker set owner
-        :param name: Short name of sticker set, to be used in :code:`t.me/addstickers/` URLs (e.g., *animals*). Can contain only english letters, digits and underscores. Must begin with a letter, can't contain consecutive underscores and must end in *'_by_<bot username>'*. *<bot_username>* is case insensitive. 1-64 characters.
+        :param name: Short name of sticker set, to be used in :code:`t.me/addstickers/` URLs (e.g., *animals*). Can contain only english letters, digits and underscores. Must begin with a letter, can't contain consecutive underscores and must end in :code:`"_by_<bot_username>"`. :code:`<bot_username>` is case insensitive. 1-64 characters.
         :param title: Sticker set title, 1-64 characters
         :param emojis: One or more emoji corresponding to the sticker
         :param png_sticker: **PNG** image with the sticker, must be up to 512 kilobytes in size, dimensions must not exceed 512px, and either width or height must be exactly 512px. Pass a *file_id* as a String to send a file that already exists on the Telegram servers, pass an HTTP URL as a String for Telegram to get a file from the Internet, or upload a new one using multipart/form-data. :ref:`More info on Sending Files » <sending-files>`
@@ -2827,6 +2917,28 @@ class Bot(ContextInstanceMixin["Bot"]):
         )
         return await self(call, request_timeout=request_timeout)
 
+    async def answer_web_app_query(
+        self,
+        web_app_query_id: str,
+        result: InlineQueryResult,
+        request_timeout: Optional[int] = None,
+    ) -> SentWebAppMessage:
+        """
+        Use this method to set the result of an interaction with a `Web App <https://core.telegram.org/bots/webapps>`_ and send a corresponding message on behalf of the user to the chat from which the query originated. On success, a :class:`aiogram.types.sent_web_app_message.SentWebAppMessage` object is returned.
+
+        Source: https://core.telegram.org/bots/api#answerwebappquery
+
+        :param web_app_query_id: Unique identifier for the query to be answered
+        :param result: A JSON-serialized object describing the message to be sent
+        :param request_timeout: Request timeout
+        :return: On success, a SentWebAppMessage object is returned.
+        """
+        call = AnswerWebAppQuery(
+            web_app_query_id=web_app_query_id,
+            result=result,
+        )
+        return await self(call, request_timeout=request_timeout)
+
     # =============================================================================================
     # Group: Payments
     # Source: https://core.telegram.org/bots/api#payments
diff --git a/aiogram/dispatcher/dispatcher.py b/aiogram/dispatcher/dispatcher.py
index 9b84262c..f18deabf 100644
--- a/aiogram/dispatcher/dispatcher.py
+++ b/aiogram/dispatcher/dispatcher.py
@@ -80,20 +80,20 @@ class Dispatcher(Router):
             self.update.outer_middleware(self.fsm)
         self.shutdown.register(self.fsm.close)
 
-        self._data: Dict[str, Any] = {}
+        self.workflow_data: Dict[str, Any] = {}
         self._running_lock = Lock()
 
     def __getitem__(self, item: str) -> Any:
-        return self._data[item]
+        return self.workflow_data[item]
 
     def __setitem__(self, key: str, value: Any) -> None:
-        self._data[key] = value
+        self.workflow_data[key] = value
 
     def __delitem__(self, key: str) -> None:
-        del self._data[key]
+        del self.workflow_data[key]
 
     def get(self, key: str, /, default: Optional[Any] = None) -> Optional[Any]:
-        return self._data.get(key, default)
+        return self.workflow_data.get(key, default)
 
     @property
     def storage(self) -> BaseStorage:
@@ -136,7 +136,7 @@ class Dispatcher(Router):
                 self.update.trigger,
                 update,
                 {
-                    **self._data,
+                    **self.workflow_data,
                     **kwargs,
                     "bot": bot,
                 },
diff --git a/aiogram/dispatcher/filters/text.py b/aiogram/dispatcher/filters/text.py
index 3dd36fbd..cd7195dd 100644
--- a/aiogram/dispatcher/filters/text.py
+++ b/aiogram/dispatcher/filters/text.py
@@ -6,7 +6,7 @@ from aiogram.dispatcher.filters import BaseFilter
 from aiogram.types import CallbackQuery, InlineQuery, Message, Poll
 
 if TYPE_CHECKING:
-    from aiogram.utils.i18n.lazy_proxy import LazyProxy
+    from aiogram.utils.i18n.lazy_proxy import LazyProxy  # NOQA
 
 TextType = Union[str, "LazyProxy"]
 
diff --git a/aiogram/dispatcher/webhook/aiohttp_server.py b/aiogram/dispatcher/webhook/aiohttp_server.py
index a8d084f8..eb3b2c5b 100644
--- a/aiogram/dispatcher/webhook/aiohttp_server.py
+++ b/aiogram/dispatcher/webhook/aiohttp_server.py
@@ -26,6 +26,7 @@ def setup_application(app: Application, dispatcher: Dispatcher, /, **kwargs: Any
         "app": app,
         "dispatcher": dispatcher,
         **kwargs,
+        **dispatcher.workflow_data,
     }
 
     async def on_startup(*a: Any, **kw: Any) -> None:  # pragma: no cover
diff --git a/aiogram/methods/__init__.py b/aiogram/methods/__init__.py
index 085044ae..f7b75066 100644
--- a/aiogram/methods/__init__.py
+++ b/aiogram/methods/__init__.py
@@ -3,6 +3,7 @@ from .answer_callback_query import AnswerCallbackQuery
 from .answer_inline_query import AnswerInlineQuery
 from .answer_pre_checkout_query import AnswerPreCheckoutQuery
 from .answer_shipping_query import AnswerShippingQuery
+from .answer_web_app_query import AnswerWebAppQuery
 from .approve_chat_join_request import ApproveChatJoinRequest
 from .ban_chat_member import BanChatMember
 from .ban_chat_sender_chat import BanChatSenderChat
@@ -31,10 +32,12 @@ from .get_chat_administrators import GetChatAdministrators
 from .get_chat_member import GetChatMember
 from .get_chat_member_count import GetChatMemberCount
 from .get_chat_members_count import GetChatMembersCount
+from .get_chat_menu_button import GetChatMenuButton
 from .get_file import GetFile
 from .get_game_high_scores import GetGameHighScores
 from .get_me import GetMe
 from .get_my_commands import GetMyCommands
+from .get_my_default_administrator_rights import GetMyDefaultAdministratorRights
 from .get_sticker_set import GetStickerSet
 from .get_updates import GetUpdates
 from .get_user_profile_photos import GetUserProfilePhotos
@@ -66,12 +69,14 @@ from .send_video_note import SendVideoNote
 from .send_voice import SendVoice
 from .set_chat_administrator_custom_title import SetChatAdministratorCustomTitle
 from .set_chat_description import SetChatDescription
+from .set_chat_menu_button import SetChatMenuButton
 from .set_chat_permissions import SetChatPermissions
 from .set_chat_photo import SetChatPhoto
 from .set_chat_sticker_set import SetChatStickerSet
 from .set_chat_title import SetChatTitle
 from .set_game_score import SetGameScore
 from .set_my_commands import SetMyCommands
+from .set_my_default_administrator_rights import SetMyDefaultAdministratorRights
 from .set_passport_data_errors import SetPassportDataErrors
 from .set_sticker_position_in_set import SetStickerPositionInSet
 from .set_sticker_set_thumb import SetStickerSetThumb
@@ -150,6 +155,10 @@ __all__ = (
     "SetMyCommands",
     "DeleteMyCommands",
     "GetMyCommands",
+    "SetChatMenuButton",
+    "GetChatMenuButton",
+    "SetMyDefaultAdministratorRights",
+    "GetMyDefaultAdministratorRights",
     "EditMessageText",
     "EditMessageCaption",
     "EditMessageMedia",
@@ -165,6 +174,7 @@ __all__ = (
     "DeleteStickerFromSet",
     "SetStickerSetThumb",
     "AnswerInlineQuery",
+    "AnswerWebAppQuery",
     "SendInvoice",
     "AnswerShippingQuery",
     "AnswerPreCheckoutQuery",
diff --git a/aiogram/methods/answer_web_app_query.py b/aiogram/methods/answer_web_app_query.py
new file mode 100644
index 00000000..3211ed38
--- /dev/null
+++ b/aiogram/methods/answer_web_app_query.py
@@ -0,0 +1,31 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, Any, Dict
+
+from ..types import InlineQueryResult, SentWebAppMessage
+from .base import Request, TelegramMethod, prepare_parse_mode
+
+if TYPE_CHECKING:
+    from ..client.bot import Bot
+
+
+class AnswerWebAppQuery(TelegramMethod[SentWebAppMessage]):
+    """
+    Use this method to set the result of an interaction with a `Web App <https://core.telegram.org/bots/webapps>`_ and send a corresponding message on behalf of the user to the chat from which the query originated. On success, a :class:`aiogram.types.sent_web_app_message.SentWebAppMessage` object is returned.
+
+    Source: https://core.telegram.org/bots/api#answerwebappquery
+    """
+
+    __returning__ = SentWebAppMessage
+
+    web_app_query_id: str
+    """Unique identifier for the query to be answered"""
+    result: InlineQueryResult
+    """A JSON-serialized object describing the message to be sent"""
+
+    def build_request(self, bot: Bot) -> Request:
+        data: Dict[str, Any] = self.dict()
+        prepare_parse_mode(
+            bot, data["result"], parse_mode_property="parse_mode", entities_property="entities"
+        )
+        return Request(method="answerWebAppQuery", data=data)
diff --git a/aiogram/methods/create_new_sticker_set.py b/aiogram/methods/create_new_sticker_set.py
index 5c807963..5faab9ef 100644
--- a/aiogram/methods/create_new_sticker_set.py
+++ b/aiogram/methods/create_new_sticker_set.py
@@ -21,7 +21,7 @@ class CreateNewStickerSet(TelegramMethod[bool]):
     user_id: int
     """User identifier of created sticker set owner"""
     name: str
-    """Short name of sticker set, to be used in :code:`t.me/addstickers/` URLs (e.g., *animals*). Can contain only english letters, digits and underscores. Must begin with a letter, can't contain consecutive underscores and must end in *'_by_<bot username>'*. *<bot_username>* is case insensitive. 1-64 characters."""
+    """Short name of sticker set, to be used in :code:`t.me/addstickers/` URLs (e.g., *animals*). Can contain only english letters, digits and underscores. Must begin with a letter, can't contain consecutive underscores and must end in :code:`"_by_<bot_username>"`. :code:`<bot_username>` is case insensitive. 1-64 characters."""
     title: str
     """Sticker set title, 1-64 characters"""
     emojis: str
diff --git a/aiogram/methods/get_chat_menu_button.py b/aiogram/methods/get_chat_menu_button.py
new file mode 100644
index 00000000..e2a97134
--- /dev/null
+++ b/aiogram/methods/get_chat_menu_button.py
@@ -0,0 +1,27 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, Any, Dict, Optional, Union
+
+from ..types import MenuButton, MenuButtonCommands, MenuButtonDefault, MenuButtonWebApp
+from .base import Request, TelegramMethod
+
+if TYPE_CHECKING:
+    from ..client.bot import Bot
+
+
+class GetChatMenuButton(TelegramMethod[MenuButton]):
+    """
+    Use this method to get the current value of the bot's menu button in a private chat, or the default menu button. Returns :class:`aiogram.types.menu_button.MenuButton` on success.
+
+    Source: https://core.telegram.org/bots/api#getchatmenubutton
+    """
+
+    __returning__ = Union[MenuButtonDefault, MenuButtonWebApp, MenuButtonCommands]
+
+    chat_id: Optional[int] = None
+    """Unique identifier for the target private chat. If not specified, default bot's menu button will be returned"""
+
+    def build_request(self, bot: Bot) -> Request:
+        data: Dict[str, Any] = self.dict()
+
+        return Request(method="getChatMenuButton", data=data)
diff --git a/aiogram/methods/get_my_default_administrator_rights.py b/aiogram/methods/get_my_default_administrator_rights.py
new file mode 100644
index 00000000..53a8e494
--- /dev/null
+++ b/aiogram/methods/get_my_default_administrator_rights.py
@@ -0,0 +1,27 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, Any, Dict, Optional
+
+from ..types import ChatAdministratorRights
+from .base import Request, TelegramMethod
+
+if TYPE_CHECKING:
+    from ..client.bot import Bot
+
+
+class GetMyDefaultAdministratorRights(TelegramMethod[ChatAdministratorRights]):
+    """
+    Use this method to get the current default administrator rights of the bot. Returns :class:`aiogram.types.chat_administrator_rights.ChatAdministratorRights` on success.
+
+    Source: https://core.telegram.org/bots/api#getmydefaultadministratorrights
+    """
+
+    __returning__ = ChatAdministratorRights
+
+    for_channels: Optional[bool] = None
+    """Pass :code:`True` to get default administrator rights of the bot in channels. Otherwise, default administrator rights of the bot for groups and supergroups will be returned."""
+
+    def build_request(self, bot: Bot) -> Request:
+        data: Dict[str, Any] = self.dict()
+
+        return Request(method="getMyDefaultAdministratorRights", data=data)
diff --git a/aiogram/methods/promote_chat_member.py b/aiogram/methods/promote_chat_member.py
index f2d8374f..4fd1a0f2 100644
--- a/aiogram/methods/promote_chat_member.py
+++ b/aiogram/methods/promote_chat_member.py
@@ -31,8 +31,8 @@ class PromoteChatMember(TelegramMethod[bool]):
     """Pass :code:`True`, if the administrator can edit messages of other users and can pin messages, channels only"""
     can_delete_messages: Optional[bool] = None
     """Pass :code:`True`, if the administrator can delete messages of other users"""
-    can_manage_voice_chats: Optional[bool] = None
-    """Pass :code:`True`, if the administrator can manage voice chats"""
+    can_manage_video_chats: Optional[bool] = None
+    """Pass :code:`True`, if the administrator can manage video chats"""
     can_restrict_members: Optional[bool] = None
     """Pass :code:`True`, if the administrator can restrict, ban or unban chat members"""
     can_promote_members: Optional[bool] = None
diff --git a/aiogram/methods/set_chat_menu_button.py b/aiogram/methods/set_chat_menu_button.py
new file mode 100644
index 00000000..6578ec4e
--- /dev/null
+++ b/aiogram/methods/set_chat_menu_button.py
@@ -0,0 +1,29 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, Any, Dict, Optional
+
+from ..types import MenuButton
+from .base import Request, TelegramMethod
+
+if TYPE_CHECKING:
+    from ..client.bot import Bot
+
+
+class SetChatMenuButton(TelegramMethod[bool]):
+    """
+    Use this method to change the bot's menu button in a private chat, or the default menu button. Returns :code:`True` on success.
+
+    Source: https://core.telegram.org/bots/api#setchatmenubutton
+    """
+
+    __returning__ = bool
+
+    chat_id: Optional[int] = None
+    """Unique identifier for the target private chat. If not specified, default bot's menu button will be changed"""
+    menu_button: Optional[MenuButton] = None
+    """A JSON-serialized object for the new bot's menu button. Defaults to :class:`aiogram.types.menu_button_default.MenuButtonDefault`"""
+
+    def build_request(self, bot: Bot) -> Request:
+        data: Dict[str, Any] = self.dict()
+
+        return Request(method="setChatMenuButton", data=data)
diff --git a/aiogram/methods/set_my_default_administrator_rights.py b/aiogram/methods/set_my_default_administrator_rights.py
new file mode 100644
index 00000000..84341180
--- /dev/null
+++ b/aiogram/methods/set_my_default_administrator_rights.py
@@ -0,0 +1,29 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, Any, Dict, Optional
+
+from ..types import ChatAdministratorRights
+from .base import Request, TelegramMethod
+
+if TYPE_CHECKING:
+    from ..client.bot import Bot
+
+
+class SetMyDefaultAdministratorRights(TelegramMethod[bool]):
+    """
+    Use this method to change the default administrator rights requested by the bot when it's added as an administrator to groups or channels. These rights will be suggested to users, but they are are free to modify the list before adding the bot. Returns :code:`True` on success.
+
+    Source: https://core.telegram.org/bots/api#setmydefaultadministratorrights
+    """
+
+    __returning__ = bool
+
+    rights: Optional[ChatAdministratorRights] = None
+    """A JSON-serialized object describing new default administrator rights. If not specified, the default administrator rights will be cleared."""
+    for_channels: Optional[bool] = None
+    """Pass :code:`True` to change the default administrator rights of the bot in channels. Otherwise, the default administrator rights of the bot for groups and supergroups will be changed."""
+
+    def build_request(self, bot: Bot) -> Request:
+        data: Dict[str, Any] = self.dict()
+
+        return Request(method="setMyDefaultAdministratorRights", data=data)
diff --git a/aiogram/types/__init__.py b/aiogram/types/__init__.py
index ebf4c839..ec04b855 100644
--- a/aiogram/types/__init__.py
+++ b/aiogram/types/__init__.py
@@ -13,6 +13,7 @@ from .bot_command_scope_default import BotCommandScopeDefault
 from .callback_game import CallbackGame
 from .callback_query import CallbackQuery
 from .chat import Chat
+from .chat_administrator_rights import ChatAdministratorRights
 from .chat_invite_link import ChatInviteLink
 from .chat_join_request import ChatJoinRequest
 from .chat_location import ChatLocation
@@ -81,6 +82,10 @@ from .labeled_price import LabeledPrice
 from .location import Location
 from .login_url import LoginUrl
 from .mask_position import MaskPosition
+from .menu_button import MenuButton
+from .menu_button_commands import MenuButtonCommands
+from .menu_button_default import MenuButtonDefault
+from .menu_button_web_app import MenuButtonWebApp
 from .message import ContentType, Message
 from .message_auto_delete_timer_changed import MessageAutoDeleteTimerChanged
 from .message_entity import MessageEntity
@@ -107,6 +112,7 @@ from .proximity_alert_triggered import ProximityAlertTriggered
 from .reply_keyboard_markup import ReplyKeyboardMarkup
 from .reply_keyboard_remove import ReplyKeyboardRemove
 from .response_parameters import ResponseParameters
+from .sent_web_app_message import SentWebAppMessage
 from .shipping_address import ShippingAddress
 from .shipping_option import ShippingOption
 from .shipping_query import ShippingQuery
@@ -118,12 +124,14 @@ from .user import User
 from .user_profile_photos import UserProfilePhotos
 from .venue import Venue
 from .video import Video
+from .video_chat_ended import VideoChatEnded
+from .video_chat_participants_invited import VideoChatParticipantsInvited
+from .video_chat_scheduled import VideoChatScheduled
+from .video_chat_started import VideoChatStarted
 from .video_note import VideoNote
 from .voice import Voice
-from .voice_chat_ended import VoiceChatEnded
-from .voice_chat_participants_invited import VoiceChatParticipantsInvited
-from .voice_chat_scheduled import VoiceChatScheduled
-from .voice_chat_started import VoiceChatStarted
+from .web_app_data import WebAppData
+from .web_app_info import WebAppInfo
 from .webhook_info import WebhookInfo
 
 __all__ = (
@@ -155,14 +163,16 @@ __all__ = (
     "Poll",
     "Location",
     "Venue",
+    "WebAppData",
     "ProximityAlertTriggered",
     "MessageAutoDeleteTimerChanged",
-    "VoiceChatScheduled",
-    "VoiceChatStarted",
-    "VoiceChatEnded",
-    "VoiceChatParticipantsInvited",
+    "VideoChatScheduled",
+    "VideoChatStarted",
+    "VideoChatEnded",
+    "VideoChatParticipantsInvited",
     "UserProfilePhotos",
     "File",
+    "WebAppInfo",
     "ReplyKeyboardMarkup",
     "KeyboardButton",
     "KeyboardButtonPollType",
@@ -174,6 +184,7 @@ __all__ = (
     "ForceReply",
     "ChatPhoto",
     "ChatInviteLink",
+    "ChatAdministratorRights",
     "ChatMember",
     "ChatMemberOwner",
     "ChatMemberAdministrator",
@@ -194,6 +205,10 @@ __all__ = (
     "BotCommandScopeChat",
     "BotCommandScopeChatAdministrators",
     "BotCommandScopeChatMember",
+    "MenuButton",
+    "MenuButtonCommands",
+    "MenuButtonWebApp",
+    "MenuButtonDefault",
     "ResponseParameters",
     "InputMedia",
     "InputMediaPhoto",
@@ -234,6 +249,7 @@ __all__ = (
     "InputContactMessageContent",
     "InputInvoiceMessageContent",
     "ChosenInlineResult",
+    "SentWebAppMessage",
     "LabeledPrice",
     "Invoice",
     "ShippingAddress",
diff --git a/aiogram/types/chat_administrator_rights.py b/aiogram/types/chat_administrator_rights.py
new file mode 100644
index 00000000..20f4b65c
--- /dev/null
+++ b/aiogram/types/chat_administrator_rights.py
@@ -0,0 +1,39 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, Optional
+
+from .base import TelegramObject
+
+if TYPE_CHECKING:
+    pass
+
+
+class ChatAdministratorRights(TelegramObject):
+    """
+    Represents the rights of an administrator in a chat.
+
+    Source: https://core.telegram.org/bots/api#chatadministratorrights
+    """
+
+    is_anonymous: bool
+    """:code:`True`, if the user's presence in the chat is hidden"""
+    can_manage_chat: bool
+    """:code:`True`, if the administrator can access the chat event log, chat statistics, message statistics in channels, see channel members, see anonymous administrators in supergroups and ignore slow mode. Implied by any other administrator privilege"""
+    can_delete_messages: bool
+    """:code:`True`, if the administrator can delete messages of other users"""
+    can_manage_video_chats: bool
+    """:code:`True`, if the administrator can manage video chats"""
+    can_restrict_members: bool
+    """:code:`True`, if the administrator can restrict, ban or unban chat members"""
+    can_promote_members: bool
+    """:code:`True`, if the administrator can add new administrators with a subset of their own privileges or demote administrators that he has promoted, directly or indirectly (promoted by administrators that were appointed by the user)"""
+    can_change_info: bool
+    """:code:`True`, if the user is allowed to change the chat title, photo and other settings"""
+    can_invite_users: bool
+    """:code:`True`, if the user is allowed to invite new users to the chat"""
+    can_post_messages: Optional[bool] = None
+    """*Optional*. :code:`True`, if the administrator can post in the channel; channels only"""
+    can_edit_messages: Optional[bool] = None
+    """*Optional*. :code:`True`, if the administrator can edit messages of other users and can pin messages; channels only"""
+    can_pin_messages: Optional[bool] = None
+    """*Optional*. :code:`True`, if the user is allowed to pin messages; groups and supergroups only"""
diff --git a/aiogram/types/chat_member.py b/aiogram/types/chat_member.py
index b3d1419c..d430e0ce 100644
--- a/aiogram/types/chat_member.py
+++ b/aiogram/types/chat_member.py
@@ -37,8 +37,8 @@ class ChatMember(TelegramObject):
     """*Optional*. :code:`True`, if the administrator can access the chat event log, chat statistics, message statistics in channels, see channel members, see anonymous administrators in supergroups and ignore slow mode. Implied by any other administrator privilege"""
     can_delete_messages: Optional[bool] = None
     """*Optional*. :code:`True`, if the administrator can delete messages of other users"""
-    can_manage_voice_chats: Optional[bool] = None
-    """*Optional*. :code:`True`, if the administrator can manage voice chats"""
+    can_manage_video_chats: Optional[bool] = None
+    """*Optional*. :code:`True`, if the administrator can manage video chats"""
     can_restrict_members: Optional[bool] = None
     """*Optional*. :code:`True`, if the administrator can restrict, ban or unban chat members"""
     can_promote_members: Optional[bool] = None
diff --git a/aiogram/types/chat_member_administrator.py b/aiogram/types/chat_member_administrator.py
index a27156f9..896033d2 100644
--- a/aiogram/types/chat_member_administrator.py
+++ b/aiogram/types/chat_member_administrator.py
@@ -29,8 +29,8 @@ class ChatMemberAdministrator(ChatMember):
     """:code:`True`, if the administrator can access the chat event log, chat statistics, message statistics in channels, see channel members, see anonymous administrators in supergroups and ignore slow mode. Implied by any other administrator privilege"""
     can_delete_messages: bool
     """:code:`True`, if the administrator can delete messages of other users"""
-    can_manage_voice_chats: bool
-    """:code:`True`, if the administrator can manage voice chats"""
+    can_manage_video_chats: bool
+    """:code:`True`, if the administrator can manage video chats"""
     can_restrict_members: bool
     """:code:`True`, if the administrator can restrict, ban or unban chat members"""
     can_promote_members: bool
diff --git a/aiogram/types/inline_keyboard_button.py b/aiogram/types/inline_keyboard_button.py
index b661339a..aeb546f1 100644
--- a/aiogram/types/inline_keyboard_button.py
+++ b/aiogram/types/inline_keyboard_button.py
@@ -7,6 +7,7 @@ from .base import MutableTelegramObject
 if TYPE_CHECKING:
     from .callback_game import CallbackGame
     from .login_url import LoginUrl
+    from .web_app_info import WebAppInfo
 
 
 class InlineKeyboardButton(MutableTelegramObject):
@@ -20,10 +21,12 @@ class InlineKeyboardButton(MutableTelegramObject):
     """Label text on the button"""
     url: Optional[str] = None
     """*Optional*. HTTP or tg:// url to be opened when the button is pressed. Links :code:`tg://user?id=<user_id>` can be used to mention a user by their ID without using a username, if this is allowed by their privacy settings."""
-    login_url: Optional[LoginUrl] = None
-    """*Optional*. An HTTP URL used to automatically authorize the user. Can be used as a replacement for the `Telegram Login Widget <https://core.telegram.org/widgets/login>`_."""
     callback_data: Optional[str] = None
     """*Optional*. Data to be sent in a `callback query <https://core.telegram.org/bots/api#callbackquery>`_ to the bot when button is pressed, 1-64 bytes"""
+    web_app: Optional[WebAppInfo] = None
+    """*Optional*. Description of the `Web App <https://core.telegram.org/bots/webapps>`_ that will be launched when the user presses the button. The Web App will be able to send an arbitrary message on behalf of the user using the method :class:`aiogram.methods.answer_web_app_query.AnswerWebAppQuery`. Available only in private chats between a user and the bot."""
+    login_url: Optional[LoginUrl] = None
+    """*Optional*. An HTTP URL used to automatically authorize the user. Can be used as a replacement for the `Telegram Login Widget <https://core.telegram.org/widgets/login>`_."""
     switch_inline_query: Optional[str] = None
     """*Optional*. If set, pressing the button will prompt the user to select one of their chats, open that chat and insert the bot's username and the specified inline query in the input field. Can be empty, in which case just the bot's username will be inserted."""
     switch_inline_query_current_chat: Optional[str] = None
diff --git a/aiogram/types/keyboard_button.py b/aiogram/types/keyboard_button.py
index c4eed8d1..bf8b0258 100644
--- a/aiogram/types/keyboard_button.py
+++ b/aiogram/types/keyboard_button.py
@@ -6,6 +6,7 @@ from .base import MutableTelegramObject
 
 if TYPE_CHECKING:
     from .keyboard_button_poll_type import KeyboardButtonPollType
+    from .web_app_info import WebAppInfo
 
 
 class WebApp(MutableTelegramObject):
@@ -19,15 +20,18 @@ class KeyboardButton(MutableTelegramObject):
 
     **Note:** *request_poll* option will only work in Telegram versions released after 23 January, 2020. Older clients will display *unsupported message*.
 
+    **Note:** *web_app* option will only work in Telegram versions released after 16 April, 2022. Older clients will display *unsupported message*.
+
     Source: https://core.telegram.org/bots/api#keyboardbutton
     """
 
     text: str
     """Text of the button. If none of the optional fields are used, it will be sent as a message when the button is pressed"""
     request_contact: Optional[bool] = None
-    """*Optional*. If :code:`True`, the user's phone number will be sent as a contact when the button is pressed. Available in private chats only"""
+    """*Optional*. If :code:`True`, the user's phone number will be sent as a contact when the button is pressed. Available in private chats only."""
     request_location: Optional[bool] = None
-    """*Optional*. If :code:`True`, the user's current location will be sent when the button is pressed. Available in private chats only"""
+    """*Optional*. If :code:`True`, the user's current location will be sent when the button is pressed. Available in private chats only."""
     request_poll: Optional[KeyboardButtonPollType] = None
-    """*Optional*. If specified, the user will be asked to create a poll and send it to the bot when the button is pressed. Available in private chats only"""
-    web_app: Optional[WebApp] = None
+    """*Optional*. If specified, the user will be asked to create a poll and send it to the bot when the button is pressed. Available in private chats only."""
+    web_app: Optional[WebAppInfo] = None
+    """*Optional*. If specified, the described `Web App <https://core.telegram.org/bots/webapps>`_ will be launched when the button is pressed. The Web App will be able to send a 'web_app_data' service message. Available in private chats only."""
diff --git a/aiogram/types/menu_button.py b/aiogram/types/menu_button.py
new file mode 100644
index 00000000..0a709dd6
--- /dev/null
+++ b/aiogram/types/menu_button.py
@@ -0,0 +1,22 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+from .base import TelegramObject
+
+if TYPE_CHECKING:
+    pass
+
+
+class MenuButton(TelegramObject):
+    """
+    This object describes the bot's menu button in a private chat. It should be one of
+
+     - :class:`aiogram.types.menu_button_commands.MenuButtonCommands`
+     - :class:`aiogram.types.menu_button_web_app.MenuButtonWebApp`
+     - :class:`aiogram.types.menu_button_default.MenuButtonDefault`
+
+    If a menu button other than :class:`aiogram.types.menu_button_default.MenuButtonDefault` is set for a private chat, then it is applied in the chat. Otherwise the default menu button is applied. By default, the menu button opens the list of bot commands.
+
+    Source: https://core.telegram.org/bots/api#menubutton
+    """
diff --git a/aiogram/types/menu_button_commands.py b/aiogram/types/menu_button_commands.py
new file mode 100644
index 00000000..5f4e252b
--- /dev/null
+++ b/aiogram/types/menu_button_commands.py
@@ -0,0 +1,16 @@
+from __future__ import annotations
+
+from pydantic import Field
+
+from . import MenuButton
+
+
+class MenuButtonCommands(MenuButton):
+    """
+    Represents a menu button, which opens the bot's list of commands.
+
+    Source: https://core.telegram.org/bots/api#menubuttoncommands
+    """
+
+    type: str = Field("commands", const=True)
+    """Type of the button, must be *commands*"""
diff --git a/aiogram/types/menu_button_default.py b/aiogram/types/menu_button_default.py
new file mode 100644
index 00000000..13cd3a37
--- /dev/null
+++ b/aiogram/types/menu_button_default.py
@@ -0,0 +1,16 @@
+from __future__ import annotations
+
+from pydantic import Field
+
+from . import MenuButton
+
+
+class MenuButtonDefault(MenuButton):
+    """
+    Describes that no specific value for the menu button was set.
+
+    Source: https://core.telegram.org/bots/api#menubuttondefault
+    """
+
+    type: str = Field("default", const=True)
+    """Type of the button, must be *default*"""
diff --git a/aiogram/types/menu_button_web_app.py b/aiogram/types/menu_button_web_app.py
new file mode 100644
index 00000000..0de45b94
--- /dev/null
+++ b/aiogram/types/menu_button_web_app.py
@@ -0,0 +1,25 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+from pydantic import Field
+
+from . import MenuButton
+
+if TYPE_CHECKING:
+    from .web_app_info import WebAppInfo
+
+
+class MenuButtonWebApp(MenuButton):
+    """
+    Represents a menu button, which launches a `Web App <https://core.telegram.org/bots/webapps>`_.
+
+    Source: https://core.telegram.org/bots/api#menubuttonwebapp
+    """
+
+    type: str = Field("web_app", const=True)
+    """Type of the button, must be *web_app*"""
+    text: str
+    """Text on the button"""
+    web_app: WebAppInfo
+    """Description of the Web App that will be launched when the user presses the button. The Web App will be able to send an arbitrary message on behalf of the user using the method :class:`aiogram.methods.answer_web_app_query.AnswerWebAppQuery`."""
diff --git a/aiogram/types/message.py b/aiogram/types/message.py
index d1ab7cbd..1fe8a4f5 100644
--- a/aiogram/types/message.py
+++ b/aiogram/types/message.py
@@ -63,15 +63,16 @@ if TYPE_CHECKING:
     from .user import User
     from .venue import Venue
     from .video import Video
+    from .video_chat_ended import VideoChatEnded
+    from .video_chat_participants_invited import VideoChatParticipantsInvited
+    from .video_chat_scheduled import VideoChatScheduled
+    from .video_chat_started import VideoChatStarted
     from .video_note import VideoNote
     from .voice import Voice
-    from .voice_chat_ended import VoiceChatEnded
-    from .voice_chat_participants_invited import VoiceChatParticipantsInvited
-    from .voice_chat_scheduled import VoiceChatScheduled
-    from .voice_chat_started import VoiceChatStarted
+    from .web_app_data import WebAppData
 
 
-class _BaseMessage(TelegramObject):
+class Message(TelegramObject):
     """
     This object represents a message.
 
@@ -184,19 +185,19 @@ class _BaseMessage(TelegramObject):
     """*Optional*. Telegram Passport data"""
     proximity_alert_triggered: Optional[ProximityAlertTriggered] = None
     """*Optional*. Service message. A user in the chat triggered another user's proximity alert while sharing Live Location."""
-    voice_chat_scheduled: Optional[VoiceChatScheduled] = None
-    """*Optional*. Service message: voice chat scheduled"""
-    voice_chat_started: Optional[VoiceChatStarted] = None
-    """*Optional*. Service message: voice chat started"""
-    voice_chat_ended: Optional[VoiceChatEnded] = None
-    """*Optional*. Service message: voice chat ended"""
-    voice_chat_participants_invited: Optional[VoiceChatParticipantsInvited] = None
-    """*Optional*. Service message: new participants invited to a voice chat"""
+    video_chat_scheduled: Optional[VideoChatScheduled] = None
+    """*Optional*. Service message: video chat scheduled"""
+    video_chat_started: Optional[VideoChatStarted] = None
+    """*Optional*. Service message: video chat started"""
+    video_chat_ended: Optional[VideoChatEnded] = None
+    """*Optional*. Service message: video chat ended"""
+    video_chat_participants_invited: Optional[VideoChatParticipantsInvited] = None
+    """*Optional*. Service message: new participants invited to a video chat"""
+    web_app_data: Optional[WebAppData] = None
+    """*Optional*. Service message: data sent by a Web App"""
     reply_markup: Optional[InlineKeyboardMarkup] = None
     """*Optional*. Inline keyboard attached to the message. :code:`login_url` buttons are represented as ordinary :code:`url` buttons."""
 
-
-class Message(_BaseMessage):
     @property
     def content_type(self) -> str:
         if self.text:
@@ -257,12 +258,16 @@ class Message(_BaseMessage):
             return ContentType.DICE
         if self.message_auto_delete_timer_changed:
             return ContentType.MESSAGE_AUTO_DELETE_TIMER_CHANGED
-        if self.voice_chat_started:
-            return ContentType.VOICE_CHAT_STARTED
-        if self.voice_chat_ended:
-            return ContentType.VOICE_CHAT_ENDED
-        if self.voice_chat_participants_invited:
-            return ContentType.VOICE_CHAT_PARTICIPANTS_INVITED
+        if self.video_chat_scheduled:
+            return ContentType.VIDEO_CHAT_SCHEDULED
+        if self.video_chat_started:
+            return ContentType.VIDEO_CHAT_STARTED
+        if self.video_chat_ended:
+            return ContentType.VIDEO_CHAT_ENDED
+        if self.video_chat_participants_invited:
+            return ContentType.VIDEO_CHAT_PARTICIPANTS_INVITED
+        if self.web_app_data:
+            return ContentType.WEB_APP_DATA
 
         return ContentType.UNKNOWN
 
@@ -1899,9 +1904,11 @@ class ContentType(helper.Helper):
     POLL = helper.Item()  # poll
     DICE = helper.Item()  # dice
     MESSAGE_AUTO_DELETE_TIMER_CHANGED = helper.Item()  # message_auto_delete_timer_changed
-    VOICE_CHAT_STARTED = helper.Item()  # voice_chat_started
-    VOICE_CHAT_ENDED = helper.Item()  # voice_chat_ended
-    VOICE_CHAT_PARTICIPANTS_INVITED = helper.Item()  # voice_chat_participants_invited
+    VIDEO_CHAT_SCHEDULED = helper.Item()  # video_chat_scheduled
+    VIDEO_CHAT_STARTED = helper.Item()  # video_chat_started
+    VIDEO_CHAT_ENDED = helper.Item()  # video_chat_ended
+    VIDEO_CHAT_PARTICIPANTS_INVITED = helper.Item()  # video_chat_participants_invited
+    WEB_APP_DATA = helper.Item()  # web_app_data
 
     UNKNOWN = helper.Item()  # unknown
     ANY = helper.Item()  # any
diff --git a/aiogram/types/sent_web_app_message.py b/aiogram/types/sent_web_app_message.py
new file mode 100644
index 00000000..7295382c
--- /dev/null
+++ b/aiogram/types/sent_web_app_message.py
@@ -0,0 +1,16 @@
+from __future__ import annotations
+
+from typing import Optional
+
+from .base import TelegramObject
+
+
+class SentWebAppMessage(TelegramObject):
+    """
+    Contains information about an inline message sent by a `Web App <https://core.telegram.org/bots/webapps>`_ on behalf of a user.
+
+    Source: https://core.telegram.org/bots/api#sentwebappmessage
+    """
+
+    inline_message_id: Optional[str] = None
+    """*Optional*. Identifier of the sent inline message. Available only if there is an `inline keyboard <https://core.telegram.org/bots/api#inlinekeyboardmarkup>`_ attached to the message."""
diff --git a/aiogram/types/sticker.py b/aiogram/types/sticker.py
index 2379d547..979d7e37 100644
--- a/aiogram/types/sticker.py
+++ b/aiogram/types/sticker.py
@@ -26,6 +26,8 @@ class Sticker(TelegramObject):
     """Sticker height"""
     is_animated: bool
     """:code:`True`, if the sticker is `animated <https://telegram.org/blog/animated-stickers>`_"""
+    is_video: bool
+    """:code:`True`, if the sticker is a `video sticker <https://telegram.org/blog/video-stickers-better-reactions>`_"""
     thumb: Optional[PhotoSize] = None
     """*Optional*. Sticker thumbnail in the .WEBP or .JPG format"""
     emoji: Optional[str] = None
diff --git a/aiogram/types/sticker_set.py b/aiogram/types/sticker_set.py
index d26d206d..3ed5055c 100644
--- a/aiogram/types/sticker_set.py
+++ b/aiogram/types/sticker_set.py
@@ -22,9 +22,11 @@ class StickerSet(TelegramObject):
     """Sticker set title"""
     is_animated: bool
     """:code:`True`, if the sticker set contains `animated stickers <https://telegram.org/blog/animated-stickers>`_"""
+    is_video: bool
+    """:code:`True`, if the sticker set contains `video stickers <https://telegram.org/blog/video-stickers-better-reactions>`_"""
     contains_masks: bool
     """:code:`True`, if the sticker set contains masks"""
     stickers: List[Sticker]
     """List of all set stickers"""
     thumb: Optional[PhotoSize] = None
-    """*Optional*. Sticker set thumbnail in the .WEBP or .TGS format"""
+    """*Optional*. Sticker set thumbnail in the .WEBP, .TGS, or .WEBM format"""
diff --git a/aiogram/types/video_chat_ended.py b/aiogram/types/video_chat_ended.py
new file mode 100644
index 00000000..cb85a931
--- /dev/null
+++ b/aiogram/types/video_chat_ended.py
@@ -0,0 +1,19 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING
+
+from .base import TelegramObject
+
+if TYPE_CHECKING:
+    pass
+
+
+class VideoChatEnded(TelegramObject):
+    """
+    This object represents a service message about a video chat ended in the chat.
+
+    Source: https://core.telegram.org/bots/api#videochatended
+    """
+
+    duration: int
+    """Video chat duration in seconds"""
diff --git a/aiogram/types/video_chat_participants_invited.py b/aiogram/types/video_chat_participants_invited.py
new file mode 100644
index 00000000..3361f8ee
--- /dev/null
+++ b/aiogram/types/video_chat_participants_invited.py
@@ -0,0 +1,19 @@
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, List
+
+from .base import TelegramObject
+
+if TYPE_CHECKING:
+    from .user import User
+
+
+class VideoChatParticipantsInvited(TelegramObject):
+    """
+    This object represents a service message about new members invited to a video chat.
+
+    Source: https://core.telegram.org/bots/api#videochatparticipantsinvited
+    """
+
+    users: List[User]
+    """New members that were invited to the video chat"""
diff --git a/aiogram/types/video_chat_scheduled.py b/aiogram/types/video_chat_scheduled.py
new file mode 100644
index 00000000..541e988a
--- /dev/null
+++ b/aiogram/types/video_chat_scheduled.py
@@ -0,0 +1,16 @@
+from __future__ import annotations
+
+from datetime import datetime
+
+from .base import TelegramObject
+
+
+class VideoChatScheduled(TelegramObject):
+    """
+    This object represents a service message about a video chat scheduled in the chat.
+
+    Source: https://core.telegram.org/bots/api#videochatscheduled
+    """
+
+    start_date: datetime
+    """Point in time (Unix timestamp) when the video chat is supposed to be started by a chat administrator"""
diff --git a/aiogram/types/video_chat_started.py b/aiogram/types/video_chat_started.py
new file mode 100644
index 00000000..a6f9aed0
--- /dev/null
+++ b/aiogram/types/video_chat_started.py
@@ -0,0 +1,11 @@
+from __future__ import annotations
+
+from .base import TelegramObject
+
+
+class VideoChatStarted(TelegramObject):
+    """
+    This object represents a service message about a video chat started in the chat. Currently holds no information.
+
+    Source: https://core.telegram.org/bots/api#videochatstarted
+    """
diff --git a/aiogram/types/voice_chat_ended.py b/aiogram/types/voice_chat_ended.py
deleted file mode 100644
index 12c705c9..00000000
--- a/aiogram/types/voice_chat_ended.py
+++ /dev/null
@@ -1,14 +0,0 @@
-from __future__ import annotations
-
-from .base import TelegramObject
-
-
-class VoiceChatEnded(TelegramObject):
-    """
-    This object represents a service message about a voice chat ended in the chat.
-
-    Source: https://core.telegram.org/bots/api#voicechatended
-    """
-
-    duration: int
-    """Voice chat duration in seconds"""
diff --git a/aiogram/types/voice_chat_participants_invited.py b/aiogram/types/voice_chat_participants_invited.py
deleted file mode 100644
index b24ef91d..00000000
--- a/aiogram/types/voice_chat_participants_invited.py
+++ /dev/null
@@ -1,19 +0,0 @@
-from __future__ import annotations
-
-from typing import TYPE_CHECKING, List, Optional
-
-from .base import TelegramObject
-
-if TYPE_CHECKING:
-    from .user import User
-
-
-class VoiceChatParticipantsInvited(TelegramObject):
-    """
-    This object represents a service message about new members invited to a voice chat.
-
-    Source: https://core.telegram.org/bots/api#voicechatparticipantsinvited
-    """
-
-    users: Optional[List[User]] = None
-    """*Optional*. New members that were invited to the voice chat"""
diff --git a/aiogram/types/voice_chat_scheduled.py b/aiogram/types/voice_chat_scheduled.py
deleted file mode 100644
index 37c6c7bd..00000000
--- a/aiogram/types/voice_chat_scheduled.py
+++ /dev/null
@@ -1,14 +0,0 @@
-from __future__ import annotations
-
-from .base import TelegramObject
-
-
-class VoiceChatScheduled(TelegramObject):
-    """
-    This object represents a service message about a voice chat scheduled in the chat.
-
-    Source: https://core.telegram.org/bots/api#voicechatscheduled
-    """
-
-    start_date: int
-    """Point in time (Unix timestamp) when the voice chat is supposed to be started by a chat administrator"""
diff --git a/aiogram/types/voice_chat_started.py b/aiogram/types/voice_chat_started.py
deleted file mode 100644
index 6ad45263..00000000
--- a/aiogram/types/voice_chat_started.py
+++ /dev/null
@@ -1,11 +0,0 @@
-from __future__ import annotations
-
-from .base import TelegramObject
-
-
-class VoiceChatStarted(TelegramObject):
-    """
-    This object represents a service message about a voice chat started in the chat. Currently holds no information.
-
-    Source: https://core.telegram.org/bots/api#voicechatstarted
-    """
diff --git a/aiogram/types/web_app_data.py b/aiogram/types/web_app_data.py
new file mode 100644
index 00000000..6a108fef
--- /dev/null
+++ b/aiogram/types/web_app_data.py
@@ -0,0 +1,16 @@
+from __future__ import annotations
+
+from .base import TelegramObject
+
+
+class WebAppData(TelegramObject):
+    """
+    Contains data sent from a `Web App <https://core.telegram.org/bots/webapps>`_ to the bot.
+
+    Source: https://core.telegram.org/bots/api#webappdata
+    """
+
+    data: str
+    """The data. Be aware that a bad client can send arbitrary data in this field."""
+    button_text: str
+    """Text of the *web_app* keyboard button, from which the Web App was opened. Be aware that a bad client can send arbitrary data in this field."""
diff --git a/aiogram/types/web_app_info.py b/aiogram/types/web_app_info.py
new file mode 100644
index 00000000..9317fae9
--- /dev/null
+++ b/aiogram/types/web_app_info.py
@@ -0,0 +1,14 @@
+from __future__ import annotations
+
+from .base import TelegramObject
+
+
+class WebAppInfo(TelegramObject):
+    """
+    Contains information about a `Web App <https://core.telegram.org/bots/webapps>`_.
+
+    Source: https://core.telegram.org/bots/api#webappinfo
+    """
+
+    url: str
+    """An HTTPS URL of a Web App to be opened with additional data as specified in `Initializing Web Apps <https://core.telegram.org/bots/webapps#initializing-web-apps>`_"""
diff --git a/aiogram/types/webhook_info.py b/aiogram/types/webhook_info.py
index 3b1a64a0..a3ec68e5 100644
--- a/aiogram/types/webhook_info.py
+++ b/aiogram/types/webhook_info.py
@@ -25,6 +25,8 @@ class WebhookInfo(TelegramObject):
     """*Optional*. Unix time for the most recent error that happened when trying to deliver an update via webhook"""
     last_error_message: Optional[str] = None
     """*Optional*. Error message in human-readable format for the most recent error that happened when trying to deliver an update via webhook"""
+    last_synchronization_error_date: Optional[datetime.datetime] = None
+    """*Optional*. Unix time of the most recent error that happened when trying to synchronize available updates with Telegram datacenters"""
     max_connections: Optional[int] = None
     """*Optional*. Maximum allowed number of simultaneous HTTPS connections to the webhook for update delivery"""
     allowed_updates: Optional[List[str]] = None
diff --git a/aiogram/utils/web_app.py b/aiogram/utils/web_app.py
new file mode 100644
index 00000000..9cce8e06
--- /dev/null
+++ b/aiogram/utils/web_app.py
@@ -0,0 +1,129 @@
+import hashlib
+import hmac
+import json
+from datetime import datetime
+from operator import itemgetter
+from typing import Any, Callable, Optional
+from urllib.parse import parse_qsl
+
+from aiogram.types import TelegramObject
+
+
+class WebAppUser(TelegramObject):
+    """
+    This object contains the data of the Web App user.
+
+    Source: https://core.telegram.org/bots/webapps#webappuser
+    """
+
+    id: int
+    """A unique identifier for the user or bot. This number may have more than 32 significant bits and some programming languages may have difficulty/silent defects in interpreting it. It has at most 52 significant bits, so a 64-bit integer or a double-precision float type is safe for storing this identifier."""
+    is_bot: Optional[bool] = None
+    """True, if this user is a bot. Returns in the receiver field only."""
+    first_name: str
+    """First name of the user or bot."""
+    last_name: Optional[str] = None
+    """Last name of the user or bot."""
+    username: Optional[str] = None
+    """Username of the user or bot."""
+    language_code: Optional[str] = None
+    """IETF language tag of the user's language. Returns in user field only."""
+    photo_url: Optional[str] = None
+    """URL of the user’s profile photo. The photo can be in .jpeg or .svg formats. Only returned for Web Apps launched from the attachment menu."""
+
+
+class WebAppInitData(TelegramObject):
+    """
+    This object contains data that is transferred to the Web App when it is opened. It is empty if the Web App was launched from a keyboard button.
+
+    Source: https://core.telegram.org/bots/webapps#webappinitdata
+    """
+
+    query_id: Optional[str] = None
+    """A unique identifier for the Web App session, required for sending messages via the answerWebAppQuery method."""
+    user: Optional[WebAppUser] = None
+    """An object containing data about the current user."""
+    receiver: Optional[WebAppUser] = None
+    """An object containing data about the chat partner of the current user in the chat where the bot was launched via the attachment menu. Returned only for Web Apps launched via the attachment menu."""
+    start_param: Optional[str] = None
+    """The value of the startattach parameter, passed via link. Only returned for Web Apps when launched from the attachment menu via link. The value of the start_param parameter will also be passed in the GET-parameter tgWebAppStartParam, so the Web App can load the correct interface right away."""
+    auth_date: datetime
+    """Unix time when the form was opened."""
+    hash: str
+    """A hash of all passed parameters, which the bot server can use to check their validity."""
+
+
+def check_webapp_signature(token: str, init_data: str) -> bool:
+    """
+    Check incoming WebApp init data signature
+
+    Source: https://core.telegram.org/bots/webapps#validating-data-received-via-the-web-app
+
+    :param token: bot Token
+    :param init_data: data from frontend to be validated
+    :return:
+    """
+    try:
+        parsed_data = dict(parse_qsl(init_data, strict_parsing=True))
+    except ValueError:  # pragma: no cover
+        # Init data is not a valid query string
+        return False
+    if "hash" not in parsed_data:
+        # Hash is not present in init data
+        return False
+    hash_ = parsed_data.pop("hash")
+
+    data_check_string = "\n".join(
+        f"{k}={v}" for k, v in sorted(parsed_data.items(), key=itemgetter(0))
+    )
+    secret_key = hmac.new(key=b"WebAppData", msg=token.encode(), digestmod=hashlib.sha256)
+    calculated_hash = hmac.new(
+        key=secret_key.digest(), msg=data_check_string.encode(), digestmod=hashlib.sha256
+    ).hexdigest()
+    return calculated_hash == hash_
+
+
+def parse_webapp_init_data(
+    init_data: str,
+    *,
+    loads: Callable[..., Any] = json.loads,
+) -> WebAppInitData:
+    """
+    Parse WebApp init data and return it as WebAppInitData object
+
+    This method doesn't make any security check, so you shall not trust to this data,
+    use :code:`safe_parse_webapp_init_data` instead.
+
+    :param init_data: data from frontend to be parsed
+    :param loads:
+    :return:
+    """
+    result = {}
+    for key, value in parse_qsl(init_data):
+        if (value.startswith("[") and value.endswith("]")) or (
+            value.startswith("{") and value.endswith("}")
+        ):
+            value = loads(value)
+        result[key] = value
+    return WebAppInitData(**result)
+
+
+def safe_parse_webapp_init_data(
+    token: str,
+    init_data: str,
+    *,
+    loads: Callable[..., Any] = json.loads,
+) -> WebAppInitData:
+    """
+    Validate raw WebApp init data and return it as WebAppInitData object
+
+    Raise :type:`ValueError` when data is invalid
+
+    :param token: bot token
+    :param init_data: data from frontend to be parsed and validated
+    :param loads:
+    :return:
+    """
+    if check_webapp_signature(token, init_data):
+        return parse_webapp_init_data(init_data, loads=loads)
+    raise ValueError("Invalid init data signature")
diff --git a/docs/api/methods/answer_web_app_query.rst b/docs/api/methods/answer_web_app_query.rst
new file mode 100644
index 00000000..a608083f
--- /dev/null
+++ b/docs/api/methods/answer_web_app_query.rst
@@ -0,0 +1,51 @@
+#################
+answerWebAppQuery
+#################
+
+Returns: :obj:`SentWebAppMessage`
+
+.. automodule:: aiogram.methods.answer_web_app_query
+    :members:
+    :member-order: bysource
+    :undoc-members: True
+
+
+Usage
+=====
+
+As bot method
+-------------
+
+.. code-block::
+
+    result: SentWebAppMessage = await bot.answer_web_app_query(...)
+
+
+Method as object
+----------------
+
+Imports:
+
+- :code:`from aiogram.methods.answer_web_app_query import AnswerWebAppQuery`
+- alias: :code:`from aiogram.methods import AnswerWebAppQuery`
+
+In handlers with current bot
+----------------------------
+
+.. code-block:: python
+
+    result: SentWebAppMessage = await AnswerWebAppQuery(...)
+
+With specific bot
+~~~~~~~~~~~~~~~~~
+
+.. code-block:: python
+
+    result: SentWebAppMessage = await bot(AnswerWebAppQuery(...))
+
+As reply into Webhook in handler
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. code-block:: python
+
+    return AnswerWebAppQuery(...)
diff --git a/docs/api/methods/get_chat_menu_button.rst b/docs/api/methods/get_chat_menu_button.rst
new file mode 100644
index 00000000..8e3df76a
--- /dev/null
+++ b/docs/api/methods/get_chat_menu_button.rst
@@ -0,0 +1,44 @@
+#################
+getChatMenuButton
+#################
+
+Returns: :obj:`MenuButton`
+
+.. automodule:: aiogram.methods.get_chat_menu_button
+    :members:
+    :member-order: bysource
+    :undoc-members: True
+
+
+Usage
+=====
+
+As bot method
+-------------
+
+.. code-block::
+
+    result: MenuButton = await bot.get_chat_menu_button(...)
+
+
+Method as object
+----------------
+
+Imports:
+
+- :code:`from aiogram.methods.get_chat_menu_button import GetChatMenuButton`
+- alias: :code:`from aiogram.methods import GetChatMenuButton`
+
+In handlers with current bot
+----------------------------
+
+.. code-block:: python
+
+    result: MenuButton = await GetChatMenuButton(...)
+
+With specific bot
+~~~~~~~~~~~~~~~~~
+
+.. code-block:: python
+
+    result: MenuButton = await bot(GetChatMenuButton(...))
diff --git a/docs/api/methods/get_my_default_administrator_rights.rst b/docs/api/methods/get_my_default_administrator_rights.rst
new file mode 100644
index 00000000..d73c54f8
--- /dev/null
+++ b/docs/api/methods/get_my_default_administrator_rights.rst
@@ -0,0 +1,44 @@
+###############################
+getMyDefaultAdministratorRights
+###############################
+
+Returns: :obj:`ChatAdministratorRights`
+
+.. automodule:: aiogram.methods.get_my_default_administrator_rights
+    :members:
+    :member-order: bysource
+    :undoc-members: True
+
+
+Usage
+=====
+
+As bot method
+-------------
+
+.. code-block::
+
+    result: ChatAdministratorRights = await bot.get_my_default_administrator_rights(...)
+
+
+Method as object
+----------------
+
+Imports:
+
+- :code:`from aiogram.methods.get_my_default_administrator_rights import GetMyDefaultAdministratorRights`
+- alias: :code:`from aiogram.methods import GetMyDefaultAdministratorRights`
+
+In handlers with current bot
+----------------------------
+
+.. code-block:: python
+
+    result: ChatAdministratorRights = await GetMyDefaultAdministratorRights(...)
+
+With specific bot
+~~~~~~~~~~~~~~~~~
+
+.. code-block:: python
+
+    result: ChatAdministratorRights = await bot(GetMyDefaultAdministratorRights(...))
diff --git a/docs/api/methods/index.rst b/docs/api/methods/index.rst
index 1a4143d0..6c778282 100644
--- a/docs/api/methods/index.rst
+++ b/docs/api/methods/index.rst
@@ -82,6 +82,10 @@ Available methods
     set_my_commands
     delete_my_commands
     get_my_commands
+    set_chat_menu_button
+    get_chat_menu_button
+    set_my_default_administrator_rights
+    get_my_default_administrator_rights
 
 Updating messages
 =================
@@ -118,6 +122,7 @@ Inline mode
     :maxdepth: 1
 
     answer_inline_query
+    answer_web_app_query
 
 Payments
 ========
diff --git a/docs/api/methods/set_chat_menu_button.rst b/docs/api/methods/set_chat_menu_button.rst
new file mode 100644
index 00000000..6a60fae9
--- /dev/null
+++ b/docs/api/methods/set_chat_menu_button.rst
@@ -0,0 +1,51 @@
+#################
+setChatMenuButton
+#################
+
+Returns: :obj:`bool`
+
+.. automodule:: aiogram.methods.set_chat_menu_button
+    :members:
+    :member-order: bysource
+    :undoc-members: True
+
+
+Usage
+=====
+
+As bot method
+-------------
+
+.. code-block::
+
+    result: bool = await bot.set_chat_menu_button(...)
+
+
+Method as object
+----------------
+
+Imports:
+
+- :code:`from aiogram.methods.set_chat_menu_button import SetChatMenuButton`
+- alias: :code:`from aiogram.methods import SetChatMenuButton`
+
+In handlers with current bot
+----------------------------
+
+.. code-block:: python
+
+    result: bool = await SetChatMenuButton(...)
+
+With specific bot
+~~~~~~~~~~~~~~~~~
+
+.. code-block:: python
+
+    result: bool = await bot(SetChatMenuButton(...))
+
+As reply into Webhook in handler
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. code-block:: python
+
+    return SetChatMenuButton(...)
diff --git a/docs/api/methods/set_my_default_administrator_rights.rst b/docs/api/methods/set_my_default_administrator_rights.rst
new file mode 100644
index 00000000..c115568c
--- /dev/null
+++ b/docs/api/methods/set_my_default_administrator_rights.rst
@@ -0,0 +1,51 @@
+###############################
+setMyDefaultAdministratorRights
+###############################
+
+Returns: :obj:`bool`
+
+.. automodule:: aiogram.methods.set_my_default_administrator_rights
+    :members:
+    :member-order: bysource
+    :undoc-members: True
+
+
+Usage
+=====
+
+As bot method
+-------------
+
+.. code-block::
+
+    result: bool = await bot.set_my_default_administrator_rights(...)
+
+
+Method as object
+----------------
+
+Imports:
+
+- :code:`from aiogram.methods.set_my_default_administrator_rights import SetMyDefaultAdministratorRights`
+- alias: :code:`from aiogram.methods import SetMyDefaultAdministratorRights`
+
+In handlers with current bot
+----------------------------
+
+.. code-block:: python
+
+    result: bool = await SetMyDefaultAdministratorRights(...)
+
+With specific bot
+~~~~~~~~~~~~~~~~~
+
+.. code-block:: python
+
+    result: bool = await bot(SetMyDefaultAdministratorRights(...))
+
+As reply into Webhook in handler
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. code-block:: python
+
+    return SetMyDefaultAdministratorRights(...)
diff --git a/docs/api/types/chat_administrator_rights.rst b/docs/api/types/chat_administrator_rights.rst
new file mode 100644
index 00000000..ef86eede
--- /dev/null
+++ b/docs/api/types/chat_administrator_rights.rst
@@ -0,0 +1,9 @@
+#######################
+ChatAdministratorRights
+#######################
+
+
+.. automodule:: aiogram.types.chat_administrator_rights
+    :members:
+    :member-order: bysource
+    :undoc-members: True
diff --git a/docs/api/types/index.rst b/docs/api/types/index.rst
index f9b1bfc2..abcfe205 100644
--- a/docs/api/types/index.rst
+++ b/docs/api/types/index.rst
@@ -40,14 +40,16 @@ Available types
     poll
     location
     venue
+    web_app_data
     proximity_alert_triggered
     message_auto_delete_timer_changed
-    voice_chat_scheduled
-    voice_chat_started
-    voice_chat_ended
-    voice_chat_participants_invited
+    video_chat_scheduled
+    video_chat_started
+    video_chat_ended
+    video_chat_participants_invited
     user_profile_photos
     file
+    web_app_info
     reply_keyboard_markup
     keyboard_button
     keyboard_button_poll_type
@@ -59,6 +61,7 @@ Available types
     force_reply
     chat_photo
     chat_invite_link
+    chat_administrator_rights
     chat_member
     chat_member_owner
     chat_member_administrator
@@ -79,6 +82,10 @@ Available types
     bot_command_scope_chat
     bot_command_scope_chat_administrators
     bot_command_scope_chat_member
+    menu_button
+    menu_button_commands
+    menu_button_web_app
+    menu_button_default
     response_parameters
     input_media
     input_media_photo
@@ -135,6 +142,7 @@ Inline mode
     input_contact_message_content
     input_invoice_message_content
     chosen_inline_result
+    sent_web_app_message
 
 Payments
 ========
diff --git a/docs/api/types/menu_button.rst b/docs/api/types/menu_button.rst
new file mode 100644
index 00000000..44eeb4c3
--- /dev/null
+++ b/docs/api/types/menu_button.rst
@@ -0,0 +1,9 @@
+##########
+MenuButton
+##########
+
+
+.. automodule:: aiogram.types.menu_button
+    :members:
+    :member-order: bysource
+    :undoc-members: True
diff --git a/docs/api/types/voice_chat_scheduled.rst b/docs/api/types/menu_button_commands.rst
similarity index 60%
rename from docs/api/types/voice_chat_scheduled.rst
rename to docs/api/types/menu_button_commands.rst
index e63936e2..66614b04 100644
--- a/docs/api/types/voice_chat_scheduled.rst
+++ b/docs/api/types/menu_button_commands.rst
@@ -1,9 +1,9 @@
 ##################
-VoiceChatScheduled
+MenuButtonCommands
 ##################
 
 
-.. automodule:: aiogram.types.voice_chat_scheduled
+.. automodule:: aiogram.types.menu_button_commands
     :members:
     :member-order: bysource
     :undoc-members: True
diff --git a/docs/api/types/menu_button_default.rst b/docs/api/types/menu_button_default.rst
new file mode 100644
index 00000000..f114387c
--- /dev/null
+++ b/docs/api/types/menu_button_default.rst
@@ -0,0 +1,9 @@
+#################
+MenuButtonDefault
+#################
+
+
+.. automodule:: aiogram.types.menu_button_default
+    :members:
+    :member-order: bysource
+    :undoc-members: True
diff --git a/docs/api/types/menu_button_web_app.rst b/docs/api/types/menu_button_web_app.rst
new file mode 100644
index 00000000..bf5c0806
--- /dev/null
+++ b/docs/api/types/menu_button_web_app.rst
@@ -0,0 +1,9 @@
+################
+MenuButtonWebApp
+################
+
+
+.. automodule:: aiogram.types.menu_button_web_app
+    :members:
+    :member-order: bysource
+    :undoc-members: True
diff --git a/docs/api/types/sent_web_app_message.rst b/docs/api/types/sent_web_app_message.rst
new file mode 100644
index 00000000..1a7d2084
--- /dev/null
+++ b/docs/api/types/sent_web_app_message.rst
@@ -0,0 +1,9 @@
+#################
+SentWebAppMessage
+#################
+
+
+.. automodule:: aiogram.types.sent_web_app_message
+    :members:
+    :member-order: bysource
+    :undoc-members: True
diff --git a/docs/api/types/voice_chat_ended.rst b/docs/api/types/video_chat_ended.rst
similarity index 61%
rename from docs/api/types/voice_chat_ended.rst
rename to docs/api/types/video_chat_ended.rst
index cce70b7c..aed8b7a9 100644
--- a/docs/api/types/voice_chat_ended.rst
+++ b/docs/api/types/video_chat_ended.rst
@@ -1,9 +1,9 @@
 ##############
-VoiceChatEnded
+VideoChatEnded
 ##############
 
 
-.. automodule:: aiogram.types.voice_chat_ended
+.. automodule:: aiogram.types.video_chat_ended
     :members:
     :member-order: bysource
     :undoc-members: True
diff --git a/docs/api/types/voice_chat_participants_invited.rst b/docs/api/types/video_chat_participants_invited.rst
similarity index 58%
rename from docs/api/types/voice_chat_participants_invited.rst
rename to docs/api/types/video_chat_participants_invited.rst
index 89a94fa9..9ca905bd 100644
--- a/docs/api/types/voice_chat_participants_invited.rst
+++ b/docs/api/types/video_chat_participants_invited.rst
@@ -1,9 +1,9 @@
 ############################
-VoiceChatParticipantsInvited
+VideoChatParticipantsInvited
 ############################
 
 
-.. automodule:: aiogram.types.voice_chat_participants_invited
+.. automodule:: aiogram.types.video_chat_participants_invited
     :members:
     :member-order: bysource
     :undoc-members: True
diff --git a/docs/api/types/video_chat_scheduled.rst b/docs/api/types/video_chat_scheduled.rst
new file mode 100644
index 00000000..0d5f8c45
--- /dev/null
+++ b/docs/api/types/video_chat_scheduled.rst
@@ -0,0 +1,9 @@
+##################
+VideoChatScheduled
+##################
+
+
+.. automodule:: aiogram.types.video_chat_scheduled
+    :members:
+    :member-order: bysource
+    :undoc-members: True
diff --git a/docs/api/types/voice_chat_started.rst b/docs/api/types/video_chat_started.rst
similarity index 60%
rename from docs/api/types/voice_chat_started.rst
rename to docs/api/types/video_chat_started.rst
index c1a1964b..5d59a22e 100644
--- a/docs/api/types/voice_chat_started.rst
+++ b/docs/api/types/video_chat_started.rst
@@ -1,9 +1,9 @@
 ################
-VoiceChatStarted
+VideoChatStarted
 ################
 
 
-.. automodule:: aiogram.types.voice_chat_started
+.. automodule:: aiogram.types.video_chat_started
     :members:
     :member-order: bysource
     :undoc-members: True
diff --git a/docs/api/types/web_app_data.rst b/docs/api/types/web_app_data.rst
new file mode 100644
index 00000000..1a94573f
--- /dev/null
+++ b/docs/api/types/web_app_data.rst
@@ -0,0 +1,9 @@
+##########
+WebAppData
+##########
+
+
+.. automodule:: aiogram.types.web_app_data
+    :members:
+    :member-order: bysource
+    :undoc-members: True
diff --git a/docs/api/types/web_app_info.rst b/docs/api/types/web_app_info.rst
new file mode 100644
index 00000000..b21f0aea
--- /dev/null
+++ b/docs/api/types/web_app_info.rst
@@ -0,0 +1,9 @@
+##########
+WebAppInfo
+##########
+
+
+.. automodule:: aiogram.types.web_app_info
+    :members:
+    :member-order: bysource
+    :undoc-members: True
diff --git a/docs/utils/index.rst b/docs/utils/index.rst
index 4f6eccf5..a7a8474d 100644
--- a/docs/utils/index.rst
+++ b/docs/utils/index.rst
@@ -7,3 +7,4 @@ Utils
     keyboard
     i18n
     chat_action
+    web_app
diff --git a/docs/utils/web_app.rst b/docs/utils/web_app.rst
new file mode 100644
index 00000000..82e9cc42
--- /dev/null
+++ b/docs/utils/web_app.rst
@@ -0,0 +1,55 @@
+======
+WebApз
+======
+
+Telegram Bot API 6.0 announces a revolution in the development of chatbots using WebApp feature.
+
+You can read more details on it in the official `blog <https://telegram.org/blog/notifications-bots#bot-revolution>`_
+and `documentation <https://core.telegram.org/bots/webapps>`_.
+
+`aiogram` implements simple utils to remove headache with the data validation from Telegram WebApp on the backend side.
+
+Usage
+=====
+
+For example from frontend you will pass :code:`application/x-www-form-urlencoded` POST request
+with :code:`_auth` field in body and wants to return User info inside response as :code:`application/json`
+
+.. code-block:: python
+
+    from aiogram.utils.web_app import safe_parse_webapp_init_data
+    from aiohttp.web_request import Request
+    from aiohttp.web_response import json_response
+
+    async def check_data_handler(request: Request):
+        bot: Bot = request.app["bot"]
+
+        data = await request.post()  # application/x-www-form-urlencoded
+        try:
+            data = safe_parse_webapp_init_data(token=bot.token, init_data=data["_auth"])
+        except ValueError:
+            return json_response({"ok": False, "err": "Unauthorized"}, status=401)
+        return json_response({"ok": True, "data": data.user.dict()})
+
+Functions
+=========
+
+.. autofunction:: aiogram.utils.web_app.check_webapp_signature
+
+.. autofunction:: aiogram.utils.web_app.parse_webapp_init_data
+
+.. autofunction:: aiogram.utils.web_app.safe_parse_webapp_init_data
+
+
+Types
+=====
+
+.. autoclass:: aiogram.utils.web_app.WebAppInitData
+    :members:
+    :member-order: bysource
+    :undoc-members: True
+
+.. autoclass:: aiogram.utils.web_app.WebAppUser
+    :members:
+    :member-order: bysource
+    :undoc-members: True
diff --git a/examples/web_app/demo.html b/examples/web_app/demo.html
new file mode 100644
index 00000000..40726974
--- /dev/null
+++ b/examples/web_app/demo.html
@@ -0,0 +1,376 @@
+<!DOCTYPE html>
+<html>
+<head>
+    <meta charset="utf-8">
+    <meta name="viewport"
+          content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no"/>
+    <meta name="format-detection" content="telephone=no"/>
+    <meta http-equiv="X-UA-Compatible" content="IE=edge"/>
+    <meta name="MobileOptimized" content="176"/>
+    <meta name="HandheldFriendly" content="True"/>
+    <meta name="robots" content="noindex,nofollow"/>
+    <script src="https://telegram.org/js/telegram-web-app.js?1"></script>
+    <script>
+        function setThemeClass() {
+            document.documentElement.className = Telegram.WebApp.colorScheme;
+        }
+
+        Telegram.WebApp.onEvent('themeChanged', setThemeClass);
+        setThemeClass();
+
+    </script>
+    <style>
+        body {
+            font-family: sans-serif;
+            background-color: var(--tg-theme-bg-color, #ffffff);
+            color: var(--tg-theme-text-color, #222222);
+            font-size: 16px;
+            margin: 0;
+            padding: 0;
+            color-scheme: var(--tg-color-scheme);
+        }
+
+        a {
+            color: var(--tg-theme-link-color, #2678b6);
+        }
+
+        button {
+            display: block;
+            width: 100%;
+            font-size: 14px;
+            margin: 15px 0;
+            padding: 12px 20px;
+            border: none;
+            border-radius: 4px;
+            background-color: var(--tg-theme-button-color, #50a8eb);
+            color: var(--tg-theme-button-text-color, #ffffff);
+            cursor: pointer;
+        }
+
+        button[disabled] {
+            opacity: 0.6;
+            cursor: auto;
+            pointer-events: none;
+        }
+
+        button.close_btn {
+            /*position: fixed;*/
+            position: absolute;
+            left: 0;
+            right: 0;
+            bottom: 0;
+            border-radius: 0;
+            margin: 0;
+            padding: 16px 20px;
+            text-transform: uppercase;
+        }
+
+        section {
+            padding: 15px 15px 65px;
+            text-align: center;
+        }
+
+        p {
+            margin: 40px 0 15px;
+        }
+
+        ul {
+            text-align: left;
+        }
+
+        li {
+            color: var(--tg-theme-hint-color, #a8a8a8);
+        }
+
+        textarea {
+            width: 100%;
+            box-sizing: border-box;
+            padding: 7px;
+        }
+
+        pre {
+            background: rgba(0, 0, 0, .07);
+            border-radius: 4px;
+            padding: 4px;
+            margin: 7px 0;
+            word-break: break-all;
+            word-break: break-word;
+            white-space: pre-wrap;
+            text-align: left;
+        }
+
+        .dark pre {
+            background: rgba(255, 255, 255, .15);
+        }
+
+        .hint {
+            font-size: .8em;
+            color: var(--tg-theme-hint-color, #a8a8a8);
+        }
+
+        .ok {
+            color: green;
+        }
+
+        .err {
+            color: red;
+        }
+
+        #fixed_wrap {
+            position: fixed;
+            left: 0;
+            right: 0;
+            top: 0;
+            transform: translateY(100vh);
+        }
+
+        .viewport_border,
+        .viewport_stable_border {
+            position: fixed;
+            left: 0;
+            right: 0;
+            top: 0;
+            height: var(--tg-viewport-height, 100vh);
+            pointer-events: none;
+        }
+
+        .viewport_stable_border {
+            height: var(--tg-viewport-stable-height, 100vh);
+        }
+
+        .viewport_border:before,
+        .viewport_stable_border:before {
+            content: attr(text);
+            display: inline-block;
+            position: absolute;
+            background: gray;
+            right: 0;
+            top: 0;
+            font-size: 7px;
+            padding: 2px 4px;
+            vertical-align: top;
+        }
+
+        .viewport_stable_border:before {
+            background: green;
+            left: 0;
+            right: auto;
+        }
+
+        .viewport_border:after,
+        .viewport_stable_border:after {
+            content: '';
+            display: block;
+            position: absolute;
+            left: 0;
+            right: 0;
+            top: 0;
+            bottom: 0;
+            border: 2px dashed gray;
+        }
+
+        .viewport_stable_border:after {
+            border-color: green;
+        }
+    </style>
+</head>
+<body style="visibility: hidden;">
+<section>
+    <button id="main_btn" onclick="sendMessage('');">Send «Hello, World!»</button>
+    <button id="with_webview_btn" onclick="sendMessage('', true);">
+        Send «Hello, World!» with inline webview button
+    </button>
+    <button onclick="webviewExpand();">Expand Webview</button>
+    <button onclick="toggleMainButton(this);">Hide Main Button</button>
+    <div id="btn_status" class="hint" style="display: none;">
+    </div>
+    <p>Test links:</p>
+    <ul>
+        <li><a id="regular_link" href="?nextpage=1">Regular link #1</a> (opens inside webview)</li>
+        <li><a href="https://telegram.org/" target="_blank">target="_blank" link</a> (opens outside
+            webview)
+        </li>
+        <li><a href="javascript:window.open('https://telegram.org/');">window.open() link</a>
+            (opens outside webview)
+        </li>
+        <li><a href="https://t.me/like">LikeBot t.me link</a> (opens inside Telegram app)</li>
+        <li><a href="tg://resolve?domain=vote">VoteBot tg:// link</a> (does not open)</li>
+    </ul>
+    <p>Test permissions:</p>
+    <ul>
+        <li><a href="javascript:;" onclick="return requestLocation(this);">Request Location</a>
+            <span></span></li>
+        <li><a href="javascript:;" onclick="return requestVideo(this);">Request Video</a>
+            <span></span></li>
+        <li><a href="javascript:;" onclick="return requestAudio(this);">Request Audio</a>
+            <span></span></li>
+    </ul>
+    <pre><code id="webview_data"></code></pre>
+    <div class="hint">
+        Data passed to webview.
+        <span id="webview_data_status" style="display: none;">Checking hash...</span>
+    </div>
+    <pre><code id="theme_data"></code></pre>
+    <div class="hint">
+        Theme params
+    </div>
+</section>
+<div class="viewport_border"></div>
+<div class="viewport_stable_border"></div>
+<script src="https://webappcontent.telegram.org/js/jquery.min.js"></script>
+<script>
+    Telegram.WebApp.ready();
+
+    var initData = Telegram.WebApp.initData || '';
+    var initDataUnsafe = Telegram.WebApp.initDataUnsafe || {};
+
+    function sendMessage(msg_id, with_webview) {
+        if (!initDataUnsafe.query_id) {
+            alert('WebViewQueryId not defined');
+            return;
+        }
+        $('button').prop('disabled', true);
+        $('#btn_status').text('Sending...').removeClass('ok err').show();
+        $.ajax('/demo/sendMessage', {
+            type: 'POST',
+            data: {
+                _auth: initData,
+                msg_id: msg_id || '',
+                with_webview: !initDataUnsafe.receiver && with_webview ? 1 : 0
+            },
+            dataType: 'json',
+            success: function (result) {
+                $('button').prop('disabled', false);
+                if (result.response) {
+                    if (result.response.ok) {
+                        $('#btn_status').html('Message sent successfully!').addClass('ok').show();
+                    } else {
+                        $('#btn_status').text(result.response.description).addClass('err').show();
+                        alert(result.response.description);
+                    }
+                } else {
+                    $('#btn_status').text('Unknown error').addClass('err').show();
+                    alert('Unknown error');
+                }
+            },
+            error: function (xhr) {
+                $('button').prop('disabled', false);
+                $('#btn_status').text('Server error').addClass('err').show();
+                alert('Server error');
+            }
+        });
+    }
+
+    function webviewExpand() {
+        Telegram.WebApp.expand();
+    }
+
+    function webviewClose() {
+        Telegram.WebApp.close();
+    }
+
+    function requestLocation(el) {
+        if (navigator.geolocation) {
+            navigator.geolocation.getCurrentPosition(function (position) {
+                $(el).next('span').html('(' + position.coords.latitude + ', ' + position.coords.longitude + ')').attr('class', 'ok');
+            });
+        } else {
+            $(el).next('span').html('Geolocation is not supported in this browser.').attr('class', 'err');
+        }
+        return false;
+    }
+
+    function requestVideo(el) {
+        if (navigator.mediaDevices) {
+            navigator.mediaDevices.getUserMedia({
+                audio: false,
+                video: true
+            }).then(function (stream) {
+                $(el).next('span').html('(Access granted)').attr('class', 'ok');
+            });
+        } else {
+            $(el).next('span').html('Media devices is not supported in this browser.').attr('class', 'err');
+        }
+        return false;
+    }
+
+    function requestAudio(el) {
+        if (navigator.mediaDevices) {
+            navigator.mediaDevices.getUserMedia({
+                audio: true,
+                video: false
+            }).then(function (stream) {
+                $(el).next('span').html('(Access granted)').attr('class', 'ok');
+            });
+        } else {
+            $(el).next('span').html('Media devices is not supported in this browser.').attr('class', 'err');
+        }
+        return false;
+    }
+
+    Telegram.WebApp.onEvent('themeChanged', function () {
+        $('#theme_data').html(JSON.stringify(Telegram.WebApp.themeParams, null, 2));
+    });
+
+    $('#main_btn').toggle(!!initDataUnsafe.query_id);
+    $('#with_webview_btn').toggle(!!initDataUnsafe.query_id && !initDataUnsafe.receiver);
+    // $('#data_btn').toggle(!initDataUnsafe.query_id || !initDataUnsafe.receiver);
+    $('#webview_data').html(JSON.stringify(initDataUnsafe, null, 2));
+    $('#theme_data').html(JSON.stringify(Telegram.WebApp.themeParams, null, 2));
+    $('#regular_link').attr('href', $('#regular_link').attr('href') + location.hash);
+    $('#text_field').focus();
+    if (initDataUnsafe.query_id && initData) {
+        $('#webview_data_status').show();
+        $.ajax('/demo/checkData', {
+            type: 'POST',
+            data: {_auth: initData},
+            dataType: 'json',
+            success: function (result) {
+                if (result.ok) {
+                    $('#webview_data_status').html('Hash is correct').addClass('ok');
+                } else {
+                    $('#webview_data_status').html(result.error).addClass('err');
+                }
+            },
+            error: function (xhr) {
+                $('#webview_data_status').html('Server error').addClass('err');
+            }
+        });
+    }
+    $('body').css('visibility', '');
+    Telegram.WebApp.MainButton
+        .setText('CLOSE WEBVIEW')
+        .show()
+        .onClick(function () {
+            webviewClose();
+        });
+
+    function toggleMainButton(el) {
+        var mainButton = Telegram.WebApp.MainButton;
+        if (mainButton.isVisible) {
+            mainButton.hide();
+            el.innerHTML = 'Show Main Button';
+        } else {
+            mainButton.show();
+            el.innerHTML = 'Hide Main Button';
+        }
+    }
+
+    function round(val, d) {
+        var k = Math.pow(10, d || 0);
+        return Math.round(val * k) / k;
+    }
+
+    function setViewportData() {
+        $('.viewport_border').attr('text', window.innerWidth + ' x ' + round(Telegram.WebApp.viewportHeight, 2));
+        $('.viewport_stable_border').attr('text', window.innerWidth + ' x ' + round(Telegram.WebApp.viewportStableHeight, 2) + ' | is_expanded: ' + (Telegram.WebApp.isExpanded ? 'true' : 'false'));
+    }
+
+    Telegram.WebApp.onEvent('viewportChanged', setViewportData);
+    setViewportData();
+
+
+</script>
+</body>
+</html>
+<!-- page generated in 1.11ms -->
diff --git a/examples/web_app/handlers.py b/examples/web_app/handlers.py
new file mode 100644
index 00000000..745d6645
--- /dev/null
+++ b/examples/web_app/handlers.py
@@ -0,0 +1,48 @@
+from aiogram import Bot, F, Router
+from aiogram.dispatcher.filters import Command
+from aiogram.types import (
+    InlineKeyboardButton,
+    InlineKeyboardMarkup,
+    MenuButtonWebApp,
+    Message,
+    WebAppInfo,
+)
+
+my_router = Router()
+
+
+@my_router.message(Command(commands=["start"]))
+async def command_start(message: Message, bot: Bot, base_url: str):
+    await bot.set_chat_menu_button(
+        chat_id=message.chat.id,
+        menu_button=MenuButtonWebApp(text="Open Menu", web_app=WebAppInfo(url=f"{base_url}/demo")),
+    )
+    await message.answer("""Hi!\nSend me any type of message to start.\nOr just send /webview""")
+
+
+@my_router.message(Command(commands=["webview"]))
+async def command_webview(message: Message, base_url: str):
+    await message.answer(
+        "Good. Now you can try to send it via Webview",
+        reply_markup=InlineKeyboardMarkup(
+            inline_keyboard=[
+                [
+                    InlineKeyboardButton(
+                        text="Open Webview", web_app=WebAppInfo(url=f"{base_url}/demo")
+                    )
+                ]
+            ]
+        ),
+    )
+
+
+@my_router.message(~F.message.via_bot)  # Echo to all messages except messages via bot
+async def echo_all(message: Message, base_url: str):
+    await message.answer(
+        "Test webview",
+        reply_markup=InlineKeyboardMarkup(
+            inline_keyboard=[
+                [InlineKeyboardButton(text="Open", web_app=WebAppInfo(url=f"{base_url}/demo"))]
+            ]
+        ),
+    )
diff --git a/examples/web_app/main.py b/examples/web_app/main.py
new file mode 100644
index 00000000..9f5260ad
--- /dev/null
+++ b/examples/web_app/main.py
@@ -0,0 +1,49 @@
+import logging
+from os import getenv
+
+from aiohttp.web import run_app
+from aiohttp.web_app import Application
+from handlers import my_router
+from routes import check_data_handler, demo_handler, send_message_handler
+
+from aiogram import Bot, Dispatcher
+from aiogram.dispatcher.webhook.aiohttp_server import SimpleRequestHandler, setup_application
+from aiogram.types import MenuButtonWebApp, WebAppInfo
+
+TELEGRAM_TOKEN = getenv("TELEGRAM_TOKEN")
+APP_BASE_URL = getenv("APP_BASE_URL")
+
+
+async def on_startup(bot: Bot, base_url: str):
+    await bot.set_webhook(f"{base_url}/webhook")
+    await bot.set_chat_menu_button(
+        menu_button=MenuButtonWebApp(text="Open Menu", web_app=WebAppInfo(url=f"{base_url}/demo"))
+    )
+
+
+def main():
+    bot = Bot(token=TELEGRAM_TOKEN, parse_mode="HTML")
+    dispatcher = Dispatcher()
+    dispatcher["base_url"] = APP_BASE_URL
+    dispatcher.startup.register(on_startup)
+
+    dispatcher.include_router(my_router)
+
+    app = Application()
+    app["bot"] = bot
+
+    app.router.add_get("/demo", demo_handler)
+    app.router.add_post("/demo/checkData", check_data_handler)
+    app.router.add_post("/demo/sendMessage", send_message_handler)
+    SimpleRequestHandler(
+        dispatcher=dispatcher,
+        bot=bot,
+    ).register(app, path="/webhook")
+    setup_application(app, dispatcher, bot=bot)
+
+    run_app(app, host="127.0.0.1", port=8081)
+
+
+if __name__ == "__main__":
+    logging.basicConfig(level=logging.INFO)
+    main()
diff --git a/examples/web_app/routes.py b/examples/web_app/routes.py
new file mode 100644
index 00000000..d8c6b697
--- /dev/null
+++ b/examples/web_app/routes.py
@@ -0,0 +1,64 @@
+from pathlib import Path
+
+from aiohttp.web_fileresponse import FileResponse
+from aiohttp.web_request import Request
+from aiohttp.web_response import json_response
+
+from aiogram import Bot
+from aiogram.types import (
+    InlineKeyboardButton,
+    InlineKeyboardMarkup,
+    InlineQueryResultArticle,
+    InputTextMessageContent,
+    WebAppInfo,
+)
+from aiogram.utils.web_app import check_webapp_signature, safe_parse_webapp_init_data
+
+
+async def demo_handler(request: Request):
+    return FileResponse(Path(__file__).parent.resolve() / "demo.html")
+
+
+async def check_data_handler(request: Request):
+    bot: Bot = request.app["bot"]
+
+    data = await request.post()
+    if check_webapp_signature(bot.token, data["_auth"]):
+        return json_response({"ok": True})
+    return json_response({"ok": False, "err": "Unauthorized"}, status=401)
+
+
+async def send_message_handler(request: Request):
+    bot: Bot = request.app["bot"]
+    data = await request.post()
+    try:
+        web_app_init_data = safe_parse_webapp_init_data(token=bot.token, init_data=data["_auth"])
+    except ValueError:
+        return json_response({"ok": False, "err": "Unauthorized"}, status=401)
+
+    print(data)
+    reply_markup = None
+    if data["with_webview"] == "1":
+        reply_markup = InlineKeyboardMarkup(
+            inline_keyboard=[
+                [
+                    InlineKeyboardButton(
+                        text="Open",
+                        web_app=WebAppInfo(url=str(request.url.with_scheme("https"))),
+                    )
+                ]
+            ]
+        )
+    await bot.answer_web_app_query(
+        web_app_query_id=web_app_init_data.query_id,
+        result=InlineQueryResultArticle(
+            id=web_app_init_data.query_id,
+            title="Demo",
+            input_message_content=InputTextMessageContent(
+                message_text="Hello, World!",
+                parse_mode=None,
+            ),
+            reply_markup=reply_markup,
+        ),
+    )
+    return json_response({"ok": True})


Test patch:
diff --git a/tests/test_api/test_methods/test_answer_web_app_query.py b/tests/test_api/test_methods/test_answer_web_app_query.py
new file mode 100644
index 00000000..8d9848da
--- /dev/null
+++ b/tests/test_api/test_methods/test_answer_web_app_query.py
@@ -0,0 +1,33 @@
+import pytest
+
+from aiogram.methods import AnswerWebAppQuery, Request
+from aiogram.types import InlineQueryResult, SentWebAppMessage
+from tests.mocked_bot import MockedBot
+
+
+class TestAnswerWebAppQuery:
+    @pytest.mark.asyncio
+    async def test_method(self, bot: MockedBot):
+        prepare_result = bot.add_result_for(AnswerWebAppQuery, ok=True, result=SentWebAppMessage())
+
+        response: SentWebAppMessage = await AnswerWebAppQuery(
+            web_app_query_id="test",
+            result=InlineQueryResult(),
+        )
+        request: Request = bot.get_request()
+        assert request.method == "answerWebAppQuery"
+        # assert request.data == {}
+        assert response == prepare_result.result
+
+    @pytest.mark.asyncio
+    async def test_bot_method(self, bot: MockedBot):
+        prepare_result = bot.add_result_for(AnswerWebAppQuery, ok=True, result=SentWebAppMessage())
+
+        response: SentWebAppMessage = await bot.answer_web_app_query(
+            web_app_query_id="test",
+            result=InlineQueryResult(),
+        )
+        request: Request = bot.get_request()
+        assert request.method == "answerWebAppQuery"
+        # assert request.data == {}
+        assert response == prepare_result.result
diff --git a/tests/test_api/test_methods/test_approve_chat_join_request.py b/tests/test_api/test_methods/test_approve_chat_join_request.py
old mode 100644
new mode 100755
diff --git a/tests/test_api/test_methods/test_ban_chat_sender_chat.py b/tests/test_api/test_methods/test_ban_chat_sender_chat.py
old mode 100644
new mode 100755
diff --git a/tests/test_api/test_methods/test_decline_chat_join_request.py b/tests/test_api/test_methods/test_decline_chat_join_request.py
old mode 100644
new mode 100755
diff --git a/tests/test_api/test_methods/test_get_chat_menu_button.py b/tests/test_api/test_methods/test_get_chat_menu_button.py
new file mode 100644
index 00000000..a7c2fd37
--- /dev/null
+++ b/tests/test_api/test_methods/test_get_chat_menu_button.py
@@ -0,0 +1,27 @@
+import pytest
+
+from aiogram.methods import GetChatMenuButton, Request
+from aiogram.types import MenuButton, MenuButtonDefault
+from tests.mocked_bot import MockedBot
+
+
+class TestGetChatMenuButton:
+    @pytest.mark.asyncio
+    async def test_method(self, bot: MockedBot):
+        prepare_result = bot.add_result_for(GetChatMenuButton, ok=True, result=MenuButtonDefault())
+
+        response: MenuButton = await GetChatMenuButton()
+        request: Request = bot.get_request()
+        assert request.method == "getChatMenuButton"
+        # assert request.data == {}
+        assert response == prepare_result.result
+
+    @pytest.mark.asyncio
+    async def test_bot_method(self, bot: MockedBot):
+        prepare_result = bot.add_result_for(GetChatMenuButton, ok=True, result=MenuButtonDefault())
+
+        response: MenuButton = await bot.get_chat_menu_button()
+        request: Request = bot.get_request()
+        assert request.method == "getChatMenuButton"
+        # assert request.data == {}
+        assert response == prepare_result.result
diff --git a/tests/test_api/test_methods/test_get_my_default_administrator_rights.py b/tests/test_api/test_methods/test_get_my_default_administrator_rights.py
new file mode 100644
index 00000000..179b468d
--- /dev/null
+++ b/tests/test_api/test_methods/test_get_my_default_administrator_rights.py
@@ -0,0 +1,53 @@
+import pytest
+
+from aiogram.methods import GetMyDefaultAdministratorRights, Request
+from aiogram.types import ChatAdministratorRights
+from tests.mocked_bot import MockedBot
+
+
+class TestGetMyDefaultAdministratorRights:
+    @pytest.mark.asyncio
+    async def test_method(self, bot: MockedBot):
+        prepare_result = bot.add_result_for(
+            GetMyDefaultAdministratorRights,
+            ok=True,
+            result=ChatAdministratorRights(
+                is_anonymous=False,
+                can_manage_chat=False,
+                can_delete_messages=False,
+                can_manage_video_chats=False,
+                can_restrict_members=False,
+                can_promote_members=False,
+                can_change_info=False,
+                can_invite_users=False,
+            ),
+        )
+
+        response: ChatAdministratorRights = await GetMyDefaultAdministratorRights()
+        request: Request = bot.get_request()
+        assert request.method == "getMyDefaultAdministratorRights"
+        # assert request.data == {}
+        assert response == prepare_result.result
+
+    @pytest.mark.asyncio
+    async def test_bot_method(self, bot: MockedBot):
+        prepare_result = bot.add_result_for(
+            GetMyDefaultAdministratorRights,
+            ok=True,
+            result=ChatAdministratorRights(
+                is_anonymous=False,
+                can_manage_chat=False,
+                can_delete_messages=False,
+                can_manage_video_chats=False,
+                can_restrict_members=False,
+                can_promote_members=False,
+                can_change_info=False,
+                can_invite_users=False,
+            ),
+        )
+
+        response: ChatAdministratorRights = await bot.get_my_default_administrator_rights()
+        request: Request = bot.get_request()
+        assert request.method == "getMyDefaultAdministratorRights"
+        # assert request.data == {}
+        assert response == prepare_result.result
diff --git a/tests/test_api/test_methods/test_get_sticker_set.py b/tests/test_api/test_methods/test_get_sticker_set.py
index baed1d40..d778f1f7 100644
--- a/tests/test_api/test_methods/test_get_sticker_set.py
+++ b/tests/test_api/test_methods/test_get_sticker_set.py
@@ -16,6 +16,7 @@ class TestGetStickerSet:
                 name="test",
                 title="test",
                 is_animated=False,
+                is_video=False,
                 contains_masks=False,
                 stickers=[
                     Sticker(
@@ -23,6 +24,7 @@ class TestGetStickerSet:
                         width=42,
                         height=42,
                         is_animated=False,
+                        is_video=False,
                         file_unique_id="file id",
                     )
                 ],
@@ -42,6 +44,7 @@ class TestGetStickerSet:
                 name="test",
                 title="test",
                 is_animated=False,
+                is_video=False,
                 contains_masks=False,
                 stickers=[
                     Sticker(
@@ -49,6 +52,7 @@ class TestGetStickerSet:
                         width=42,
                         height=42,
                         is_animated=False,
+                        is_video=False,
                         file_unique_id="file id",
                     )
                 ],
diff --git a/tests/test_api/test_methods/test_send_sticker.py b/tests/test_api/test_methods/test_send_sticker.py
index d356e8ae..239065eb 100644
--- a/tests/test_api/test_methods/test_send_sticker.py
+++ b/tests/test_api/test_methods/test_send_sticker.py
@@ -22,6 +22,7 @@ class TestSendSticker:
                     width=42,
                     height=42,
                     is_animated=False,
+                    is_video=False,
                     file_unique_id="file id",
                 ),
                 chat=Chat(id=42, type="private"),
@@ -45,6 +46,7 @@ class TestSendSticker:
                     width=42,
                     height=42,
                     is_animated=False,
+                    is_video=False,
                     file_unique_id="file id",
                 ),
                 chat=Chat(id=42, type="private"),
diff --git a/tests/test_api/test_methods/test_set_chat_menu_button.py b/tests/test_api/test_methods/test_set_chat_menu_button.py
new file mode 100644
index 00000000..97e2fa90
--- /dev/null
+++ b/tests/test_api/test_methods/test_set_chat_menu_button.py
@@ -0,0 +1,26 @@
+import pytest
+
+from aiogram.methods import Request, SetChatMenuButton
+from tests.mocked_bot import MockedBot
+
+
+class TestSetChatMenuButton:
+    @pytest.mark.asyncio
+    async def test_method(self, bot: MockedBot):
+        prepare_result = bot.add_result_for(SetChatMenuButton, ok=True, result=True)
+
+        response: bool = await SetChatMenuButton()
+        request: Request = bot.get_request()
+        assert request.method == "setChatMenuButton"
+        # assert request.data == {}
+        assert response == prepare_result.result
+
+    @pytest.mark.asyncio
+    async def test_bot_method(self, bot: MockedBot):
+        prepare_result = bot.add_result_for(SetChatMenuButton, ok=True, result=True)
+
+        response: bool = await bot.set_chat_menu_button()
+        request: Request = bot.get_request()
+        assert request.method == "setChatMenuButton"
+        # assert request.data == {}
+        assert response == prepare_result.result
diff --git a/tests/test_api/test_methods/test_set_my_default_administrator_rights.py b/tests/test_api/test_methods/test_set_my_default_administrator_rights.py
new file mode 100644
index 00000000..4bd08822
--- /dev/null
+++ b/tests/test_api/test_methods/test_set_my_default_administrator_rights.py
@@ -0,0 +1,26 @@
+import pytest
+
+from aiogram.methods import Request, SetMyDefaultAdministratorRights
+from tests.mocked_bot import MockedBot
+
+
+class TestSetMyDefaultAdministratorRights:
+    @pytest.mark.asyncio
+    async def test_method(self, bot: MockedBot):
+        prepare_result = bot.add_result_for(SetMyDefaultAdministratorRights, ok=True, result=True)
+
+        response: bool = await SetMyDefaultAdministratorRights()
+        request: Request = bot.get_request()
+        assert request.method == "setMyDefaultAdministratorRights"
+        # assert request.data == {}
+        assert response == prepare_result.result
+
+    @pytest.mark.asyncio
+    async def test_bot_method(self, bot: MockedBot):
+        prepare_result = bot.add_result_for(SetMyDefaultAdministratorRights, ok=True, result=True)
+
+        response: bool = await bot.set_my_default_administrator_rights()
+        request: Request = bot.get_request()
+        assert request.method == "setMyDefaultAdministratorRights"
+        # assert request.data == {}
+        assert response == prepare_result.result
diff --git a/tests/test_api/test_methods/test_unban_chat_sender_chat.py b/tests/test_api/test_methods/test_unban_chat_sender_chat.py
old mode 100644
new mode 100755
diff --git a/tests/test_api/test_types/test_message.py b/tests/test_api/test_types/test_message.py
index b2b66b77..fcf357c4 100644
--- a/tests/test_api/test_types/test_message.py
+++ b/tests/test_api/test_types/test_message.py
@@ -52,11 +52,13 @@ from aiogram.types import (
     User,
     Venue,
     Video,
+    VideoChatEnded,
+    VideoChatParticipantsInvited,
+    VideoChatScheduled,
+    VideoChatStarted,
     VideoNote,
     Voice,
-    VoiceChatEnded,
-    VoiceChatParticipantsInvited,
-    VoiceChatStarted,
+    WebAppData,
 )
 from aiogram.types.message import ContentType, Message
 
@@ -122,6 +124,7 @@ TEST_MESSAGE_STICKER = Message(
         width=42,
         height=42,
         is_animated=False,
+        is_video=False,
     ),
     chat=Chat(id=42, type="private"),
     from_user=User(id=42, is_bot=False, first_name="Test"),
@@ -318,29 +321,38 @@ TEST_MESSAGE_MESSAGE_AUTO_DELETE_TIMER_CHANGED = Message(
     message_auto_delete_timer_changed=MessageAutoDeleteTimerChanged(message_auto_delete_time=42),
     from_user=User(id=42, is_bot=False, first_name="Test"),
 )
-TEST_MESSAGE_VOICE_CHAT_STARTED = Message(
+TEST_MESSAGE_VIDEO_CHAT_STARTED = Message(
     message_id=42,
     date=datetime.datetime.now(),
     chat=Chat(id=42, type="private"),
     from_user=User(id=42, is_bot=False, first_name="Test"),
-    voice_chat_started=VoiceChatStarted(),
+    video_chat_started=VideoChatStarted(),
 )
-TEST_MESSAGE_VOICE_CHAT_ENDED = Message(
+TEST_MESSAGE_VIDEO_CHAT_ENDED = Message(
     message_id=42,
     date=datetime.datetime.now(),
     chat=Chat(id=42, type="private"),
     from_user=User(id=42, is_bot=False, first_name="Test"),
-    voice_chat_ended=VoiceChatEnded(duration=42),
+    video_chat_ended=VideoChatEnded(duration=42),
 )
-TEST_MESSAGE_VOICE_CHAT_PARTICIPANTS_INVITED = Message(
+TEST_MESSAGE_VIDEO_CHAT_PARTICIPANTS_INVITED = Message(
     message_id=42,
     date=datetime.datetime.now(),
     chat=Chat(id=42, type="private"),
     from_user=User(id=42, is_bot=False, first_name="Test"),
-    voice_chat_participants_invited=VoiceChatParticipantsInvited(
+    video_chat_participants_invited=VideoChatParticipantsInvited(
         users=[User(id=69, is_bot=False, first_name="Test")]
     ),
 )
+TEST_MESSAGE_VIDEO_CHAT_SCHEDULED = Message(
+    message_id=42,
+    date=datetime.datetime.now(),
+    chat=Chat(id=42, type="private"),
+    from_user=User(id=42, is_bot=False, first_name="Test"),
+    video_chat_scheduled=VideoChatScheduled(
+        start_date=datetime.datetime.now(),
+    ),
+)
 TEST_MESSAGE_DICE = Message(
     message_id=42,
     date=datetime.datetime.now(),
@@ -348,6 +360,13 @@ TEST_MESSAGE_DICE = Message(
     dice=Dice(value=6, emoji="X"),
     from_user=User(id=42, is_bot=False, first_name="Test"),
 )
+TEST_MESSAGE_WEB_APP_DATA = Message(
+    message_id=42,
+    date=datetime.datetime.now(),
+    chat=Chat(id=42, type="private"),
+    web_app_data=WebAppData(data="test", button_text="Test"),
+    from_user=User(id=42, is_bot=False, first_name="Test"),
+)
 TEST_MESSAGE_UNKNOWN = Message(
     message_id=42,
     date=datetime.datetime.now(),
@@ -391,13 +410,15 @@ class TestMessage:
                 TEST_MESSAGE_MESSAGE_AUTO_DELETE_TIMER_CHANGED,
                 ContentType.MESSAGE_AUTO_DELETE_TIMER_CHANGED,
             ],
-            [TEST_MESSAGE_VOICE_CHAT_STARTED, ContentType.VOICE_CHAT_STARTED],
-            [TEST_MESSAGE_VOICE_CHAT_ENDED, ContentType.VOICE_CHAT_ENDED],
+            [TEST_MESSAGE_VIDEO_CHAT_SCHEDULED, ContentType.VIDEO_CHAT_SCHEDULED],
+            [TEST_MESSAGE_VIDEO_CHAT_STARTED, ContentType.VIDEO_CHAT_STARTED],
+            [TEST_MESSAGE_VIDEO_CHAT_ENDED, ContentType.VIDEO_CHAT_ENDED],
             [
-                TEST_MESSAGE_VOICE_CHAT_PARTICIPANTS_INVITED,
-                ContentType.VOICE_CHAT_PARTICIPANTS_INVITED,
+                TEST_MESSAGE_VIDEO_CHAT_PARTICIPANTS_INVITED,
+                ContentType.VIDEO_CHAT_PARTICIPANTS_INVITED,
             ],
             [TEST_MESSAGE_DICE, ContentType.DICE],
+            [TEST_MESSAGE_WEB_APP_DATA, ContentType.WEB_APP_DATA],
             [TEST_MESSAGE_UNKNOWN, ContentType.UNKNOWN],
         ],
     )
@@ -535,9 +556,9 @@ class TestMessage:
             [TEST_MESSAGE_PASSPORT_DATA, None],
             [TEST_MESSAGE_POLL, SendPoll],
             [TEST_MESSAGE_MESSAGE_AUTO_DELETE_TIMER_CHANGED, None],
-            [TEST_MESSAGE_VOICE_CHAT_STARTED, None],
-            [TEST_MESSAGE_VOICE_CHAT_ENDED, None],
-            [TEST_MESSAGE_VOICE_CHAT_PARTICIPANTS_INVITED, None],
+            [TEST_MESSAGE_VIDEO_CHAT_STARTED, None],
+            [TEST_MESSAGE_VIDEO_CHAT_ENDED, None],
+            [TEST_MESSAGE_VIDEO_CHAT_PARTICIPANTS_INVITED, None],
             [TEST_MESSAGE_DICE, SendDice],
             [TEST_MESSAGE_UNKNOWN, None],
         ],
diff --git a/tests/test_dispatcher/test_dispatcher.py b/tests/test_dispatcher/test_dispatcher.py
index 89d027b1..f501ed75 100644
--- a/tests/test_dispatcher/test_dispatcher.py
+++ b/tests/test_dispatcher/test_dispatcher.py
@@ -82,11 +82,11 @@ class TestDispatcher:
         assert dp.get("foo", 42) == 42
 
         dp["foo"] = 1
-        assert dp._data["foo"] == 1
+        assert dp.workflow_data["foo"] == 1
         assert dp["foo"] == 1
 
         del dp["foo"]
-        assert "foo" not in dp._data
+        assert "foo" not in dp.workflow_data
 
     def test_storage_property(self, dispatcher: Dispatcher):
         assert dispatcher.storage is dispatcher.fsm.storage
diff --git a/tests/test_dispatcher/test_filters/test_chat_member_updated.py b/tests/test_dispatcher/test_filters/test_chat_member_updated.py
index 63ee1245..dae0e985 100644
--- a/tests/test_dispatcher/test_filters/test_chat_member_updated.py
+++ b/tests/test_dispatcher/test_filters/test_chat_member_updated.py
@@ -320,7 +320,7 @@ class TestChatMemberUpdatedStatusFilter:
             "can_be_edited": True,
             "can_manage_chat": True,
             "can_delete_messages": True,
-            "can_manage_voice_chats": True,
+            "can_manage_video_chats": True,
             "can_restrict_members": True,
             "can_promote_members": True,
             "can_change_info": True,
diff --git a/tests/test_utils/test_web_app.py b/tests/test_utils/test_web_app.py
new file mode 100644
index 00000000..abebd909
--- /dev/null
+++ b/tests/test_utils/test_web_app.py
@@ -0,0 +1,80 @@
+import pytest
+
+from aiogram.utils.web_app import (
+    WebAppInitData,
+    check_webapp_signature,
+    parse_webapp_init_data,
+    safe_parse_webapp_init_data,
+)
+
+
+class TestWebApp:
+    @pytest.mark.parametrize(
+        "token,case,result",
+        [
+            [
+                "42:TEST",
+                "auth_date=1650385342"
+                "&user=%7B%22id%22%3A42%2C%22first_name%22%3A%22Test%22%7D"
+                "&query_id=test"
+                "&hash=46d2ea5e32911ec8d30999b56247654460c0d20949b6277af519e76271182803",
+                True,
+            ],
+            [
+                "42:INVALID",
+                "auth_date=1650385342"
+                "&user=%7B%22id%22%3A42%2C%22first_name%22%3A%22Test%22%7D"
+                "&query_id=test"
+                "&hash=46d2ea5e32911ec8d30999b56247654460c0d20949b6277af519e76271182803",
+                False,
+            ],
+            [
+                "42:TEST",
+                "user=%7B%22id%22%3A42%2C%22first_name%22%3A%22Test%22%7D&query_id=test&hash=test",
+                False,
+            ],
+            [
+                "42:TEST",
+                "user=%7B%22id%22%3A42%2C%22first_name%22%3A%22Test%22%7D&query_id=test",
+                False,
+            ],
+            ["42:TEST", "", False],
+            ["42:TEST", "test&foo=bar=baz", False],
+        ],
+    )
+    def test_check_webapp_signature(self, token, case, result):
+        assert check_webapp_signature(token, case) is result
+
+    def test_parse_web_app_init_data(self):
+        parsed = parse_webapp_init_data(
+            "auth_date=1650385342"
+            "&user=%7B%22id%22%3A42%2C%22first_name%22%3A%22Test%22%7D"
+            "&query_id=test"
+            "&hash=46d2ea5e32911ec8d30999b56247654460c0d20949b6277af519e76271182803",
+        )
+        assert isinstance(parsed, WebAppInitData)
+        assert parsed.user
+        assert parsed.user.first_name == "Test"
+        assert parsed.user.id == 42
+        assert parsed.query_id == "test"
+        assert parsed.hash == "46d2ea5e32911ec8d30999b56247654460c0d20949b6277af519e76271182803"
+        assert parsed.auth_date.year == 2022
+
+    def test_valid_safe_parse_webapp_init_data(self):
+        assert safe_parse_webapp_init_data(
+            "42:TEST",
+            "auth_date=1650385342"
+            "&user=%7B%22id%22%3A42%2C%22first_name%22%3A%22Test%22%7D"
+            "&query_id=test"
+            "&hash=46d2ea5e32911ec8d30999b56247654460c0d20949b6277af519e76271182803",
+        )
+
+    def test_invalid_safe_parse_webapp_init_data(self):
+        with pytest.raises(ValueError):
+            safe_parse_webapp_init_data(
+                "42:TOKEN",
+                "auth_date=1650385342"
+                "&user=%7B%22id%22%3A42%2C%22first_name%22%3A%22Test%22%7D"
+                "&query_id=test"
+                "&hash=test",
+            )