diff --git a/.travis.yml b/.travis.yml
index 42f854b2d890804b3d22f2cce8775b1a613f368c..7cb64b784bdc4c616c4573fe1bddc5b22cea5a3f 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -32,7 +32,7 @@ matrix:
     - os: linux
       compiler: clang
       env:
-        - CXX_VERSION=clang++-5.0
+        - CXX_VERSION=clang++-6.0
         - LINT=ON
         - TESTS=ON
       addons:
@@ -60,7 +60,7 @@ script:
   - cmake --version
 
   # Build library, examples & tests
-  - cmake -H. -Bbuild -DOPENSSL_ROOT_DIR=$OPENSLL_ROOT_DIR -DBUILD_LIB_TESTS=$TESTS -DBUILD_OLM=1
+  - cmake -H. -Bbuild -DOPENSSL_ROOT_DIR=$OPENSLL_ROOT_DIR -DBUILD_LIB_TESTS=$TESTS
   - cmake --build build
 
   # Unit & Integration tests
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 84079bdbb7d36cd25a224d142a55e2f492bde773..465730a45d274e7a2551b50b9e9b7f56efebba71 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -5,7 +5,6 @@ project(matrix_client CXX)
 option(ASAN "Compile with address sanitizers" OFF)
 option(BUILD_LIB_TESTS "Build tests" ON)
 option(BUILD_LIB_EXAMPLES "Build examples" ON)
-option(BUILD_OLM "Fetch & build libolm" OFF)
 
 set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake)
 set(CMAKE_INCLUDE_CURRENT_DIR ON)
diff --git a/src/client.cpp b/src/client.cpp
index b19f6fc7b687e853e991746bee932e3f31571fe9..4eb178256156f888af2b46ebfb8959d3299721e8 100644
--- a/src/client.cpp
+++ b/src/client.cpp
@@ -577,3 +577,23 @@ Client::query_keys(
         post<mtx::requests::QueryKeys, mtx::responses::QueryKeys>(
           "/client/r0/keys/query", req, callback);
 }
+
+void
+Client::key_changes(
+  const std::string &from,
+  const std::string &to,
+  std::function<void(const mtx::responses::KeyChanges &res, RequestErr err)> callback)
+{
+        std::map<std::string, std::string> params;
+
+        if (!from.empty())
+                params.emplace("from", from);
+
+        if (!to.empty())
+                params.emplace("to", to);
+
+        get<mtx::responses::KeyChanges>("/client/r0/keys/changes?" + utils::query_params(params),
+                                        [callback](const mtx::responses::KeyChanges &res,
+                                                   HeaderFields,
+                                                   RequestErr err) { callback(res, err); });
+}
diff --git a/src/client.hpp b/src/client.hpp
index 4ba7d1169de3fa60f049f6eca6785726eac3959a..f7838935b9b58a065ffff8836a1571df63bdc7d3 100644
--- a/src/client.hpp
+++ b/src/client.hpp
@@ -211,6 +211,13 @@ public:
           const mtx::requests::QueryKeys &req,
           std::function<void(const mtx::responses::QueryKeys &res, RequestErr err)> cb);
 
