diff --git a/src/jamidht/account_manager.cpp b/src/jamidht/account_manager.cpp
index 8074a284d5c1b1b7073b3dca6a3f2bc7604869d8..9652197798954115645359af2088e1b711ad11f9 100644
--- a/src/jamidht/account_manager.cpp
+++ b/src/jamidht/account_manager.cpp
@@ -181,6 +181,7 @@ AccountManager::useIdentity(const std::string& accountId,
                             const std::string& username,
                             const OnChangeCallback& onChange)
 {
+    accountId_ = accountId;
     if (receipt.empty() or receiptSignature.empty())
         return nullptr;
 
diff --git a/src/jamidht/account_manager.h b/src/jamidht/account_manager.h
index 9703012f43025ee1c8c9569a8685ec2ea1a8708d..3c8e10b578a40dff8dfeff61568c9b300bad447a 100644
--- a/src/jamidht/account_manager.h
+++ b/src/jamidht/account_manager.h
@@ -270,6 +270,7 @@ protected:
     std::filesystem::path path_;
     OnChangeCallback onChange_;
     std::unique_ptr<AccountInfo> info_;
+    std::string accountId_;
     std::shared_ptr<dht::DhtRunner> dht_;
     std::reference_wrapper<NameDirectory> nameDir_;
 };
diff --git a/src/jamidht/conversation_module.cpp b/src/jamidht/conversation_module.cpp
index 704e57a485e8c41d21ff12da37ba6b027fc6d4ff..c1a3db60ba6b9c1218644e884511ea1ee034e568 100644
--- a/src/jamidht/conversation_module.cpp
+++ b/src/jamidht/conversation_module.cpp
@@ -323,7 +323,9 @@ public:
     /**
      * @note convInfosMtx_ should be locked
      */
-    void saveConvInfos() const { ConversationModule::saveConvInfos(accountId_, convInfos_); }
+    void saveConvInfos() const {
+        ConversationModule::saveConvInfos(accountId_, convInfos_);
+    }
     /**
      * @note conversationsRequestsMtx_ should be locked
      */
@@ -403,6 +405,10 @@ public:
     void fixStructures(std::shared_ptr<JamiAccount> account, const std::vector<std::tuple<std::string, std::string, std::string>>& updateContactConv, const std::set<std::string>& toRm);
 
     void cloneConversationFrom(const std::shared_ptr<SyncedConversation> conv, const std::string& deviceId, const std::string& oldConvId = "");
+    void bootstrap(const std::string& convId);
+    void cloneConversationFrom(const std::string& conversationId,
+                            const std::string& uri,
+                            const std::string& oldConvId = "");
 };
 
 ConversationModule::Impl::Impl(std::weak_ptr<JamiAccount>&& account,
@@ -1225,6 +1231,85 @@ ConversationModule::Impl::cloneConversationFrom(const std::shared_ptr<SyncedConv
         MIME_TYPE_GIT);
 }
 
