diff --git a/.ci/adjust-config.sh b/.ci/adjust-config.sh
index 8f2111376284af7e74d12e6e4121e17050db8c8a..6f223e1b807c6dd31cdc7b8e68614affeeeb5b83 100755
--- a/.ci/adjust-config.sh
+++ b/.ci/adjust-config.sh
@@ -13,28 +13,38 @@ $CMD perl -pi -w -e \
 
 (
 cat <<HEREDOC
+
+
 rc_message:
-  per_second: 1000
-  burst_count: 10000
+  per_second: 10000
+  burst_count: 100000
 
 rc_registration:
-  per_second: 1000
-  burst_count: 3000
+  per_second: 10000
+  burst_count: 30000
 
 rc_login:
   address:
-    per_second: 1000
-    burst_count: 3000
+    per_second: 10000
+    burst_count: 30000
   account:
-    per_second: 1000
-    burst_count: 3000
+    per_second: 10000
+    burst_count: 30000
   failed_attempts:
-    per_second: 1000
-    burst_count: 3000
+    per_second: 10000
+    burst_count: 30000
 
 rc_admin_redaction:
   per_second: 1000
   burst_count: 5000
+
+rc_joins:
+  local:
+    per_second: 10000
+    burst_count: 100000
+  remote:
+    per_second: 10000
+    burst_count: 100000
 HEREDOC
 ) | $CMD tee -a data/homeserver.yaml
 
diff --git a/Makefile b/Makefile
index 97eba7fb2699f9981ef817d940f9c333b16f2334..92805e07b551d66fe202557e70cdf656227158f3 100644
--- a/Makefile
+++ b/Makefile
@@ -1,6 +1,6 @@
 FILES=`find lib include tests examples -type f -type f \( -iname "*.cpp" -o -iname "*.hpp" \)`
 
-SYNAPSE_IMAGE="matrixdotorg/synapse:v1.7.2"
+SYNAPSE_IMAGE="matrixdotorg/synapse:v1.24.0"
 
 DEPS_BUILD_DIR=.deps
 DEPS_SOURCE_DIR=deps
diff --git a/include/mtx/events/collections.hpp b/include/mtx/events/collections.hpp
index 449ea3193f7efe24261f3cb3c51ed42cb2d95c5e..5378f1af3b362372545bc48e416c1a047a520043 100644
--- a/include/mtx/events/collections.hpp
+++ b/include/mtx/events/collections.hpp
@@ -57,7 +57,9 @@ using DeviceEvents = std::variant<events::DeviceEvent<msgs::RoomKey>,
                                   events::DeviceEvent<msgs::KeyVerificationAccept>,
                                   events::DeviceEvent<msgs::KeyVerificationCancel>,
                                   events::DeviceEvent<msgs::KeyVerificationKey>,
-                                  events::DeviceEvent<msgs::KeyVerificationMac>>;
+                                  events::DeviceEvent<msgs::KeyVerificationMac>,
+                                  events::DeviceEvent<msgs::SecretRequest>,
+                                  events::DeviceEvent<msgs::SecretSend>>;
 
 //! Collection of room specific account data
 using RoomAccountDataEvents =
@@ -279,5 +281,11 @@ constexpr inline EventType to_device_content_to_type<mtx::events::msg::KeyVerifi
 template<>
 constexpr inline EventType to_device_content_to_type<mtx::events::msg::KeyVerificationMac> =
   EventType::KeyVerificationMac;
+template<>
+constexpr inline EventType to_device_content_to_type<mtx::events::msg::SecretSend> =
+  EventType::SecretSend;
+template<>
+constexpr inline EventType to_device_content_to_type<mtx::events::msg::SecretRequest> =
+  EventType::SecretRequest;
 } // namespace events
 } // namespace mtx
