diff --git a/CMakeLists.txt b/CMakeLists.txt
index 90cd3d67ec867da92b006a02810533a4b566d34a..a9bdeec145756c54a6b673ef9cf010b40c3b1478 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -336,6 +336,7 @@ set(SRC_FILES
 	src/encryption/DeviceVerificationFlow.cpp
 	src/encryption/Olm.cpp
 	src/encryption/SelfVerificationStatus.cpp
+	src/encryption/VerificationManager.cpp
 
 	# Generic notification stuff
 	src/notifications/Manager.cpp
@@ -548,9 +549,10 @@ qt5_wrap_cpp(MOC_HEADERS
 	src/voip/CallManager.h
 	src/voip/WebRTCSession.h
 
-	src/encryption/SelfVerificationStatus.h
 	src/encryption/DeviceVerificationFlow.h
 	src/encryption/Olm.h
+	src/encryption/SelfVerificationStatus.h
+	src/encryption/VerificationManager.h
 
 	src/notifications/Manager.h
 
diff --git a/resources/qml/Root.qml b/resources/qml/Root.qml
index 1813046925ecd873a078b8eb7404282f592d7076..361099edc4d1413cdf897bf04f73236e9d891c52 100644
--- a/resources/qml/Root.qml
+++ b/resources/qml/Root.qml
@@ -178,6 +178,10 @@ Page {
             dialog.show();
         }
 
+        target: VerificationManager
+    }
+
+    Connections {
         function onOpenProfile(profile) {
             var userProfile = userProfileComponent.createObject(timelineRoot, {
                 "profile": profile
diff --git a/resources/qml/device-verification/DeviceVerification.qml b/resources/qml/device-verification/DeviceVerification.qml
index 01e3bad403e1ae57ff6310122a0371e2094af245..5bc8b9c85918ddaf06185daa3e683727a37f27b9 100644
--- a/resources/qml/device-verification/DeviceVerification.qml
+++ b/resources/qml/device-verification/DeviceVerification.qml
@@ -12,7 +12,7 @@ ApplicationWindow {
 
     property var flow
 
-    onClosing: TimelineManager.removeVerificationFlow(flow)
+    onClosing: VerificationManager.removeVerificationFlow(flow)
     title: stack.currentItem.title
     modality: Qt.NonModal
     palette: Nheko.colors
diff --git a/resources/res.qrc b/resources/res.qrc
index ffbadd91aa2c00db58518196b342f78e2fd22afc..e544316bd05e973900538fde2c5e571205f7ec14 100644
--- a/resources/res.qrc
+++ b/resources/res.qrc
@@ -141,37 +141,42 @@
         <file>qml/SelfVerificationCheck.qml</file>
         <file>qml/TypingIndicator.qml</file>
         <file>qml/NotificationWarning.qml</file>
-        <file>qml/emoji/EmojiPicker.qml</file>
-        <file>qml/emoji/StickerPicker.qml</file>
-        <file>qml/delegates/MessageDelegate.qml</file>
+        <file>qml/components/AdaptiveLayout.qml</file>
+        <file>qml/components/AdaptiveLayoutElement.qml</file>
+        <file>qml/components/AvatarListTile.qml</file>
+        <file>qml/components/FlatButton.qml</file>
         <file>qml/delegates/Encrypted.qml</file>
         <file>qml/delegates/FileMessage.qml</file>
         <file>qml/delegates/ImageMessage.qml</file>
+        <file>qml/delegates/MessageDelegate.qml</file>
         <file>qml/delegates/NoticeMessage.qml</file>
         <file>qml/delegates/Pill.qml</file>
         <file>qml/delegates/Placeholder.qml</file>
         <file>qml/delegates/PlayableMediaMessage.qml</file>
         <file>qml/delegates/Reply.qml</file>
         <file>qml/delegates/TextMessage.qml</file>
-        <file>qml/device-verification/Waiting.qml</file>
         <file>qml/device-verification/DeviceVerification.qml</file>
         <file>qml/device-verification/DigitVerification.qml</file>
         <file>qml/device-verification/EmojiVerification.qml</file>
-        <file>qml/device-verification/NewVerificationRequest.qml</file>
         <file>qml/device-verification/Failed.qml</file>
-		<file>qml/device-verification/Success.qml</file>
-        <file>qml/dialogs/ImagePackSettingsDialog.qml</file>
+        <file>qml/device-verification/NewVerificationRequest.qml</file>
+        <file>qml/device-verification/Success.qml</file>
+        <file>qml/device-verification/Waiting.qml</file>
         <file>qml/dialogs/ImagePackEditorDialog.qml</file>
-		<file>qml/dialogs/InputDialog.qml</file>
-		<file>qml/dialogs/InviteDialog.qml</file>
-		<file>qml/dialogs/JoinRoomDialog.qml</file>
-		<file>qml/dialogs/LogoutDialog.qml</file>
-		<file>qml/dialogs/RawMessageDialog.qml</file>
-		<file>qml/dialogs/ReadReceipts.qml</file>
-		<file>qml/dialogs/RoomDirectory.qml</file>
-		<file>qml/dialogs/RoomMembers.qml</file>
-		<file>qml/dialogs/RoomSettings.qml</file>
-		<file>qml/dialogs/UserProfile.qml</file>
+        <file>qml/dialogs/ImagePackSettingsDialog.qml</file>
+        <file>qml/dialogs/InputDialog.qml</file>
+        <file>qml/dialogs/InviteDialog.qml</file>
+        <file>qml/dialogs/JoinRoomDialog.qml</file>
+        <file>qml/dialogs/LeaveRoomDialog.qml</file>
+        <file>qml/dialogs/LogoutDialog.qml</file>
+        <file>qml/dialogs/RawMessageDialog.qml</file>
+        <file>qml/dialogs/ReadReceipts.qml</file>
+        <file>qml/dialogs/RoomDirectory.qml</file>
+        <file>qml/dialogs/RoomMembers.qml</file>
+        <file>qml/dialogs/RoomSettings.qml</file>
+        <file>qml/dialogs/UserProfile.qml</file>
+        <file>qml/emoji/EmojiPicker.qml</file>
+        <file>qml/emoji/StickerPicker.qml</file>
         <file>qml/ui/Ripple.qml</file>
         <file>qml/ui/Spinner.qml</file>
         <file>qml/ui/animations/BlinkAnimation.qml</file>
@@ -183,18 +188,6 @@
         <file>qml/voip/PlaceCall.qml</file>
         <file>qml/voip/ScreenShare.qml</file>
         <file>qml/voip/VideoCall.qml</file>
-        <file>qml/components/AdaptiveLayout.qml</file>
-        <file>qml/components/AdaptiveLayoutElement.qml</file>
-		<file>qml/components/AvatarListTile.qml</file>
-        <file>qml/components/FlatButton.qml</file>
-        <file>qml/dialogs/InviteDialog.qml</file>
-		<file>qml/dialogs/LeaveRoomDialog.qml</file>
-        <file>qml/dialogs/RawMessageDialog.qml</file>
-        <file>qml/dialogs/ReadReceipts.qml</file>
-        <file>qml/dialogs/RoomDirectory.qml</file>
-        <file>qml/dialogs/RoomMembers.qml</file>
-        <file>qml/dialogs/RoomSettings.qml</file>
-		<file>qml/dialogs/UserProfile.qml</file>
     </qresource>
     <qresource prefix="/media">
         <file>media/ring.ogg</file>
diff --git a/src/encryption/VerificationManager.cpp b/src/encryption/VerificationManager.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..b9b51d35659788264a7a727afd90070794254ff8
--- /dev/null
+++ b/src/encryption/VerificationManager.cpp
@@ -0,0 +1,126 @@
+// SPDX-FileCopyrightText: 2021 Nheko Contributors
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "VerificationManager.h"
+#include "Cache.h"
+#include "ChatPage.h"
+#include "DeviceVerificationFlow.h"
+#include "timeline/TimelineViewManager.h"
+
+VerificationManager::VerificationManager(TimelineViewManager *o)
+  : QObject(o)
+  , rooms_(o->rooms())
+{}
+
+void
+VerificationManager::receivedRoomDeviceVerificationRequest(
+  const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationRequest> &message,
+  TimelineModel *model)
+{
+    if (this->isInitialSync_)
+        return;
+
+    auto event_id = QString::fromStdString(message.event_id);
+    if (!this->dvList.contains(event_id)) {
+        if (auto flow = DeviceVerificationFlow::NewInRoomVerification(
+              this, model, message.content, QString::fromStdString(message.sender), event_id)) {
+            dvList[event_id] = flow;
+            emit newDeviceVerificationRequest(flow.data());
+        }
+    }
+}
+
+void
+VerificationManager::receivedDeviceVerificationRequest(
+  const mtx::events::msg::KeyVerificationRequest &msg,
+  std::string sender)
+{
+    if (this->isInitialSync_)
+        return;
+
+    if (!msg.transaction_id)
+        return;
+
+    auto txnid = QString::fromStdString(msg.transaction_id.value());
+    if (!this->dvList.contains(txnid)) {
+        if (auto flow = DeviceVerificationFlow::NewToDeviceVerification(
+              this, msg, QString::fromStdString(sender), txnid)) {
+            dvList[txnid] = flow;
+            emit newDeviceVerificationRequest(flow.data());
+        }
+    }
+}
+
+void
+VerificationManager::receivedDeviceVerificationStart(
+  const mtx::events::msg::KeyVerificationStart &msg,
+  std::string sender)
+{
+    if (this->isInitialSync_)
+        return;
+
+    if (!msg.transaction_id)
+        return;
+
+    auto txnid = QString::fromStdString(msg.transaction_id.value());
+    if (!this->dvList.contains(txnid)) {
+        if (auto flow = DeviceVerificationFlow::NewToDeviceVerification(
+              this, msg, QString::fromStdString(sender), txnid)) {
+            dvList[txnid] = flow;
+            emit newDeviceVerificationRequest(flow.data());
+        }
+    }
+}
+
+void
+VerificationManager::verifyUser(QString userid)
+{
+    auto joined_rooms = cache::joinedRooms();
+    auto room_infos   = cache::getRoomInfo(joined_rooms);
+
+    for (std::string room_id : joined_rooms) {
+        if ((room_infos[QString::fromStdString(room_id)].member_count == 2) &&
+            cache::isRoomEncrypted(room_id)) {
+            auto room_members = cache::roomMembers(room_id);
+            if (std::find(room_members.begin(), room_members.end(), (userid).toStdString()) !=
+                room_members.end()) {
+                if (auto model = rooms_->getRoomById(QString::fromStdString(room_id))) {
+                    auto flow =
+                      DeviceVerificationFlow::InitiateUserVerification(this, model.data(), userid);
+                    connect(model.data(),
+                            &TimelineModel::updateFlowEventId,
+                            this,
+                            [this, flow](std::string eventId) {
+                                dvList[QString::fromStdString(eventId)] = flow;
+                            });
+                    emit newDeviceVerificationRequest(flow.data());
+                    return;
+                }
+            }
+        }
+    }
+
+    emit ChatPage::instance()->showNotification(
+      tr("No encrypted private chat found with this user. Create an "
+         "encrypted private chat with this user and try again."));
+}
+
+void
+VerificationManager::removeVerificationFlow(DeviceVerificationFlow *flow)
+{
+    for (auto it = dvList.keyValueBegin(); it != dvList.keyValueEnd(); ++it) {
+        if ((*it).second == flow) {
+            dvList.remove((*it).first);
+            return;
+        }
+    }
+}
+
+void
+VerificationManager::verifyDevice(QString userid, QString deviceid)
+{
+    auto flow = DeviceVerificationFlow::InitiateDeviceVerification(this, userid, deviceid);
+    this->dvList[flow->transactionId()] = flow;
+    emit newDeviceVerificationRequest(flow.data());
+}
diff --git a/src/encryption/VerificationManager.h b/src/encryption/VerificationManager.h
new file mode 100644
index 0000000000000000000000000000000000000000..e00ddc10ff141fbe8f6d7f1fe8eb39fb5eaf424b
--- /dev/null
+++ b/src/encryption/VerificationManager.h
@@ -0,0 +1,48 @@
+// SPDX-FileCopyrightText: 2021 Nheko Contributors
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include <QHash>
+#include <QObject>
+#include <QSharedPointer>
+
+#include <mtx/events.hpp>
+#include <mtx/events/encrypted.hpp>
+
+class DeviceVerificationFlow;
+class TimelineModel;
+class TimelineModel;
+class TimelineViewManager;
+class RoomlistModel;
+
+class VerificationManager : public QObject
+{
+    Q_OBJECT
+
+public:
+    VerificationManager(TimelineViewManager *o = nullptr);
+
+    Q_INVOKABLE void removeVerificationFlow(DeviceVerificationFlow *flow);
+    void verifyUser(QString userid);
+    void verifyDevice(QString userid, QString deviceid);
+
+signals:
+    void newDeviceVerificationRequest(DeviceVerificationFlow *flow);
+
+public slots:
+    void receivedRoomDeviceVerificationRequest(
+      const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationRequest> &message,
+      TimelineModel *model);
+    void receivedDeviceVerificationRequest(const mtx::events::msg::KeyVerificationRequest &msg,
+                                           std::string sender);
+    void receivedDeviceVerificationStart(const mtx::events::msg::KeyVerificationStart &msg,
+                                         std::string sender);
+
+private:
+    QHash<QString, QSharedPointer<DeviceVerificationFlow>> dvList;
+    bool isInitialSync_ = false;
+    RoomlistModel *rooms_;
+};
+
diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp
index 86f59c52b67203f73e6fe4bb3448dfaa7b5a8ea3..94e6a0d71ebee0b3d4ce49a4ef823a1ab63ce24f 100644
--- a/src/timeline/TimelineViewManager.cpp
+++ b/src/timeline/TimelineViewManager.cpp
@@ -143,9 +143,10 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par
   , colorImgProvider(new ColorImageProvider())
   , blurhashProvider(new BlurhashProvider())
   , jdenticonProvider(new JdenticonProvider())
-  , callManager_(callManager)
   , rooms_(new RoomlistModel(this))
   , communities_(new CommunitiesModel(this))
+  , callManager_(callManager)
+  , verificationManager_(new VerificationManager(this))
 {
     qRegisterMetaType<mtx::events::msg::KeyVerificationAccept>();
     qRegisterMetaType<mtx::events::msg::KeyVerificationCancel>();
@@ -244,6 +245,7 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par
       "im.nheko", 1, 0, "Nheko", [](QQmlEngine *, QJSEngine *) -> QObject * {
           return new Nheko();
       });
+    qmlRegisterSingletonInstance("im.nheko", 1, 0, "VerificationManager", verificationManager_);
     qmlRegisterSingletonType<SelfVerificationStatus>(
       "im.nheko", 1, 0, "SelfVerificationStatus", [](QQmlEngine *, QJSEngine *) -> QObject * {
           return new SelfVerificationStatus();
@@ -285,63 +287,16 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par
     connect(parent, &ChatPage::themeChanged, this, &TimelineViewManager::updateColorPalette);
     connect(dynamic_cast<ChatPage *>(parent),
             &ChatPage::receivedRoomDeviceVerificationRequest,
-            this,
-            [this](const mtx::events::RoomEvent<mtx::events::msg::KeyVerificationRequest> &message,
-                   TimelineModel *model) {
-                if (this->isInitialSync_)
-                    return;
-
-                auto event_id = QString::fromStdString(message.event_id);
-                if (!this->dvList.contains(event_id)) {
-                    if (auto flow = DeviceVerificationFlow::NewInRoomVerification(
-                          this,
-                          model,
-                          message.content,
-                          QString::fromStdString(message.sender),
-                          event_id)) {
-                        dvList[event_id] = flow;
-                        emit newDeviceVerificationRequest(flow.data());
-                    }
-                }
-            });
+            verificationManager_,
+            &VerificationManager::receivedRoomDeviceVerificationRequest);
     connect(dynamic_cast<ChatPage *>(parent),
             &ChatPage::receivedDeviceVerificationRequest,
-            this,
-            [this](const mtx::events::msg::KeyVerificationRequest &msg, std::string sender) {
-                if (this->isInitialSync_)
-                    return;
-
-                if (!msg.transaction_id)
-                    return;
-
-                auto txnid = QString::fromStdString(msg.transaction_id.value());
-                if (!this->dvList.contains(txnid)) {
-                    if (auto flow = DeviceVerificationFlow::NewToDeviceVerification(
-                          this, msg, QString::fromStdString(sender), txnid)) {
-                        dvList[txnid] = flow;
-                        emit newDeviceVerificationRequest(flow.data());
-                    }
-                }
-            });
+            verificationManager_,
+            &VerificationManager::receivedDeviceVerificationRequest);
     connect(dynamic_cast<ChatPage *>(parent),
             &ChatPage::receivedDeviceVerificationStart,
-            this,
-            [this](const mtx::events::msg::KeyVerificationStart &msg, std::string sender) {
-                if (this->isInitialSync_)
-                    return;
-
-                if (!msg.transaction_id)
-                    return;
-
-                auto txnid = QString::fromStdString(msg.transaction_id.value());
-                if (!this->dvList.contains(txnid)) {
-                    if (auto flow = DeviceVerificationFlow::NewToDeviceVerification(
-                          this, msg, QString::fromStdString(sender), txnid)) {
-                        dvList[txnid] = flow;
-                        emit newDeviceVerificationRequest(flow.data());
-                    }
-                }
-            });
+            verificationManager_,
+            &VerificationManager::receivedDeviceVerificationStart);
     connect(parent, &ChatPage::loggedOut, this, [this]() {
         isInitialSync_ = true;
         emit initialSyncChanged(true);
@@ -475,58 +430,6 @@ TimelineViewManager::openImageOverlayInternal(QString eventId, QImage img)
     });
 }
 
-void
-TimelineViewManager::verifyUser(QString userid)
-{
-    auto joined_rooms = cache::joinedRooms();
-    auto room_infos   = cache::getRoomInfo(joined_rooms);
-
-    for (std::string room_id : joined_rooms) {
-        if ((room_infos[QString::fromStdString(room_id)].member_count == 2) &&
-            cache::isRoomEncrypted(room_id)) {
-            auto room_members = cache::roomMembers(room_id);
-            if (std::find(room_members.begin(), room_members.end(), (userid).toStdString()) !=
-                room_members.end()) {
-                if (auto model = rooms_->getRoomById(QString::fromStdString(room_id))) {
-                    auto flow =
-                      DeviceVerificationFlow::InitiateUserVerification(this, model.data(), userid);
-                    connect(model.data(),
-                            &TimelineModel::updateFlowEventId,
-                            this,
-                            [this, flow](std::string eventId) {
-                                dvList[QString::fromStdString(eventId)] = flow;
-                            });
-                    emit newDeviceVerificationRequest(flow.data());
-                    return;
-                }
-            }
-        }
-    }
-
-    emit ChatPage::instance()->showNotification(
-      tr("No encrypted private chat found with this user. Create an "
-         "encrypted private chat with this user and try again."));
-}
-
-void
-TimelineViewManager::removeVerificationFlow(DeviceVerificationFlow *flow)
-{
-    for (auto it = dvList.keyValueBegin(); it != dvList.keyValueEnd(); ++it) {
-        if ((*it).second == flow) {
-            dvList.remove((*it).first);
-            return;
-        }
-    }
-}
-
-void
-TimelineViewManager::verifyDevice(QString userid, QString deviceid)
-{
-    auto flow = DeviceVerificationFlow::InitiateDeviceVerification(this, userid, deviceid);
-    this->dvList[flow->transactionId()] = flow;
-    emit newDeviceVerificationRequest(flow.data());
-}
-
 void
 TimelineViewManager::updateReadReceipts(const QString &room_id,
                                         const std::vector<QString> &event_ids)
diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h
index 723282d615d7df5114dc3129ef4c788e2fee1529..6696b1c4dfb756a87500f6f5864771a51ec34808 100644
--- a/src/timeline/TimelineViewManager.h
+++ b/src/timeline/TimelineViewManager.h
@@ -9,7 +9,6 @@
 #include <QQuickTextDocument>
 #include <QQuickView>
 #include <QQuickWidget>
-#include <QSharedPointer>
 #include <QWidget>
 
 #include <mtx/common.hpp>
@@ -23,6 +22,7 @@
 #include "Utils.h"
 #include "emoji/EmojiModel.h"
 #include "emoji/Provider.h"
+#include "encryption/VerificationManager.h"
 #include "timeline/CommunitiesModel.h"
 #include "timeline/RoomlistModel.h"
 #include "voip/CallManager.h"
@@ -33,7 +33,6 @@ class BlurhashProvider;
 class ColorImageProvider;
 class UserSettings;
 class ChatPage;
-class DeviceVerificationFlow;
 class ImagePackListModel;
 
 class TimelineViewManager : public QObject
@@ -53,6 +52,7 @@ public:
 
     MxcImageProvider *imageProvider() { return imgProvider; }
     CallManager *callManager() { return callManager_; }
+    VerificationManager *verificationManager() { return verificationManager_; }
 
     void clearAll() { rooms_->clear(); }
 
@@ -73,19 +73,14 @@ public:
     Q_INVOKABLE void openGlobalUserProfile(QString userId);
 
     Q_INVOKABLE void focusMessageInput();
-    Q_INVOKABLE void removeVerificationFlow(DeviceVerificationFlow *flow);
 
     Q_INVOKABLE void fixImageRendering(QQuickTextDocument *t, QQuickItem *i);
 
-    void verifyUser(QString userid);
-    void verifyDevice(QString userid, QString deviceid);
-
 signals:
     void activeTimelineChanged(TimelineModel *timeline);
     void initialSyncChanged(bool isInitialSync);
     void replyingEventChanged(QString replyingEvent);
     void replyClosed();
-    void newDeviceVerificationRequest(DeviceVerificationFlow *flow);
     void inviteUsers(QString roomId, QStringList users);
     void showRoomList();
     void narrowViewChanged();
@@ -142,17 +137,17 @@ private:
     BlurhashProvider *blurhashProvider;
     JdenticonProvider *jdenticonProvider;
 
-    CallManager *callManager_ = nullptr;
-
     bool isInitialSync_   = true;
     bool isWindowFocused_ = false;
 
     RoomlistModel *rooms_          = nullptr;
     CommunitiesModel *communities_ = nullptr;
 
-    QHash<QString, QColor> userColors;
+    // don't move this above the rooms_
+    CallManager *callManager_                 = nullptr;
+    VerificationManager *verificationManager_ = nullptr;
 
-    QHash<QString, QSharedPointer<DeviceVerificationFlow>> dvList;
+    QHash<QString, QColor> userColors;
 };
 Q_DECLARE_METATYPE(mtx::events::msg::KeyVerificationAccept)
 Q_DECLARE_METATYPE(mtx::events::msg::KeyVerificationCancel)
diff --git a/src/ui/UserProfile.cpp b/src/ui/UserProfile.cpp
index d62e3248b13705a45ed0fa22189461d4776e2d65..0e3fd39f2ca756c945d729075d1037377f4d464a 100644
--- a/src/ui/UserProfile.cpp
+++ b/src/ui/UserProfile.cpp
@@ -338,9 +338,9 @@ void
 UserProfile::verify(QString device)
 {
     if (!device.isEmpty())
-        manager->verifyDevice(userid_, device);
+        manager->verificationManager()->verifyDevice(userid_, device);
     else {
-        manager->verifyUser(userid_);
+        manager->verificationManager()->verifyUser(userid_);
     }
 }