+void
+ConversationModule::Impl::bootstrap(const std::string& convId)
+{
+    std::vector<DeviceId> kd;
+    if (auto acc = account_.lock())
+        for (const auto& [id, _] : acc->getKnownDevices())
+            kd.emplace_back(id);
+    auto bootstrap = [&](auto& conv) {
+        if (conv) {
+#ifdef LIBJAMI_TESTABLE
+            conv->onBootstrapStatus(bootstrapCbTest_);
+#endif // LIBJAMI_TESTABLE
+            conv->bootstrap(std::bind(&ConversationModule::Impl::bootstrapCb,
+                                      this,
+                                      conv->id()),
+                            kd);
+        }
+    };
+    std::vector<std::string> toClone;
+    if (convId.empty()) {
+        std::lock_guard<std::mutex> lk(convInfosMtx_);
+        for (const auto& [conversationId, convInfo] : convInfos_) {
+            auto conv = getConversation(conversationId);
+            if (!conv)
+                return;
+            if ((!conv->conversation && !conv->info.removed)) {
+                // Because we're not tracking contact presence in order to sync now,
+                // we need to ask to clone requests when bootstraping all conversations
+                // else it can stay syncing
+                toClone.emplace_back(conversationId);
+            } else if (conv->conversation) {
+                bootstrap(conv->conversation);
+            }
+        }
+    } else if (auto conv = getConversation(convId)) {
+        std::lock_guard<std::mutex> lk(conv->mtx);
+        if (conv->conversation)
+            bootstrap(conv->conversation);
+    }
+
+    for (const auto& cid : toClone) {
+        auto members = getConversationMembers(cid);
+        for (const auto& member : members) {
+            if (member.at("uri") != username_)
+                cloneConversationFrom(cid, member.at("uri"));
+        }
+    }
+}
+
+void
+ConversationModule::Impl::cloneConversationFrom(const std::string& conversationId, const std::string& uri, const std::string& oldConvId)
+{
+    auto acc = account_.lock();
+    auto memberHash = dht::InfoHash(uri);
+    if (!acc || !memberHash) {
+        JAMI_WARNING("Invalid member detected: {}", uri);
+        return;
+    }
+    auto conv = startConversation(conversationId);
+    conv->info = {};
+    conv->info.id = conversationId;
+    conv->info.created = std::time(nullptr);
+    conv->info.members.emplace_back(username_);
+    conv->info.members.emplace_back(uri);
+
+    std::lock_guard<std::mutex> lk(conv->mtx);
+
+    acc->forEachDevice(
+        memberHash,
+        [w = weak(), conv, conversationId, oldConvId](
+            const std::shared_ptr<dht::crypto::PublicKey>& pk) {
+            auto sthis = w.lock();
+            auto deviceId = pk->getLongId().toString();
+            if (!sthis or deviceId == sthis->deviceId_)
+                return;
+            sthis->cloneConversationFrom(conv, deviceId, oldConvId);
+        });
+    addConvInfo(conv->info);
+}
 
 ////////////////////////////////////////////////////////////////
 
@@ -1449,51 +1534,9 @@ ConversationModule::loadConversations()
 void
 ConversationModule::bootstrap(const std::string& convId)
 {
-    std::vector<DeviceId> kd;
-    if (auto acc = pimpl_->account_.lock())
-        for (const auto& [id, _] : acc->getKnownDevices())
-            kd.emplace_back(id);
-    auto bootstrap = [&](auto& conv) {
-        if (conv) {
-#ifdef LIBJAMI_TESTABLE
-            conv->onBootstrapStatus(pimpl_->bootstrapCbTest_);
-#endif // LIBJAMI_TESTABLE
-            conv->bootstrap(std::bind(&ConversationModule::Impl::bootstrapCb,
-                                      pimpl_.get(),
-                                      conv->id()),
-                            kd);
-        }
-    };
-    std::vector<std::string> toClone;
-    if (convId.empty()) {
-        std::lock_guard<std::mutex> lk(pimpl_->convInfosMtx_);
-        for (const auto& [conversationId, convInfo] : pimpl_->convInfos_) {
-            auto conv = pimpl_->getConversation(conversationId);
-            if (!conv)
-                return;
-            if ((!conv->conversation && !conv->info.removed)) {
-                // Because we're not tracking contact presence in order to sync now,
-                // we need to ask to clone requests when bootstraping all conversations
-                // else it can stay syncing
-                toClone.emplace_back(conversationId);
-            } else if (conv->conversation) {
-                bootstrap(conv->conversation);
-            }
-        }
-    } else if (auto conv = pimpl_->getConversation(convId)) {
-        std::lock_guard<std::mutex> lk(conv->mtx);
-        if (conv->conversation)
-            bootstrap(conv->conversation);
-    }
-
-    for (const auto& cid : toClone) {
-        auto members = getConversationMembers(cid);
-        for (const auto& member : members) {
-            if (member.at("uri") != pimpl_->username_)
-                cloneConversationFrom(cid, member.at("uri"));
-        }
-    }
+    pimpl_->bootstrap(convId);
 }
+
 void
 ConversationModule::monitor()
 {
@@ -1605,6 +1648,7 @@ ConversationModule::onTrustRequest(const std::string& uri,
         emitSignal<libjami::ConversationSignal::ConversationRequestReceived>(pimpl_->accountId_,
                                                                              conversationId,
                                                                              reqMap);
+        pimpl_->needsSyncingCb_({});
     }
 }
 