diff --git a/include/mtx/events/encrypted.hpp b/include/mtx/events/encrypted.hpp
index 625bc747d6fb7760f1dcf72ceb227c71fa0d10a9..7c2babe980ad4a4e7d251a6aeceb9fc0c248dc39 100644
--- a/include/mtx/events/encrypted.hpp
+++ b/include/mtx/events/encrypted.hpp
@@ -395,6 +395,41 @@ from_json(const nlohmann::json &obj, KeyVerificationMac &event);
 void
 to_json(nlohmann::json &obj, const KeyVerificationMac &event);
 
+struct SecretRequest
+{
+        //! The type of request.
+        RequestAction action;
+
+        //! Required if action is request. The name of the secret that is being requested.
+        std::string name;
+
+        //! A unique identifier for this request.
+        std::string request_id;
+        //! The device requesting the keys.
+        std::string requesting_device_id;
+};
+
+void
+from_json(const nlohmann::json &obj, SecretRequest &event);
+
+void
+to_json(nlohmann::json &obj, const SecretRequest &event);
+
+struct SecretSend
+{
+        //! Required. The contents of the secret.
+        std::string secret;
+
+        //! A unique identifier for this request.
+        std::string request_id;
+};
+
+void
+from_json(const nlohmann::json &obj, SecretSend &event);
+
+void
+to_json(nlohmann::json &obj, const SecretSend &event);
+
 } // namespace msg
 } // namespace events
 } // namespace mtx
diff --git a/include/mtx/events/event_type.hpp b/include/mtx/events/event_type.hpp
index 8ef332721bd57a8cba3313b8a95be7692d82a159..2a8fd4559cb2336e7a2728ec14346f051f0cda03 100644
--- a/include/mtx/events/event_type.hpp
+++ b/include/mtx/events/event_type.hpp
@@ -87,6 +87,11 @@ enum class EventType
         // m.call.hangup
         CallHangUp,
 
+        // m.secret.request
+        SecretRequest,
+        // m.secret.send
+        SecretSend,
+
         // custom events
         // im.nheko.hidden_events
         NhekoHiddenEvents,
diff --git a/include/mtxclient/crypto/client.hpp b/include/mtxclient/crypto/client.hpp
index 1476743d3814b23d2e5b807b573ef83556ec3a9d..4b2934f773363d51b08d39dad6116f226c91e8c0 100644
--- a/include/mtxclient/crypto/client.hpp
+++ b/include/mtxclient/crypto/client.hpp
@@ -45,6 +45,10 @@ public:
           : msg_(func + ": " + std::string(olm_pk_decryption_last_error(s)))
         {}
 
+        olm_exception(std::string func, OlmPkSigning *s)
+          : msg_(func + ": " + std::string(olm_pk_signing_last_error(s)))
+        {}
+
         olm_exception(std::string func, OlmOutboundGroupSession *s)
           : msg_(func + ": " + std::string(olm_outbound_group_session_last_error(s)))
         {}
@@ -114,6 +118,21 @@ private:
         SASPtr sas;
 };
 
+struct PkSigning
+{
+        //! Construct from base64 key
+        static PkSigning from_seed(std::string seed);
+        std::string sign(const std::string &message);
+
+        //! base64 public key
+        std::string public_key() const { return public_key_; }
+
+private:
+        PkSigning() {}
+        std::unique_ptr<OlmPkSigning, OlmDeleter> signing;
+        std::string public_key_;
+};
+
 class OlmClient : public std::enable_shared_from_this<OlmClient>
 {
 public:
diff --git a/include/mtxclient/crypto/objects.hpp b/include/mtxclient/crypto/objects.hpp
index a0fa155a27b8e5ff206dc38d3da3d4ef28d35b42..9303940dfc333ef626242bf122b9e0d6ce4bd4f9 100644
--- a/include/mtxclient/crypto/objects.hpp
+++ b/include/mtxclient/crypto/objects.hpp
@@ -14,6 +14,7 @@ struct OlmDeleter
         void operator()(OlmUtility *ptr) { delete[](reinterpret_cast<uint8_t *>(ptr)); }
 
         void operator()(OlmPkDecryption *ptr) { delete[](reinterpret_cast<uint8_t *>(ptr)); }
+        void operator()(OlmPkSigning *ptr) { delete[](reinterpret_cast<uint8_t *>(ptr)); }
 
         void operator()(OlmSession *ptr) { delete[](reinterpret_cast<uint8_t *>(ptr)); }
         void operator()(OlmOutboundGroupSession *ptr)
@@ -48,6 +49,13 @@ struct PkDecryptionObject
         }
 };
 