+        //! Gets a list of users who have updated their device identity keys
+        //! since a previous sync token.
+        void key_changes(
+          const std::string &from,
+          const std::string &to,
+          std::function<void(const mtx::responses::KeyChanges &res, RequestErr err)> cb);
+
 private:
         template<class Request, class Response>
         void post(const std::string &endpoint,
diff --git a/tests/e2ee.cpp b/tests/e2ee.cpp
index 52c04dad76695f18f39a2076561d548099502a3e..9149bb67673f870b074ef1900c542ff6b088ee64 100644
--- a/tests/e2ee.cpp
+++ b/tests/e2ee.cpp
@@ -24,6 +24,20 @@ using namespace std;
 
 using ErrType = std::experimental::optional<errors::ClientError>;
 
+mtx::requests::UploadKeys
+generate_keys(std::shared_ptr<olm::Account> account,
+              const mtx::identifiers::User &user_id,
+              const std::string &device_id)
+{
+        auto idks = mtx::client::crypto::identity_keys(account);
+
+        mtx::client::crypto::generate_one_time_keys(account, 1);
+        auto otks = mtx::client::crypto::one_time_keys(account);
+
+        return mtx::client::crypto::create_upload_keys_request(
+          account, idks, otks, user_id, device_id);
+}
+
 void
 check_error(ErrType err)
 {
@@ -146,13 +160,7 @@ TEST(Encryption, UploadKeys)
         while (alice->access_token().empty())
                 std::this_thread::sleep_for(std::chrono::milliseconds(100));
 
-        json identity_keys = mtx::client::crypto::identity_keys(olm_account);
-
-        mtx::client::crypto::generate_one_time_keys(olm_account, 1);
-        auto one_time_keys = mtx::client::crypto::one_time_keys(olm_account);
-
-        auto req = mtx::client::crypto::create_upload_keys_request(
-          olm_account, identity_keys, one_time_keys, alice->user_id(), alice->device_id());
+        auto req = ::generate_keys(olm_account, alice->user_id(), alice->device_id());
 
         alice->upload_keys(req, [](const mtx::responses::UploadKeys &res, ErrType err) {
                 check_error(err);
@@ -181,20 +189,8 @@ TEST(Encryption, QueryKeys)
                 std::this_thread::sleep_for(std::chrono::milliseconds(100));
 
         // Create and upload keys for both users.
-        auto alice_idks = mtx::client::crypto::identity_keys(alice_olm);
-        auto bob_idks   = mtx::client::crypto::identity_keys(bob_olm);
-
-        mtx::client::crypto::generate_one_time_keys(alice_olm, 1);
-        mtx::client::crypto::generate_one_time_keys(bob_olm, 1);
-
-        auto alice_otks = mtx::client::crypto::one_time_keys(alice_olm);
-        auto bob_otks   = mtx::client::crypto::one_time_keys(bob_olm);
-
-        auto alice_req = mtx::client::crypto::create_upload_keys_request(
-          alice_olm, alice_idks, alice_otks, alice->user_id(), alice->device_id());
-
-        auto bob_req = mtx::client::crypto::create_upload_keys_request(
-          bob_olm, bob_idks, bob_otks, bob->user_id(), bob->device_id());
+        auto alice_req = ::generate_keys(alice_olm, alice->user_id(), alice->device_id());
+        auto bob_req   = ::generate_keys(bob_olm, bob->user_id(), bob->device_id());
 
         // Validates that both upload requests are finished.
         atomic_int uploads(0);
@@ -269,3 +265,59 @@ TEST(Encryption, QueryKeys)
         alice->close();
         bob->close();
 }
+
+TEST(Encryption, KeyChanges)
+{
+        auto carl     = std::make_shared<Client>("localhost");
+        auto carl_olm = mtx::client::crypto::olm_new_account();
+
+        carl->login(
+          "carl", "secret", [](const mtx::responses::Login &, ErrType err) { check_error(err); });
+
+        while (carl->access_token().empty())
+                std::this_thread::sleep_for(std::chrono::milliseconds(100));
+
+        mtx::requests::CreateRoom req;
+        carl->create_room(req, [carl, carl_olm](const mtx::responses::CreateRoom &, ErrType err) {
+                check_error(err);
+
+                // Carl syncs to get the first next_batch token.
+                carl->sync(
+                  "", "", false, 0, [carl, carl_olm](const mtx::responses::Sync &res, ErrType err) {
+                          check_error(err);
+                          const auto next_batch_token = res.next_batch;
+
+                          auto key_req =
+                            ::generate_keys(carl_olm, carl->user_id(), carl->device_id());
+
+                          atomic_bool keys_uploaded(false);
+
+                          // Changes his keys.
+                          carl->upload_keys(
+                            key_req,
+                            [&keys_uploaded](const mtx::responses::UploadKeys &, ErrType err) {
+                                    check_error(err);
+                                    keys_uploaded = true;
+                            });
+
+                          while (!keys_uploaded)
+                                  std::this_thread::sleep_for(std::chrono::milliseconds(100));
+
+                          // The key changes should contain his username
+                          // because of the key uploading.
+                          carl->key_changes(
+                            next_batch_token,
+                            "",
+                            [carl](const mtx::responses::KeyChanges &res, ErrType err) {
+                                    check_error(err);
+
+                                    EXPECT_EQ(res.changed.size(), 1);
+                                    EXPECT_EQ(res.left.size(), 0);
+
+                                    EXPECT_EQ(res.changed.at(0), carl->user_id().to_string());
+                            });
+                  });
+        });
+
+        carl->close();
+}