@@ -1775,32 +1819,7 @@ ConversationModule::cloneConversationFrom(const std::string& conversationId,
                                           const std::string& uri,
                                           const std::string& oldConvId)
 {
-    auto acc = pimpl_->account_.lock();
-    auto memberHash = dht::InfoHash(uri);
-    if (!acc || !memberHash) {
-        JAMI_WARNING("Invalid member detected: {}", uri);
-        return;
-    }
-    auto conv = pimpl_->startConversation(conversationId);
-    conv->info = {};
-    conv->info.id = conversationId;
-    conv->info.created = std::time(nullptr);
-    conv->info.members.emplace_back(pimpl_->username_);
-    conv->info.members.emplace_back(uri);
-
-    std::lock_guard<std::mutex> lk(conv->mtx);
-
-    acc->forEachDevice(
-        memberHash,
-        [w = pimpl_->weak(), conv, conversationId, oldConvId](
-            const std::shared_ptr<dht::crypto::PublicKey>& pk) {
-            auto sthis = w.lock();
-            auto deviceId = pk->getLongId().toString();
-            if (!sthis or deviceId == sthis->deviceId_)
-                return;
-            sthis->cloneConversationFrom(conv, deviceId, oldConvId);
-        });
-    addConvInfo(conv->info);
+    pimpl_->cloneConversationFrom(conversationId, uri, oldConvId);
 }
 
 // Message send/load
