diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp
index a3469a69044204c16fb0983f470eb2a237e12bfc..2ab5cac23ac7d0ff58c1b8cf17bcb646d4e900cc 100644
--- a/src/ChatPage.cpp
+++ b/src/ChatPage.cpp
@@ -127,6 +127,12 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QObject *parent)
           }
       });
 
+    connect(this,
+            &ChatPage::internalKnock,
+            this,
+            qOverload<const QString &, const std::vector<std::string> &, QString, bool>(
+              &ChatPage::knockRoom),
+            Qt::QueuedConnection);
     connect(this, &ChatPage::leftRoom, this, &ChatPage::removeRoom);
     connect(this, &ChatPage::changeToRoom, this, &ChatPage::changeRoom, Qt::QueuedConnection);
     connect(this, &ChatPage::notificationsRetrieved, this, &ChatPage::sendNotifications);
@@ -659,17 +665,34 @@ ChatPage::trySync()
 }
 
 void
-ChatPage::knockRoom(const QString &room, const QString &reason)
+ChatPage::knockRoom(const QString &room,
+                    const std::vector<std::string> &via,
+                    QString reason,
+                    bool failedJoin)
 {
     const auto room_id = room.toStdString();
-    if (QMessageBox::Yes !=
-        QMessageBox::question(
-          nullptr, tr("Confirm knock"), tr("Do you really want to ask to join %1?").arg(room)))
+    bool confirmed     = false;
+    reason             = QInputDialog::getText(
+      nullptr,
+      tr("Knock on room"),
+      failedJoin
+                    ? tr(
+            "You failed to join %1. You can try to knock, so that others can invite you in. Do you "
+                        "want to do so?\nYou may optionally provide a reason for others to accept your knock:")
+            .arg(room)
+                    : tr("Do you really want to knock on %1? You may optionally provide a reason for others to "
+                         "accept your knock:")
+            .arg(room),
+      QLineEdit::Normal,
+      reason,
+      &confirmed);
+    if (!confirmed) {
         return;
+    }
 
     http::client()->knock_room(
       room_id,
-      {},
+      via,
       [this, room_id](const mtx::responses::RoomId &, mtx::http::RequestErr err) {
           if (err) {
               emit showNotification(tr("Failed to knock room: %1")
@@ -704,10 +727,13 @@ ChatPage::joinRoomVia(const std::string &room_id,
     http::client()->join_room(
       room_id,
       via,
-      [this, room_id](const mtx::responses::RoomId &, mtx::http::RequestErr err) {
+      [this, room_id, reason, via](const mtx::responses::RoomId &, mtx::http::RequestErr err) {
           if (err) {
-              emit showNotification(
-                tr("Failed to join room: %1").arg(QString::fromStdString(err->matrix_error.error)));
+              if (err->matrix_error.errcode == mtx::errors::ErrorCode::M_FORBIDDEN)
+                  emit internalKnock(QString::fromStdString(room_id), via, reason, true);
+              else
+                  emit showNotification(tr("Failed to join room: %1")
+                                          .arg(QString::fromStdString(err->matrix_error.error)));
               return;
           }
 
@@ -1388,6 +1414,9 @@ ChatPage::handleMatrixUri(QString uri)
         if (action == u"join" || action.isEmpty()) {
             joinRoomVia(targetRoomId, vias);
             return true;
+        } else if (action == u"knock" || action.isEmpty()) {
+            knockRoom(mxid1, vias);
+            return true;
         }
         return false;
     } else if (sigil1 == u"r") {
@@ -1409,6 +1438,9 @@ ChatPage::handleMatrixUri(QString uri)
         if (action == u"join" || action.isEmpty()) {
             joinRoomVia(mxid1.toStdString(), vias);
             return true;
+        } else if (action == u"knock" || action.isEmpty()) {
+            knockRoom(mxid1, vias);
+            return true;
         }
         return false;
     }
diff --git a/src/ChatPage.h b/src/ChatPage.h
index f2249637d12e0202238da1c812772c0e8fae6f00..2673a3de55460e4b5ed8e932f0ee3215767c2687 100644
--- a/src/ChatPage.h
+++ b/src/ChatPage.h
@@ -84,7 +84,11 @@ public slots:
     void leaveRoom(const QString &room_id, const QString &reason);
     void createRoom(const mtx::requests::CreateRoom &req);
     void joinRoom(const QString &room, const QString &reason = "");
-    void knockRoom(const QString &room, const QString &reason = "");
+    void knockRoom(const QString &room, QString reason = "") { knockRoom(room, {}, reason, false); }
+    void knockRoom(const QString &room,
+                   const std::vector<std::string> &via,
+                   QString reason  = "",
+                   bool failedJoin = false);
     void joinRoomVia(const std::string &room_id,
                      const std::vector<std::string> &via,
                      bool promptForConfirmation = true,
@@ -161,6 +165,11 @@ signals:
     void downloadedSecrets(mtx::secret_storage::AesHmacSha2KeyDescription keyDesc,
                            const SecretsToDecrypt &secrets);
 
+    void internalKnock(const QString &room,
+                       const std::vector<std::string> &via,
+                       QString reason  = "",
+                       bool failedJoin = false);
+
 private slots:
     void logout();
     void removeRoom(const QString &room_id);