+struct PkSigningObject
+{
+        using olm_type = OlmPkSigning;
+
+        static olm_type *allocate() { return olm_pk_signing(new uint8_t[olm_pk_signing_size()]); }
+};
+
 struct AccountObject
 {
         using olm_type = OlmAccount;
diff --git a/lib/crypto/client.cpp b/lib/crypto/client.cpp
index baed4e35b201abeace1b505923eff3013ee411a1..53b1ae4a426d81b8c46645cd2b0ef84efb1b7a41 100644
--- a/lib/crypto/client.cpp
+++ b/lib/crypto/client.cpp
@@ -529,6 +529,44 @@ SAS::calculate_mac(std::string input_data, std::string info)
         return to_string(output_buffer);
 }
 
+PkSigning
+PkSigning::from_seed(std::string seed)
+{
+        PkSigning s{};
+        s.signing = create_olm_object<PkSigningObject>();
+
+        auto seed_ = base642bin(seed);
+
+        auto pub_key_buffer = BinaryBuf(olm_pk_signing_public_key_length());
+        auto ret            = olm_pk_signing_key_from_seed(s.signing.get(),
+                                                pub_key_buffer.data(),
+                                                pub_key_buffer.size(),
+                                                seed_.data(),
+                                                seed_.size());
+
+        if (ret == olm_error())
+                throw olm_exception("signing_from_seed", s.signing.get());
+
+        s.public_key_ = to_string(pub_key_buffer);
+
+        return s;
+}
+
+std::string
+PkSigning::sign(const std::string &message)
+{
+        auto signature = BinaryBuf(olm_pk_signature_length());
+        auto message_  = to_binary_buf(message);
+
+        auto ret = olm_pk_sign(
+          signing.get(), message_.data(), message_.size(), signature.data(), signature.size());
+
+        if (ret == olm_error())
+                throw olm_exception("olm_pk_sign", signing.get());
+
+        return to_string(signature);
+}
+
 nlohmann::json
 OlmClient::create_olm_encrypted_content(OlmSession *session,
                                         nlohmann::json event,
diff --git a/lib/http/client.cpp b/lib/http/client.cpp
index d3f29975aba97e6f03a392c44dd3d955bb25562b..32bd363f0620d1a3e2c1dbfdfca6494584d0420c 100644
--- a/lib/http/client.cpp
+++ b/lib/http/client.cpp
@@ -1248,3 +1248,5 @@ MTXCLIENT_SEND_TO_DEVICE(mtx::events::msg::KeyVerificationAccept)
 MTXCLIENT_SEND_TO_DEVICE(mtx::events::msg::KeyVerificationCancel)
 MTXCLIENT_SEND_TO_DEVICE(mtx::events::msg::KeyVerificationKey)
 MTXCLIENT_SEND_TO_DEVICE(mtx::events::msg::KeyVerificationMac)
+MTXCLIENT_SEND_TO_DEVICE(mtx::events::msg::SecretSend)
+MTXCLIENT_SEND_TO_DEVICE(mtx::events::msg::SecretRequest)
diff --git a/lib/structs/events.cpp b/lib/structs/events.cpp
index 9be4a2241fe29eb958e2ebdb2969124c7e9358f8..0a65c88a303300a6d17fd2f50a4b1be4720bbc74 100644
--- a/lib/structs/events.cpp
+++ b/lib/structs/events.cpp
@@ -84,6 +84,10 @@ getEventType(const std::string &type)
                 return EventType::CallAnswer;
         else if (type == "m.call.hangup")
                 return EventType::CallHangUp;
+        else if (type == "m.secret.request")
+                return EventType::SecretRequest;
+        else if (type == "m.secret.send")
+                return EventType::SecretSend;
         else if (type == "im.nheko.hidden_events")
                 return EventType::NhekoHiddenEvents;
         else
@@ -168,6 +172,10 @@ to_string(EventType type)
                 return "m.call.answer";
         case EventType::CallHangUp:
                 return "m.call.hangup";
+        case EventType::SecretRequest:
+                return "m.secret.request";
+        case EventType::SecretSend:
+                return "m.secret.send";
         case EventType::NhekoHiddenEvents:
                 return "im.nheko.hidden_events";
         case EventType::Unsupported:
diff --git a/lib/structs/events/collections.cpp b/lib/structs/events/collections.cpp
index 350cfe73bed1a6c3b948af68aaf679797fb1ab72..661dd38cfe29a739fca8ab08198fee5ae1169abf 100644
--- a/lib/structs/events/collections.cpp
+++ b/lib/structs/events/collections.cpp
@@ -79,6 +79,8 @@ MTXCLIENT_INSTANTIATE_JSON_FUNCTIONS(events::DeviceEvent, msgs::KeyVerificationM
 MTXCLIENT_INSTANTIATE_JSON_FUNCTIONS(events::DeviceEvent, msgs::RoomKey)
 MTXCLIENT_INSTANTIATE_JSON_FUNCTIONS(events::DeviceEvent, msgs::ForwardedRoomKey)
 MTXCLIENT_INSTANTIATE_JSON_FUNCTIONS(events::DeviceEvent, msgs::KeyRequest)
+MTXCLIENT_INSTANTIATE_JSON_FUNCTIONS(events::DeviceEvent, msgs::SecretRequest)
+MTXCLIENT_INSTANTIATE_JSON_FUNCTIONS(events::DeviceEvent, msgs::SecretSend)
 
 MTXCLIENT_INSTANTIATE_JSON_FUNCTIONS(events::Event, mtx::events::account_data::Tags)
 MTXCLIENT_INSTANTIATE_JSON_FUNCTIONS(events::Event, pushrules::GlobalRuleset)
@@ -293,6 +295,8 @@ from_json(const json &obj, TimelineEvent &e)
         case events::EventType::Tag:              // Not part of the timeline
         case events::EventType::Presence:         // Not part of the timeline
         case events::EventType::PushRules:        // Not part of the timeline
+        case events::EventType::SecretRequest:    // Not part of the timeline
+        case events::EventType::SecretSend:       // Not part of the timeline
         case events::EventType::NhekoHiddenEvents:
         case events::EventType::Unsupported:
                 return;
diff --git a/lib/structs/events/encrypted.cpp b/lib/structs/events/encrypted.cpp
index ade1abd6cfc7b804d63a781b9ac5fd143777b204..432f365dfb927c0d0ed3f08c0d3bf3f77810caa0 100644
--- a/lib/structs/events/encrypted.cpp
+++ b/lib/structs/events/encrypted.cpp
@@ -435,6 +435,57 @@ to_json(json &obj, const KeyVerificationMac &event)
                 obj["m.relates_to"] = event.relates_to.value();
 }
 
+void
+from_json(const nlohmann::json &obj, SecretRequest &event)
+{
+        event.action = RequestAction::Unknown;
+        auto action  = obj.value("action", "");
+        if (action == "request") {
+                event.action = RequestAction::Request;
+        } else if (action == "request_cancellation") {
+                event.action = RequestAction::Cancellation;
+        }
+
+        event.name = obj.value("name", "");
+
+        event.request_id           = obj.value("request_id", "");
+        event.requesting_device_id = obj.value("requesting_device_id", "");
+}
+
+void
+to_json(nlohmann::json &obj, const SecretRequest &event)
+{
+        switch (event.action) {
+        case RequestAction::Request:
+                obj["action"] = "request";
+                break;
+        case RequestAction::Cancellation:
+                obj["action"] = "request_cancellation";
+                break;
+        default:
+                throw std::invalid_argument("Unknown secret request action type");
+        }
+
+        if (!event.name.empty())
+                obj["name"] = event.name;
+
+        obj["request_id"]           = event.request_id;
+        obj["requesting_device_id"] = event.requesting_device_id;
+}
+
+void
+from_json(const nlohmann::json &obj, SecretSend &event)
+{
+        event.request_id = obj.value("request_id", "");
+        event.secret     = obj.value("secret", "");
+}
+
+void
+to_json(nlohmann::json &obj, const SecretSend &event)
+{
+        obj["request_id"] = event.request_id;
+        obj["secret"]     = event.secret;
+}
 } // namespace msg
 } // namespace events
 } // namespace mtx