@@ -2044,6 +2063,7 @@ ConversationModule::onSyncData(const SyncMsg& msg,
                                const std::string& peerId,
                                const std::string& deviceId)
 {
+    std::vector<std::string> toClone;
     for (const auto& [key, convInfo] : msg.c) {
         const auto& convId = convInfo.id;
         pimpl_->rmConversationRequest(convId);
@@ -2062,8 +2082,17 @@ ConversationModule::onSyncData(const SyncMsg& msg,
                 JAMI_DEBUG("Re-add previously removed conversation {:s}", convId);
             }
             conv->info = convInfo;
-            if (!conv->conversation)
-                pimpl_->cloneConversation(deviceId, peerId, conv, convInfo.lastDisplayed);
+            if (!conv->conversation) {
+                if (deviceId != "") {
+                    pimpl_->cloneConversation(deviceId, peerId, conv, convInfo.lastDisplayed);
+                } else {
+                    // In this case, informations are from JAMS
+                    // JAMS doesn't store the conversation itself, so we
+                    // must use informations to clone the conversation
+                    addConvInfo(convInfo);
+                    toClone.emplace_back(convId);
+                }
+            }
         } else {
             if (conv->conversation && !conv->conversation->isRemoving()) {
                 emitSignal<libjami::ConversationSignal::ConversationRemoved>(pimpl_->accountId_,
@@ -2085,6 +2114,14 @@ ConversationModule::onSyncData(const SyncMsg& msg,
         }
     }
 
+    for (const auto& cid : toClone) {
+        auto members = getConversationMembers(cid);
+        for (const auto& member : members) {
+            if (member.at("uri") != pimpl_->username_)
+                cloneConversationFrom(cid, member.at("uri"));
+        }
+    }
+
     for (const auto& [convId, req] : msg.cr) {
         if (pimpl_->isAcceptedConversation(convId)) {
             // Already accepted request
diff --git a/src/jamidht/jamiaccount.cpp b/src/jamidht/jamiaccount.cpp
index 55efcf4dcab563a8f795efc102512d9c829cceea..7607ca92aadaad4f56fdde7ff465d88344f0e216 100644
--- a/src/jamidht/jamiaccount.cpp
+++ b/src/jamidht/jamiaccount.cpp
@@ -2138,9 +2138,15 @@ JamiAccount::convModule()
             weak(),
             [this](auto&& syncMsg) {
                 dht::ThreadPool::io().run([w = weak(), syncMsg] {
-                    if (auto shared = w.lock())
+                    if (auto shared = w.lock()) {
+                        auto& config = shared->config();
+                        // For JAMS account, we must update the server
+                        if (!config.managerUri.empty())
+                            if (auto am = shared->accountManager())
+                                am->syncDevices();
                         if (auto sm = shared->syncModule())
                             sm->syncWithConnected(syncMsg);
+                    }
                 });
             },
             [this](auto&& uri, auto&& device, auto&& msg, auto token = 0) {
diff --git a/src/jamidht/server_account_manager.cpp b/src/jamidht/server_account_manager.cpp
index 9b391eb4798cd9582ff93d1258342cbc2878f7b3..056d24a6d2790f89f8450f869c2b8e8fdaa0bcf2 100644
--- a/src/jamidht/server_account_manager.cpp
+++ b/src/jamidht/server_account_manager.cpp
@@ -24,6 +24,8 @@
 #include <opendht/log.h>
 #include <opendht/thread_pool.h>
 
+#include "conversation_module.h"
+#include "jamiaccount.h"
 #include "manager.h"
 
 #include <algorithm>
@@ -41,6 +43,8 @@ constexpr std::string_view PATH_DEVICE = JAMI_PATH_AUTH "/device";
 constexpr std::string_view PATH_DEVICES = JAMI_PATH_AUTH "/devices";
 constexpr std::string_view PATH_SEARCH = JAMI_PATH_AUTH "/directory/search";
 constexpr std::string_view PATH_CONTACTS = JAMI_PATH_AUTH "/contacts";
+constexpr std::string_view PATH_CONVERSATIONS = JAMI_PATH_AUTH "/conversations";
+constexpr std::string_view PATH_CONVERSATIONS_REQUESTS = JAMI_PATH_AUTH "/conversationRequests";
 constexpr std::string_view PATH_BLUEPRINT = JAMI_PATH_AUTH "/policyData";
 
 ServerAccountManager::ServerAccountManager(const std::filesystem::path& path,
@@ -379,8 +383,95 @@ ServerAccountManager::syncDevices()
 {
     const std::string urlDevices = managerHostname_ + PATH_DEVICES;
     const std::string urlContacts = managerHostname_ + PATH_CONTACTS;
+    const std::string urlConversations = managerHostname_ + PATH_CONVERSATIONS;
+    const std::string urlConversationsRequests = managerHostname_ + PATH_CONVERSATIONS_REQUESTS;
 
-    JAMI_WARN("[Auth] syncContacts %s", urlContacts.c_str());
+    JAMI_WARNING("[Auth] sync conversations {}", urlConversations);
+    Json::Value jsonConversations(Json::arrayValue);
+    for (const auto& [key, convInfo] : ConversationModule::convInfos(accountId_)) {
+        jsonConversations.append(convInfo.toJson());
+    }
+    sendDeviceRequest(std::make_shared<Request>(
+        *Manager::instance().ioContext(),
+        urlConversations,
+        jsonConversations,
+        [w=weak_from_this()](Json::Value json, const dht::http::Response& response) {
+                JAMI_DEBUG("[Auth] Got conversation sync request callback with status code={}",
+                         response.status_code);
+            auto this_ = std::static_pointer_cast<ServerAccountManager>(w.lock());
+            if (!this_) return;
+            if (response.status_code >= 200 && response.status_code < 300) {
+                try {
+                    JAMI_WARNING("[Auth] Got server response: {}", response.body);
+                    if (not json.isArray()) {
+                        JAMI_ERROR("[Auth] Can't parse server response: not an array");
+                    } else {
+                        SyncMsg convs;
+                        for (unsigned i = 0, n = json.size(); i < n; i++) {
+                            const auto& e = json[i];
+                            auto ci = ConvInfo(e);
+                            convs.c[ci.id] = std::move(ci);
+                        }
+                        dht::ThreadPool::io().run([accountId=this_->accountId_, convs] {
+                            if (auto acc = Manager::instance().getAccount<JamiAccount>(accountId)) {
+                                acc->convModule()->onSyncData(convs, "", "");
+                            }
+                        });
+                    }
+                } catch (const std::exception& e) {
+                    JAMI_ERROR("Error when iterating conversation list: {}", e.what());
+                }
+            } else if (response.status_code == 401)
+                this_->authError(TokenScope::Device);
+
+            this_->clearRequest(response.request);
+        },
+        logger_));
+
+    JAMI_WARNING("[Auth] sync conversations requests {}", urlConversationsRequests);
+    Json::Value jsonConversationsRequests(Json::arrayValue);
+    for (const auto& [key, convRequest] : ConversationModule::convRequests(accountId_)) {
+        auto jsonConversation = convRequest.toJson();
+        jsonConversationsRequests.append(std::move(jsonConversation));
+    }
+    sendDeviceRequest(std::make_shared<Request>(
+        *Manager::instance().ioContext(),
+        urlConversationsRequests,
+        jsonConversationsRequests,
+        [w=weak_from_this()](Json::Value json, const dht::http::Response& response) {
+                JAMI_DEBUG("[Auth] Got conversations requests sync request callback with status code={}",
+                         response.status_code);
+            auto this_ = std::static_pointer_cast<ServerAccountManager>(w.lock());
+            if (!this_) return;
+            if (response.status_code >= 200 && response.status_code < 300) {
+                try {
+                    JAMI_WARNING("[Auth] Got server response: {}", response.body);
+                    if (not json.isArray()) {
+                        JAMI_ERROR("[Auth] Can't parse server response: not an array");
+                    } else {
+                        SyncMsg convReqs;
+                        for (unsigned i = 0, n = json.size(); i < n; i++) {
+                            const auto& e = json[i];
+                            auto cr = ConversationRequest(e);
+                            convReqs.cr[cr.conversationId] = std::move(cr);
+                        }
+                        dht::ThreadPool::io().run([accountId=this_->accountId_, convReqs] {
+                            if (auto acc = Manager::instance().getAccount<JamiAccount>(accountId)) {
+                                acc->convModule()->onSyncData(convReqs, "", "");
+                            }
+                        });
+                    }
+                } catch (const std::exception& e) {
+                    JAMI_ERROR("Error when iterating conversations requests list: {}", e.what());
+                }
+            } else if (response.status_code == 401)
+                this_->authError(TokenScope::Device);
+
+            this_->clearRequest(response.request);
+        },
+        logger_));
+
+    JAMI_WARNING("[Auth] syncContacts {}", urlContacts);
     Json::Value jsonContacts(Json::arrayValue);
     for (const auto& contact : info_->contacts->getContacts()) {
         auto jsonContact = contact.second.toJson();
@@ -392,15 +483,15 @@ ServerAccountManager::syncDevices()
         urlContacts,
         jsonContacts,
         [w=weak_from_this()](Json::Value json, const dht::http::Response& response) {
-                JAMI_DBG("[Auth] Got contact sync request callback with status code=%u",
+                JAMI_DEBUG("[Auth] Got contact sync request callback with status code={}",
                          response.status_code);
             auto this_ = std::static_pointer_cast<ServerAccountManager>(w.lock());
             if (!this_) return;
             if (response.status_code >= 200 && response.status_code < 300) {
                 try {
-                    JAMI_WARN("[Auth] Got server response: %s", response.body.c_str());
+                    JAMI_WARNING("[Auth] Got server response: {}", response.body);
                     if (not json.isArray()) {
-                        JAMI_ERR("[Auth] Can't parse server response: not an array");
+                        JAMI_ERROR("[Auth] Can't parse server response: not an array");
                     } else {
                         for (unsigned i = 0, n = json.size(); i < n; i++) {
                             const auto& e = json[i];
@@ -411,7 +502,7 @@ ServerAccountManager::syncDevices()
                         this_->info_->contacts->saveContacts();
                     }
                 } catch (const std::exception& e) {
-                    JAMI_ERR("Error when iterating contact list: %s", e.what());
+                    JAMI_ERROR("Error when iterating contact list: {}", e.what());
                 }
             } else if (response.status_code == 401)
                 this_->authError(TokenScope::Device);
@@ -420,19 +511,19 @@ ServerAccountManager::syncDevices()
         },
         logger_));
 
-    JAMI_WARN("[Auth] syncDevices %s", urlDevices.c_str());
+    JAMI_WARNING("[Auth] syncDevices {}", urlDevices);
     sendDeviceRequest(std::make_shared<Request>(
         *Manager::instance().ioContext(),
         urlDevices,
         [w=weak_from_this()](Json::Value json, const dht::http::Response& response) {
-            JAMI_DBG("[Auth] Got request callback with status code=%u", response.status_code);
+            JAMI_DEBUG("[Auth] Got request callback with status code={}", response.status_code);
             auto this_ = std::static_pointer_cast<ServerAccountManager>(w.lock());
             if (!this_) return;
             if (response.status_code >= 200 && response.status_code < 300) {
                 try {
-                    JAMI_WARN("[Auth] Got server response: %s", response.body.c_str());
+                    JAMI_WARNING("[Auth] Got server response: {}", response.body);
                     if (not json.isArray()) {
-                        JAMI_ERR("[Auth] Can't parse server response: not an array");
+                        JAMI_ERROR("[Auth] Can't parse server response: not an array");
                     } else {
                         for (unsigned i = 0, n = json.size(); i < n; i++) {
                             const auto& e = json[i];
@@ -445,7 +536,7 @@ ServerAccountManager::syncDevices()
                         }
                     }
                 } catch (const std::exception& e) {
-                    JAMI_ERR("Error when iterating device list: %s", e.what());
+                    JAMI_ERROR("Error when iterating device list: {}", e.what());
                 }
             } else if (response.status_code == 401)
                 this_->authError(TokenScope::Device);
@@ -497,24 +588,24 @@ ServerAccountManager::revokeDevice(const std::string& password,
         return false;
     }
     const std::string url = managerHostname_ + PATH_DEVICE + "/" + device;
-    JAMI_WARN("[Revoke] Revoking device of %s at %s", info_->username.c_str(), url.c_str());
+    JAMI_WARNING("[Revoke] Revoking device of {} at {}", info_->username, url);
     auto request = std::make_shared<Request>(
         *Manager::instance().ioContext(),
         url,
         [cb, w=weak_from_this()](Json::Value json, const dht::http::Response& response) {
-            JAMI_DBG("[Revoke] Got request callback with status code=%u", response.status_code);
+            JAMI_DEBUG("[Revoke] Got request callback with status code={}", response.status_code);
             auto this_ = std::static_pointer_cast<ServerAccountManager>(w.lock());
             if (!this_) return;
             if (response.status_code >= 200 && response.status_code < 300) {
                 try {
-                    JAMI_WARN("[Revoke] Got server response");
+                    JAMI_WARNING("[Revoke] Got server response");
                     if (json["errorDetails"].empty()) {
                         if (cb)
                             cb(RevokeDeviceResult::SUCCESS);
                         this_->syncDevices();
                     }
                 } catch (const std::exception& e) {
-                    JAMI_ERR("Error when loading device list: %s", e.what());
+                    JAMI_ERROR("Error when loading device list: {}", e.what());
                 }
             } else if (cb)
                 cb(RevokeDeviceResult::ERROR_NETWORK);
@@ -536,7 +627,7 @@ bool
 ServerAccountManager::searchUser(const std::string& query, SearchCallback cb)
 {
     const std::string url = managerHostname_ + PATH_SEARCH + "?queryString=" + query;
-    JAMI_WARN("[Search] Searching user %s at %s", query.c_str(), url.c_str());
+    JAMI_WARNING("[Search] Searching user {} at {}", query, url);
     sendDeviceRequest(std::make_shared<Request>(
         *Manager::instance().ioContext(),
         url,
@@ -550,7 +641,7 @@ ServerAccountManager::searchUser(const std::string& query, SearchCallback cb)
                     Json::Value::ArrayIndex rcount = profiles.size();
                     std::vector<std::map<std::string, std::string>> results;
                     results.reserve(rcount);
-                    JAMI_WARN("[Search] Got server response: %s", response.body.c_str());
+                    JAMI_WARNING("[Search] Got server response: {}", response.body);
                     for (Json::Value::ArrayIndex i = 0; i < rcount; i++) {
                         const auto& ruser = profiles[i];
                         std::map<std::string, std::string> user;
@@ -564,7 +655,7 @@ ServerAccountManager::searchUser(const std::string& query, SearchCallback cb)
                     if (cb)
                         cb(results, SearchResponse::found);
                 } catch (const std::exception& e) {
-                    JAMI_ERR("[Search] Error during search: %s", e.what());
+                    JAMI_ERROR("[Search] Error during search: {}", e.what());
                 }
             } else {
                 if (response.status_code == 401)