diff --git a/CHANGELOG.md b/CHANGELOG.md
index f3ef54d8..5fc64a20 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -42,6 +42,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 ### Fixed
 
+- Deserialization of chat migrations, see issue [#427][issue427] ([#143][pr143])
 - Type of `BanChatMember::until_date`: `u64` -> `chrono::DateTime<Utc>` ([#116][pr116])
 - Type of `Poll::correct_option_id`: `i32` -> `u8` ([#119][pr119])
 - Type of `Poll::open_period`: `i32` -> `u16` ([#119][pr119])
@@ -54,7 +55,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 [pr133]: https://github.com/teloxide/teloxide-core/pull/133
 [pr141]: https://github.com/teloxide/teloxide-core/pull/141
 [pr142]: https://github.com/teloxide/teloxide-core/pull/142
+[pr143]: https://github.com/teloxide/teloxide-core/pull/143
 [issue473]: https://github.com/teloxide/teloxide/issues/473
+[issue427]: https://github.com/teloxide/teloxide/issues/427
 
 ## 0.3.3 - 2021-08-03
 
diff --git a/src/types/encrypted_credentials.rs b/src/types/encrypted_credentials.rs
index 583821e7..50d87c73 100644
--- a/src/types/encrypted_credentials.rs
+++ b/src/types/encrypted_credentials.rs
@@ -43,8 +43,8 @@ mod tests {
             "hash":"1122",
             "secret":"secret"
         }"#
-        .replace("\n", "")
-        .replace(" ", "");
+        .replace('\n', "")
+        .replace(' ', "");
         let encrypted_credentials = EncryptedCredentials {
             data: "someData".to_string(),
             hash: "1122".to_string(),
diff --git a/src/types/message.rs b/src/types/message.rs
index 0afd73f4..c9a4ed48 100644
--- a/src/types/message.rs
+++ b/src/types/message.rs
@@ -46,7 +46,6 @@ pub enum MessageKind {
     SupergroupChatCreated(MessageSupergroupChatCreated),
     ChannelChatCreated(MessageChannelChatCreated),
     MessageAutoDeleteTimerChanged(MessageMessageAutoDeleteTimerChanged),
-    Migrate(MessageMigrate),
     Pinned(MessagePinned),
     Invoice(MessageInvoice),
     SuccessfulPayment(MessageSuccessfulPayment),
@@ -157,23 +156,32 @@ pub struct MessageMessageAutoDeleteTimerChanged {
     pub message_auto_delete_timer_changed: MessageAutoDeleteTimerChanged,
 }
 
-#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
-pub struct MessageMigrate {
+/// Represents group migration to a supergroup or a supergroup migration from a
+/// group.
+///
+/// Note that bot receives **both** updates. For example: a group with id `0`
+/// migrates to a supergroup with id `1` bots in that group will receive 2
+/// updates:
+/// - `message.chat.id = 0`, `message.chat_migration() = ChatMigration::To {
+///   chat_id: 1 }`
+/// - `message.chat.id = 1`, `message.chat_migration() = ChatMigration::From {
+///   chat_id: 0 }`
+#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
+#[serde(untagged)]
+pub enum ChatMigration {
     /// The group has been migrated to a supergroup with the specified
-    /// identifier. This number may be greater than 32 bits and some
-    /// programming languages may have difficulty/silent defects in
-    /// interpreting it. But it is smaller than 52 bits, so a signed 64 bit
-    /// integer or double-precision float type are safe for storing this
-    /// identifier.
-    pub migrate_to_chat_id: i64,
+    /// identifier `chat_id`.
+    To {
+        #[serde(rename = "migrate_to_chat_id")]
+        chat_id: i64,
+    },
 
     /// The supergroup has been migrated from a group with the specified
-    /// identifier. This number may be greater than 32 bits and some
-    /// programming languages may have difficulty/silent defects in
-    /// interpreting it. But it is smaller than 52 bits, so a signed 64 bit
-    /// integer or double-precision float type are safe for storing this
-    /// identifier.
-    pub migrate_from_chat_id: i64,
+    /// identifier `chat_id`.
+    From {
+        #[serde(rename = "migrate_from_chat_id")]
+        chat_id: i64,
+    },
 }
 
 #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
@@ -283,6 +291,7 @@ pub enum MediaKind {
     VideoNote(MediaVideoNote),
     Voice(MediaVoice),
     Venue(MediaVenue),
+    Migration(ChatMigration),
 }
 
 #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
@@ -500,15 +509,15 @@ mod getters {
     use crate::types::{
         self,
         message::{ForwardKind::NonChannel, MessageKind::*},
-        Chat, ForwardChannel, ForwardKind, ForwardNonChannel, ForwardOrigin, ForwardedFrom,
-        MediaAnimation, MediaAudio, MediaContact, MediaDocument, MediaGame, MediaKind,
-        MediaLocation, MediaPhoto, MediaPoll, MediaSticker, MediaText, MediaVenue, MediaVideo,
-        MediaVideoNote, MediaVoice, Message, MessageChannelChatCreated, MessageCommon,
+        Chat, ChatMigration, ForwardChannel, ForwardKind, ForwardNonChannel, ForwardOrigin,
+        ForwardedFrom, MediaAnimation, MediaAudio, MediaContact, MediaDocument, MediaGame,
+        MediaKind, MediaLocation, MediaPhoto, MediaPoll, MediaSticker, MediaText, MediaVenue,
+        MediaVideo, MediaVideoNote, MediaVoice, Message, MessageChannelChatCreated, MessageCommon,
         MessageConnectedWebsite, MessageDeleteChatPhoto, MessageDice, MessageEntity,
-        MessageGroupChatCreated, MessageInvoice, MessageLeftChatMember, MessageMigrate,
-        MessageNewChatMembers, MessageNewChatPhoto, MessageNewChatTitle, MessagePassportData,
-        MessagePinned, MessageProximityAlertTriggered, MessageSuccessfulPayment,
-        MessageSupergroupChatCreated, PhotoSize, True, User,
+        MessageGroupChatCreated, MessageInvoice, MessageLeftChatMember, MessageNewChatMembers,
+        MessageNewChatPhoto, MessageNewChatTitle, MessagePassportData, MessagePinned,
+        MessageProximityAlertTriggered, MessageSuccessfulPayment, MessageSupergroupChatCreated,
+        PhotoSize, True, User,
     };
 
     /// Getters for [Message] fields from [telegram docs].
@@ -926,21 +935,32 @@ mod getters {
             }
         }
 
+        pub fn chat_migration(&self) -> Option<ChatMigration> {
+            match &self.kind {
+                Common(MessageCommon {
+                    media_kind: MediaKind::Migration(chat_migration),
+                    ..
+                }) => Some(*chat_migration),
+                _ => None,
+            }
+        }
+
         pub fn migrate_to_chat_id(&self) -> Option<i64> {
             match &self.kind {
-                Migrate(MessageMigrate {
-                    migrate_to_chat_id, ..
-                }) => Some(*migrate_to_chat_id),
+                Common(MessageCommon {
+                    media_kind: MediaKind::Migration(ChatMigration::To { chat_id }),
+                    ..
+                }) => Some(*chat_id),
                 _ => None,
             }
         }
 
         pub fn migrate_from_chat_id(&self) -> Option<i64> {
             match &self.kind {
-                Migrate(MessageMigrate {
-                    migrate_from_chat_id,
+                Common(MessageCommon {
+                    media_kind: MediaKind::Migration(ChatMigration::From { chat_id }),
                     ..
-                }) => Some(*migrate_from_chat_id),
+                }) => Some(*chat_id),
                 _ => None,
             }
         }
@@ -1252,4 +1272,42 @@ mod tests {
         let message = from_str::<Message>(json);
         assert!(message.is_ok());
     }
+
+    /// Regression test for <https://github.com/teloxide/teloxide/issues/427>
+    #[test]
+    fn issue_427() {
+        let old = -599075523;
+        let new = -1001555296434;
+
+        // Migration to a supergroup
+        let json = r#"{"chat":{"all_members_are_administrators":false,"id":-599075523,"title":"test","type":"group"},"date":1629404938,"from":{"first_name":"nullptr","id":729497414,"is_bot":false,"language_code":"en","username":"hex0x0000"},"message_id":16,"migrate_to_chat_id":-1001555296434}"#;
+        let message: Message = from_str(json).unwrap();
+
+        assert_eq!(message.chat.id, old);
+        assert_eq!(
+            message.chat_migration(),
+            Some(ChatMigration::To { chat_id: new })
+        );
+        assert_eq!(message.migrate_to_chat_id(), Some(new));
+
+        // The user who initialized the migration
+        assert!(message.from().is_some());
+
+        // Migration from a common group
+        let json = r#"{"chat":{"id":-1001555296434,"title":"test","type":"supergroup"},"date":1629404938,"from":{"first_name":"Group","id":1087968824,"is_bot":true,"username":"GroupAnonymousBot"},"message_id":1,"migrate_from_chat_id":-599075523,"sender_chat":{"id":-1001555296434,"title":"test","type":"supergroup"}}"#;
+        let message: Message = from_str(json).unwrap();
+
+        assert_eq!(message.chat.id, new);
+        assert_eq!(
+            message.chat_migration(),
+            Some(ChatMigration::From { chat_id: old })
+        );
+        assert_eq!(message.migrate_from_chat_id(), Some(old));
+
+        // Anonymous bot
+        assert!(message.from().is_some());
+
+        // The chat to which the group migrated
+        assert!(message.sender_chat().is_some());
+    }
 }