diff --git a/lib/structs/responses/common.cpp b/lib/structs/responses/common.cpp
index 23e9790d64451d231eef99e17ea2dda388584aa4..0381246a769dce00f03ddded0fbe1dd89afc6274 100644
--- a/lib/structs/responses/common.cpp
+++ b/lib/structs/responses/common.cpp
@@ -114,6 +114,8 @@ parse_room_account_data_events(
                 case events::EventType::KeyVerificationAccept:
                 case events::EventType::KeyVerificationKey:
                 case events::EventType::KeyVerificationMac:
+                case events::EventType::SecretRequest:
+                case events::EventType::SecretSend:
                 case events::EventType::Presence:
                 case events::EventType::Reaction:
                 case events::EventType::RoomAliases:
@@ -554,6 +556,8 @@ parse_timeline_events(const json &events,
                 case events::EventType::Tag:              // Not part of the timeline or state
                 case events::EventType::Presence:         // Not part of the timeline or state
                 case events::EventType::PushRules:        // Not part of the timeline or state
+                case events::EventType::SecretRequest:    // Not part of the timeline or state
+                case events::EventType::SecretSend:       // Not part of the timeline or state
                 case events::EventType::Unsupported:
                 case events::EventType::NhekoHiddenEvents:
                         continue;
@@ -687,6 +691,22 @@ parse_device_events(const json &events,
                                 log_error(err, e);
                         }
 
+                        break;
+                case events::EventType::SecretSend:
+                        try {
+                                container.emplace_back(events::DeviceEvent<SecretSend>(e));
+                        } catch (json::exception &err) {
+                                log_error(err, e);
+                        }
+
+                        break;
+                case events::EventType::SecretRequest:
+                        try {
+                                container.emplace_back(events::DeviceEvent<SecretRequest>(e));
+                        } catch (json::exception &err) {
+                                log_error(err, e);
+                        }
+
                         break;
                 default:
                         continue;
@@ -843,6 +863,8 @@ parse_state_events(const json &events,
                 case events::EventType::KeyVerificationAccept:
                 case events::EventType::KeyVerificationKey:
                 case events::EventType::KeyVerificationMac:
+                case events::EventType::SecretRequest:
+                case events::EventType::SecretSend:
                 case events::EventType::CallInvite:
                 case events::EventType::CallCandidates:
                 case events::EventType::CallAnswer:
@@ -994,6 +1016,8 @@ parse_stripped_events(const json &events,
                 case events::EventType::KeyVerificationAccept:
                 case events::EventType::KeyVerificationKey:
                 case events::EventType::KeyVerificationMac:
+                case events::EventType::SecretRequest:
+                case events::EventType::SecretSend:
                 case events::EventType::CallInvite:
                 case events::EventType::CallCandidates:
                 case events::EventType::CallAnswer:
diff --git a/tests/client_api.cpp b/tests/client_api.cpp
index 1d4dab26ceee7464d6398b7540502eb8ef444876..3cb2a7508bd74685dc5f0a9195a1e9d38216871d 100644
--- a/tests/client_api.cpp
+++ b/tests/client_api.cpp
@@ -784,13 +784,14 @@ TEST(ClientAPI, Versions)
         mtx_client->versions([](const mtx::responses::Versions &res, RequestErr err) {
                 check_error(err);
 
-                EXPECT_EQ(res.versions.size(), 6);
+                EXPECT_EQ(res.versions.size(), 7);
                 EXPECT_EQ(res.versions.at(0), "r0.0.1");
                 EXPECT_EQ(res.versions.at(1), "r0.1.0");
                 EXPECT_EQ(res.versions.at(2), "r0.2.0");
                 EXPECT_EQ(res.versions.at(3), "r0.3.0");
                 EXPECT_EQ(res.versions.at(4), "r0.4.0");
                 EXPECT_EQ(res.versions.at(5), "r0.5.0");
+                EXPECT_EQ(res.versions.at(6), "r0.6.0");
         });
 
         mtx_client->close();
diff --git a/tests/e2ee.cpp b/tests/e2ee.cpp
index f2d08d2fb759c10030dae4ce2681008cae10c9b9..6f2cf68f0fc84ee76c5138f927ecdfc372aa6beb 100644
--- a/tests/e2ee.cpp
+++ b/tests/e2ee.cpp
@@ -903,6 +903,196 @@ TEST(Encryption, OlmRoomKeyEncryption)
         bob_http->close();
 }
 
+TEST(Encryption, ShareSecret)
+{
+        // Alice wants to use olm to send data to Bob.
+        auto alice_olm  = std::make_shared<OlmClient>();
+        auto alice_http = std::make_shared<Client>("localhost");
+        alice_olm->create_new_account();
+        alice_olm->generate_one_time_keys(10);
+
+        auto bob_olm  = std::make_shared<OlmClient>();
+        auto bob_http = std::make_shared<Client>("localhost");
+        bob_olm->create_new_account();
+        bob_olm->generate_one_time_keys(10);
+
+        alice_http->login("alice", "secret", &check_login);
+        bob_http->login("bob", "secret", &check_login);
+
+        WAIT_UNTIL(!bob_http->access_token().empty() && !alice_http->access_token().empty())
+
+        bob_olm->set_user_id(bob_http->user_id().to_string());
+        bob_olm->set_device_id(bob_http->device_id());
+        alice_olm->set_user_id(alice_http->user_id().to_string());
+        alice_olm->set_device_id(alice_http->device_id());
+
+        // Both users upload their identity & one time keys
+        atomic_int uploads(0);
+        auto upload_cb = [&uploads](const mtx::responses::UploadKeys &res, RequestErr err) {
+                check_error(err);
+                EXPECT_EQ(res.one_time_key_counts.size(), 1);
+                EXPECT_EQ(res.one_time_key_counts.at("signed_curve25519"), 10);
+                uploads += 1;
+        };
+
+        alice_http->upload_keys(alice_olm->create_upload_keys_request(), upload_cb);
+        bob_http->upload_keys(bob_olm->create_upload_keys_request(), upload_cb);
+
+        WAIT_UNTIL(uploads == 2)
+
+        atomic_bool request_finished(false);
+        std::string bob_ed25519, bob_curve25519, bob_otk;
+
+        // Alice needs Bob's ed25519 device key.
+        mtx::requests::QueryKeys query;
+        query.device_keys[bob_http->user_id().to_string()] = {};
+        alice_http->query_keys(query,
+                               [&request_finished, &bob_ed25519, &bob_curve25519, bob = bob_http](
+                                 const mtx::responses::QueryKeys &res, RequestErr err) {
+                                       check_error(err);
+
+                                       const auto device_id = bob->device_id();
+                                       const auto user_id   = bob->user_id().to_string();
+                                       const auto devices   = res.device_keys.at(user_id);
+
+                                       assert(devices.find(device_id) != devices.end());
+
+                                       bob_ed25519 =
+                                         devices.at(device_id).keys.at("ed25519:" + device_id);
+                                       bob_curve25519 =
+                                         devices.at(device_id).keys.at("curve25519:" + device_id);
+
+                                       request_finished = true;
+                               });
+
+        WAIT_UNTIL(request_finished);
+
+        // Alice needs one of Bob's one time keys.
+        request_finished = false;
+
+        mtx::requests::ClaimKeys claim_keys;
+        claim_keys.one_time_keys[bob_http->user_id().to_string()][bob_http->device_id()] =
+          SIGNED_CURVE25519;
+
+        alice_http->claim_keys(claim_keys,
+                               [&bob_otk, bob = bob_http, &request_finished](
+                                 const mtx::responses::ClaimKeys &res, RequestErr err) {
+                                       check_error(err);
+
+                                       const auto user_id   = bob->user_id().to_string();
+                                       const auto device_id = bob->device_id();
+
+                                       auto retrieved_devices = res.one_time_keys.at(user_id);
+                                       for (const auto &device : retrieved_devices) {
+                                               if (device.first == device_id) {
+                                                       bob_otk = device.second.begin()->at("key");
+                                                       break;
+                                               }
+                                       }
+
+                                       request_finished = true;
+                               });
+
+        WAIT_UNTIL(request_finished);
+
+        EXPECT_EQ(bob_ed25519, bob_olm->identity_keys().ed25519);
+        EXPECT_EQ(bob_curve25519, bob_olm->identity_keys().curve25519);
+        EXPECT_EQ(bob_otk, bob_olm->one_time_keys().curve25519.begin()->second);
+
+        constexpr auto SECRET_TEXT = "Hello Bob!";
+        constexpr auto REQUEST_ID  = "askjfdgsdfgfdg";
+
+        // Alice create m.room.key request
+        mtx::events::msg::SecretRequest secretRequest{};
+        secretRequest.request_id           = REQUEST_ID;
+        secretRequest.name                 = "abcdefg";
+        secretRequest.requesting_device_id = bob_http->device_id();
+        secretRequest.action               = RequestAction::Request;
+
+        SyncOpts opts;
+        opts.timeout = 0;
+
+        // Finally sends the olm encrypted message to Bob's device.
+        atomic_bool is_sent(false);
+        bob_http->send_to_device<SecretRequest>(
+          bob_http->generate_txn_id(),
+          {{alice_http->user_id(), {{alice_http->device_id(), secretRequest}}}},
+          [&](RequestErr err) {
+                  check_error(err);
+
+                  alice_http->sync(opts, [&](const mtx::responses::Sync &res, RequestErr err) {
+                          check_error(err);
+
+                          mtx::events::DeviceEvent<mtx::events::msg::SecretSend> secretSend{};
+                          secretSend.content.secret = SECRET_TEXT;
+                          secretSend.content.request_id =
+                            std::get<mtx::events::DeviceEvent<mtx::events::msg::SecretRequest>>(
+                              res.to_device.events.at(0))
+                              .content.request_id;
+                          secretSend.type = mtx::events::EventType::SecretSend;
+
+                          json payload = secretSend;
+
+                          // Alice creates an outbound session.
+                          auto out_session =
+                            alice_olm->create_outbound_session(bob_curve25519, bob_otk);
+                          auto device_msg = alice_olm->create_olm_encrypted_content(
+                            out_session.get(),
+                            payload,
+                            UserId("@bob:localhost"),
+                            bob_olm->identity_keys().ed25519,
+                            bob_curve25519);
+
+                          std::map<mtx::identifiers::User,
+                                   std::map<std::string, mtx::events::msg::OlmEncrypted>>
+                            body{{bob_http->user_id(), {{bob_http->device_id(), device_msg}}}};
+                          alice_http->send_to_device<OlmEncrypted>(
+                            alice_http->generate_txn_id(), body, [&is_sent](RequestErr err) {
+                                    check_error(err);
+                                    is_sent = true;
+                            });
+                  });
+          });
+
+        WAIT_UNTIL(is_sent)
+
+        bob_http->sync(
+          opts,
+          [bob = bob_olm, SECRET_TEXT, REQUEST_ID](const mtx::responses::Sync &res,
+                                                   RequestErr err) {
+                  check_error(err);
+
+                  EXPECT_EQ(res.to_device.events.size(), 1);
+
+                  auto olm_msg =
+                    std::get<DeviceEvent<msgs::OlmEncrypted>>(res.to_device.events[0]).content;
+                  auto cipher = olm_msg.ciphertext.begin();
+
+                  EXPECT_EQ(cipher->first, bob->identity_keys().curve25519);
+
+                  const auto msg_body = cipher->second.body;
+                  const auto msg_type = cipher->second.type;
+
+                  assert(msg_type == 0);
+
+                  auto inbound_session = bob->create_inbound_session(msg_body);
+                  ASSERT_TRUE(matches_inbound_session_from(
+                    inbound_session.get(), olm_msg.sender_key, msg_body));
+
+                  auto output = bob->decrypt_message(inbound_session.get(), msg_type, msg_body);
+
+                  // Parsing the original plaintext json object.
+                  auto ev = json::parse(std::string_view((char *)output.data(), output.size()))
+                              .get<mtx::events::DeviceEvent<mtx::events::msg::SecretSend>>();
+
+                  ASSERT_EQ(ev.content.secret, SECRET_TEXT);
+                  ASSERT_EQ(ev.content.request_id, REQUEST_ID);
+          });
+
+        alice_http->close();
+        bob_http->close();
+}
+
 TEST(Encryption, PickleAccount)
 {
         auto alice = std::make_shared<OlmClient>();
diff --git a/tests/media_api.cpp b/tests/media_api.cpp
index 3c76493c565e515d6ce827cea3c60f80ecba51c0..defd835234a1881bb09f5f83a11f3975aeefe072 100644
--- a/tests/media_api.cpp
+++ b/tests/media_api.cpp
@@ -50,24 +50,26 @@ TEST(MediaAPI, UploadTextFile)
 
                 const auto text = "This is some random text";
 
-                alice->upload(text,
-                              "text/plain",
-                              "doc.txt",
-                              [alice, text](const mtx::responses::ContentURI &res, RequestErr err) {
-                                      validate_upload(res, err);
-
-                                      alice->download(res.content_uri,
-                                                      [text](const string &data,
-                                                             const string &content_type,
-                                                             const string &original_filename,
-                                                             RequestErr err) {
-                                                              ASSERT_FALSE(err);
-                                                              EXPECT_EQ(data, text);
-                                                              EXPECT_EQ(content_type, "text/plain");
-                                                              EXPECT_EQ(original_filename,
-                                                                        "doc.txt");
-                                                      });
-                              });
+                alice->upload(
+                  text,
+                  "text/plain",
+                  "doc.txt",
+                  [alice, text](const mtx::responses::ContentURI &res, RequestErr err) {
+                          validate_upload(res, err);
+
+                          alice->download(
+                            res.content_uri,
+                            [text](const string &data,
+                                   const string &content_type,
+                                   const string &original_filename,
+                                   RequestErr err) {
+                                    ASSERT_FALSE(err);
+                                    EXPECT_EQ(data, text);
+                                    EXPECT_EQ(content_type.substr(0, std::size("text/plain") - 1),
+                                              "text/plain");
+                                    EXPECT_EQ(original_filename, "doc.txt");
+                            });
+                  });
         });
 
         alice->close();