diff --git a/bin/dbus/cx.ring.Ring.ConfigurationManager.xml b/bin/dbus/cx.ring.Ring.ConfigurationManager.xml
index 5e3c6f3e38d636260b08f225d57e41bf69504fbd..a978d063915faccefbf77ef92022bb8e7c81524e 100644
--- a/bin/dbus/cx.ring.Ring.ConfigurationManager.xml
+++ b/bin/dbus/cx.ring.Ring.ConfigurationManager.xml
@@ -1956,6 +1956,33 @@
            </arg>
        </signal>
 
+       <signal name="onConversationError" tp:name-for-bindings="onConversationError">
+           <tp:added version="10.0.0"/>
+           <tp:docstring>
+               Notify clients when an error occurs in a conversation
+           </tp:docstring>
+           <arg type="s" name="account_id">
+               <tp:docstring>
+                   Account id related
+               </tp:docstring>
+           </arg>
+           <arg type="s" name="conversation_id">
+               <tp:docstring>
+                   Conversation id
+               </tp:docstring>
+           </arg>
+           <arg type="u" name="code">
+               <tp:docstring>
+                   TODO
+               </tp:docstring>
+           </arg>
+           <arg type="s" name="what">
+               <tp:docstring>
+                   The error's description
+               </tp:docstring>
+           </arg>
+       </signal>
+
        <signal name="debugMessageReceived" tp:name-for-bindings="debugMessageReceived">
            <tp:added version="5.2.0"/>
            <tp:docstring>
diff --git a/bin/dbus/dbusclient.cpp b/bin/dbus/dbusclient.cpp
index 0881232c84e1a5baeb873b9079fb7112302f56b9..6724539a512173a3b370a76c602d4dc21eed0bdd 100644
--- a/bin/dbus/dbusclient.cpp
+++ b/bin/dbus/dbusclient.cpp
@@ -305,6 +305,8 @@ DBusClient::initLibrary(int flags)
             bind(&DBusConfigurationManager::conversationRemoved, confM, _1, _2)),
         exportable_callback<ConversationSignal::ConversationMemberEvent>(
             bind(&DBusConfigurationManager::conversationMemberEvent, confM, _1, _2, _3, _4)),
+        exportable_callback<ConversationSignal::OnConversationError>(
+            bind(&DBusConfigurationManager::onConversationError, confM, _1, _2, _3, _4)),
     };
 
 #ifdef ENABLE_VIDEO
diff --git a/bin/jni/conversation.i b/bin/jni/conversation.i
index 40b249a44dca21da1946c5c761f0c418c1ce99d8..1427bd08ad8f5be8def108299ab046f6873da1d3 100644
--- a/bin/jni/conversation.i
+++ b/bin/jni/conversation.i
@@ -31,6 +31,7 @@ public:
     virtual void conversationReady(const std::string& /*accountId*/, const std::string& /* conversationId */){}
     virtual void conversationRemoved(const std::string& /*accountId*/, const std::string& /* conversationId */){}
     virtual void conversationMemberEvent(const std::string& /*accountId*/, const std::string& /* conversationId */, const std::string& /* memberUri */, int /* event */){}
+    virtual void onConversationError(const std::string& /*accountId*/, const std::string& /* conversationId */, uint32_t /* code */, const std::string& /* what */){}
 };
 %}
 
@@ -66,4 +67,5 @@ public:
     virtual void conversationReady(const std::string& /*accountId*/, const std::string& /* conversationId */){}
     virtual void conversationRemoved(const std::string& /*accountId*/, const std::string& /* conversationId */){}
     virtual void conversationMemberEvent(const std::string& /*accountId*/, const std::string& /* conversationId */, const std::string& /* memberUri */, int /* event */){}
+    virtual void onConversationError(const std::string& /*accountId*/, const std::string& /* conversationId */, uint32_t /* code */, const std::string& /* what */){}
 };
\ No newline at end of file
diff --git a/bin/jni/jni_interface.i b/bin/jni/jni_interface.i
index 34030f34251f3ade5f8e20cb36beefdf91d6eefd..da3e951268cf47bd4933c5f87269043fe7aec770 100644
--- a/bin/jni/jni_interface.i
+++ b/bin/jni/jni_interface.i
@@ -320,7 +320,8 @@ void init(ConfigurationCallback* confM, Callback* callM, PresenceCallback* presM
         exportable_callback<ConversationSignal::ConversationRequestReceived>(bind(&ConversationCallback::conversationRequestReceived, convM, _1, _2, _3)),
         exportable_callback<ConversationSignal::ConversationReady>(bind(&ConversationCallback::conversationReady, convM, _1, _2)),
         exportable_callback<ConversationSignal::ConversationRemoved>(bind(&ConversationCallback::conversationRemoved, convM, _1, _2)),
-        exportable_callback<ConversationSignal::ConversationMemberEvent>(bind(&ConversationCallback::conversationMemberEvent, convM, _1, _2, _3, _4))
+        exportable_callback<ConversationSignal::ConversationMemberEvent>(bind(&ConversationCallback::conversationMemberEvent, convM, _1, _2, _3, _4)),
+        exportable_callback<ConversationSignal::OnConversationError>(bind(&ConversationCallback::onConversationError, convM, _1, _2, _3, _4))
     };
 
     if (!DRing::init(static_cast<DRing::InitFlag>(DRing::DRING_FLAG_DEBUG)))
diff --git a/bin/nodejs/conversation.i b/bin/nodejs/conversation.i
index 04377ee7fe0adfc48d41d4781e4eb9066b44638e..af662e05ec08345d53971cf08e7f2a5666e5e66f 100644
--- a/bin/nodejs/conversation.i
+++ b/bin/nodejs/conversation.i
@@ -31,6 +31,7 @@ public:
     virtual void conversationReady(const std::string& /*accountId*/, const std::string& /* conversationId */){}
     virtual void conversationRemoved(const std::string& /*accountId*/, const std::string& /* conversationId */){}
     virtual void conversationMemberEvent(const std::string& /*accountId*/, const std::string& /* conversationId */, const std::string& /* memberUri */, int /* event */){}
+    virtual void onConversationError(const std::string& /*accountId*/, const std::string& /* conversationId */, uint32_t /* code */, const std::string& /* what */){}
 };
 %}
 
@@ -79,4 +80,5 @@ public:
     virtual void conversationReady(const std::string& /*accountId*/, const std::string& /* conversationId */){}
     virtual void conversationRemoved(const std::string& /*accountId*/, const std::string& /* conversationId */){}
     virtual void conversationMemberEvent(const std::string& /*accountId*/, const std::string& /* conversationId */, const std::string& /* memberUri */, int /* event */){}
+    virtual void onConversationError(const std::string& /*accountId*/, const std::string& /* conversationId */, uint32_t /* code */, const std::string& /* what */){}
 };
\ No newline at end of file
diff --git a/src/client/ring_signal.cpp b/src/client/ring_signal.cpp
index fe6d35cd5ae83c9ac166649e1eff219423c2ce91..4397e6d48280365677073f69d16f6c3b1bf7092a 100644
--- a/src/client/ring_signal.cpp
+++ b/src/client/ring_signal.cpp
@@ -132,6 +132,7 @@ getSignalHandlers()
         exported_callback<DRing::ConversationSignal::ConversationReady>(),
         exported_callback<DRing::ConversationSignal::ConversationRemoved>(),
         exported_callback<DRing::ConversationSignal::ConversationMemberEvent>(),
+        exported_callback<DRing::ConversationSignal::OnConversationError>(),
     };
 
     return handlers;
diff --git a/src/dring/conversation_interface.h b/src/dring/conversation_interface.h
index 5bda2bed21ddac94219da4205f658fcf114ed1b7..9e33a649c2f8570cf421c2d8ac1a805e9815eb32 100644
--- a/src/dring/conversation_interface.h
+++ b/src/dring/conversation_interface.h
@@ -107,6 +107,14 @@ struct DRING_PUBLIC ConversationSignal
                              const std::string& /* memberUri */,
                              int /* event 0 = add, 1 = joins, 2 = leave, 3 = banned */);
     };
+    struct DRING_PUBLIC OnConversationError
+    {
+        constexpr static const char* name = "OnConversationError";
+        using cb_type = void(const std::string& /*accountId*/,
+                             const std::string& /* conversationId */,
+                             int code,
+                             const std::string& what);
+    };
 };
 
 } // namespace DRing
diff --git a/src/jamidht/conversation.cpp b/src/jamidht/conversation.cpp
index dae2f8a50f66784ac9ae907dc18c406c53256296..2acce647948195c49b6b37175ebff7cb4b658e87 100644
--- a/src/jamidht/conversation.cpp
+++ b/src/jamidht/conversation.cpp
@@ -63,6 +63,10 @@ public:
                                                                 remoteDevice,
                                                                 conversationId);
         if (!repository_) {
+            if (auto shared = account.lock()) {
+                emitSignal<DRing::ConversationSignal::OnConversationError>(
+                    shared->getAccountID(), conversationId, EFETCH, "Couldn't clone repository");
+            }
             throw std::logic_error("Couldn't clone repository");
         }
     }
@@ -97,12 +101,7 @@ Conversation::Impl::isAdmin() const
 
     auto adminsPath = repoPath() + DIR_SEPARATOR_STR + "admins";
     auto cert = shared->identity().second;
-    auto parentCert = cert->issuer;
-    if (!parentCert) {
-        JAMI_ERR("Parent cert is null!");
-        return false;
-    }
-    auto uri = parentCert->getId().toString();
+    auto uri = cert->getIssuerUID();
     return fileutils::isFile(fileutils::getFullPath(adminsPath, uri + ".crt"));
 }
 
@@ -123,10 +122,24 @@ Conversation::Impl::convCommitToMap(const std::vector<ConversationCommit>& commi
     for (const auto& commit : commits) {
         auto authorDevice = commit.author.email;
         auto cert = tls::CertificateStore::instance().getCertificate(authorDevice);
-        if (!cert && cert->issuer) {
-            JAMI_WARN("No author found for commit %s", commit.id.c_str());
+        if (!cert) {
+            JAMI_WARN("No author found for commit %s, reload certificates", commit.id.c_str());
+            if (repository_)
+                repository_->pinCertificates();
+            // Get certificate from repo
+            try {
+                auto certPath = fileutils::getFullPath(repoPath(), std::string("devices") + DIR_SEPARATOR_STR + authorDevice + ".crt");
+                auto deviceCert = fileutils::loadTextFile(certPath);
+                cert = std::make_shared<crypto::Certificate>(deviceCert);
+                if (!cert) {
+                    JAMI_ERR("No author found for commit %s", commit.id.c_str());
+                    continue;
+                }
+            } catch (...) {
+                continue;
+            }
         }
-        auto authorId = cert->issuer->getId().toString();
+        auto authorId = cert->getIssuerUID();
         std::string parents;
         auto parentsSize = commit.parents.size();
         for (std::size_t i = 0; i < parentsSize; ++i) {
@@ -522,15 +535,6 @@ Conversation::generateInvitation() const
     std::map<std::string, std::string> invite;
     Json::Value root;
     root["conversationId"] = id();
-    // TODO remove, cause the peer cannot trust?
-    // Or add signatures?
-    for (const auto& member : getMembers()) {
-        Json::Value jsonMember;
-        for (const auto& [key, value] : member) {
-            jsonMember[key] = value;
-        }
-        root["members"].append(jsonMember);
-    }
     // TODO metadatas
     Json::StreamWriterBuilder wbuilder;
     wbuilder["commentStyle"] = "None";
diff --git a/src/jamidht/conversationrepository.cpp b/src/jamidht/conversationrepository.cpp
index 94abeebc22a9de180d6203b0bc9c6d70bda49de1..693dd27aa06b07e4abd74aff9b11dd8a0d7a9b7c 100644
--- a/src/jamidht/conversationrepository.cpp
+++ b/src/jamidht/conversationrepository.cpp
@@ -23,6 +23,7 @@
 #include "fileutils.h"
 #include "gittransport.h"
 #include "string_utils.h"
+#include "client/ring_signal.h"
 
 using random_device = dht::crypto::random_device;
 
@@ -123,7 +124,8 @@ public:
     mutable std::mutex membersMtx_ {};
     std::vector<ConversationMember> members_ {};
 
-    std::vector<ConversationMember> members() const {
+    std::vector<ConversationMember> members() const
+    {
         std::lock_guard<std::mutex> lk(membersMtx_);
         return members_;
     }
@@ -427,7 +429,8 @@ ConversationRepository::Impl::createMergeCommit(git_index* index, const std::str
         return false;
     }
     GitAnnotatedCommit annotated {annotated_ptr, git_annotated_commit_free};
-    if (git_commit_lookup(&parent, repository_.get(), git_annotated_commit_id(annotated.get())) < 0) {
+    if (git_commit_lookup(&parent, repository_.get(), git_annotated_commit_id(annotated.get()))
+        < 0) {
         JAMI_ERR("Couldn't lookup commit %s", wanted_ref.c_str());
         return false;
     }
@@ -485,7 +488,12 @@ ConversationRepository::Impl::createMergeCommit(git_index* index, const std::str
         JAMI_INFO("New merge commit added with id: %s", commit_str);
         // Move commit to main branch
         git_reference* ref_ptr = nullptr;
-        if (git_reference_create(&ref_ptr, repository_.get(), "refs/heads/main", &commit_oid, true, nullptr)
+        if (git_reference_create(&ref_ptr,
+                                 repository_.get(),
+                                 "refs/heads/main",
+                                 &commit_oid,
+                                 true,
+                                 nullptr)
             < 0) {
             JAMI_WARN("Could not move commit to main");
         }
@@ -517,7 +525,12 @@ ConversationRepository::Impl::mergeFastforward(const git_oid* target_oid, int is
         const auto* symbolic_ref = git_reference_symbolic_target(head_ref.get());
 
         // Create our main reference on the target OID
-        if (git_reference_create(&target_ref_ptr, repository_.get(), symbolic_ref, target_oid, 0, nullptr)
+        if (git_reference_create(&target_ref_ptr,
+                                 repository_.get(),
+                                 symbolic_ref,
+                                 target_oid,
+                                 0,
+                                 nullptr)
             < 0) {
             JAMI_ERR("failed to create main reference");
             return false;
@@ -655,9 +668,9 @@ ConversationRepository::Impl::checkVote(const std::string& userDevice,
     }
 
     auto cert = tls::CertificateStore::instance().getCertificate(userDevice);
-    if (!cert && cert->issuer)
+    if (!cert)
         return false;
-    auto userUri = cert->issuer->getId().toString();
+    auto userUri = cert->getIssuerUID();
     // Check that voter is admin
     auto adminFile = std::string("admins") + DIR_SEPARATOR_STR + userUri + ".crt";
 
@@ -740,9 +753,9 @@ ConversationRepository::Impl::checkValidAdd(const std::string& userDevice,
                                             const std::string& parentId) const
 {
     auto cert = tls::CertificateStore::instance().getCertificate(userDevice);
-    if (!cert && cert->issuer)
+    if (!cert)
         return false;
-    auto userUri = cert->issuer->getId().toString();
+    auto userUri = cert->getIssuerUID();
 
     auto repo = repository();
     std::string repoPath = git_repository_workdir(repository_.get());
@@ -825,9 +838,9 @@ ConversationRepository::Impl::checkValidJoins(const std::string& userDevice,
                                               const std::string& parentId) const
 {
     auto cert = tls::CertificateStore::instance().getCertificate(userDevice);
-    if (!cert && cert->issuer)
+    if (!cert)
         return false;
-    auto userUri = cert->issuer->getId().toString();
+    auto userUri = cert->getIssuerUID();
     // Check no other files changed
     auto changedFiles = ConversationRepository::changedFiles(diffStats(commitId, parentId));
     auto oneone = mode() == ConversationMode::ONE_TO_ONE;
@@ -888,9 +901,9 @@ ConversationRepository::Impl::checkValidRemove(const std::string& userDevice,
                                                const std::string& parentId) const
 {
     auto cert = tls::CertificateStore::instance().getCertificate(userDevice);
-    if (!cert && cert->issuer)
+    if (!cert)
         return false;
-    auto userUri = cert->issuer->getId().toString();
+    auto userUri = cert->getIssuerUID();
     auto removeSelf = userUri == uriMember;
 
     // Retrieve tree for recent commit
@@ -918,7 +931,7 @@ ConversationRepository::Impl::checkValidRemove(const std::string& userDevice,
             // Ignore
         } else if (std::regex_match(f, base_match, regex_votes)) {
             if (base_match.size() != 4 or base_match[2] != uriMember) {
-                JAMI_ERR("Invalid vote file detected :%s", f.c_str());
+                JAMI_ERR("Invalid vote file detected: %s", f.c_str());
                 return false;
             }
             voters.emplace_back(base_match[3]);
@@ -950,9 +963,9 @@ ConversationRepository::Impl::checkValidRemove(const std::string& userDevice,
             return false;
         }
         cert = tls::CertificateStore::instance().getCertificate(deviceUri);
-        if (!cert && cert->issuer)
+        if (!cert)
             return false;
-        if (uriMember != cert->issuer->getId().toString()
+        if (uriMember != cert->getIssuerUID()
             and uriMember != deviceUri /* If device is removed */) {
             JAMI_ERR("device removed but not for removed user (%s)", deviceFile.c_str());
             return false;
@@ -1001,9 +1014,9 @@ ConversationRepository::Impl::isValidUserAtCommit(const std::string& userDevice,
                                                   const std::string& commitId) const
 {
     auto cert = tls::CertificateStore::instance().getCertificate(userDevice);
-    if (!cert && cert->issuer)
+    if (!cert || !cert->issuer)
         return false;
-    auto userUri = cert->issuer->getId().toString();
+    auto userUri = cert->getIssuerUID();
 
     // Retrieve tree for commit
     auto tree = treeAtCommit(commitId);
@@ -1045,9 +1058,11 @@ ConversationRepository::Impl::checkInitialCommit(const std::string& userDevice,
                                                  const std::string& commitId) const
 {
     auto cert = tls::CertificateStore::instance().getCertificate(userDevice);
-    if (!cert && cert->issuer)
+    if (!cert) {
+        JAMI_ERR("Cannot find certificate for %s", userDevice.c_str());
         return false;
-    auto userUri = cert->issuer->getId().toString();
+    }
+    auto userUri = cert->getIssuerUID();
     auto changedFiles = ConversationRepository::changedFiles(diffStats(commitId, ""));
 
     try {
@@ -1191,6 +1206,12 @@ ConversationRepository::Impl::mode() const
     auto lastMsg = log(id_, "", 1);
     if (lastMsg.size() == 0) {
         throw std::logic_error("Can't retrieve first commit");
+        if (auto shared = account_.lock()) {
+            emitSignal<DRing::ConversationSignal::OnConversationError>(shared->getAccountID(),
+                                                                       id_,
+                                                                       EINVALIDMODE,
+                                                                       "No initial commit");
+        }
     }
     auto commitMsg = lastMsg[0].commit_msg;
 
@@ -1200,9 +1221,21 @@ ConversationRepository::Impl::mode() const
     auto reader = std::unique_ptr<Json::CharReader>(rbuilder.newCharReader());
     if (!reader->parse(commitMsg.data(), commitMsg.data() + commitMsg.size(), &root, &err)) {
         throw std::logic_error("Can't retrieve first commit");
+        if (auto shared = account_.lock()) {
+            emitSignal<DRing::ConversationSignal::OnConversationError>(shared->getAccountID(),
+                                                                       id_,
+                                                                       EINVALIDMODE,
+                                                                       "No initial commit");
+        }
     }
     if (!root.isMember("mode")) {
         throw std::logic_error("No mode detected for initial commit");
+        if (auto shared = account_.lock()) {
+            emitSignal<DRing::ConversationSignal::OnConversationError>(shared->getAccountID(),
+                                                                       id_,
+                                                                       EINVALIDMODE,
+                                                                       "No mode detected");
+        }
     }
     int mode = root["mode"].asInt();
 
@@ -1220,6 +1253,12 @@ ConversationRepository::Impl::mode() const
         mode_ = ConversationMode::PUBLIC;
         break;
     default:
+        if (auto shared = account_.lock()) {
+            emitSignal<DRing::ConversationSignal::OnConversationError>(shared->getAccountID(),
+                                                                       id_,
+                                                                       EINVALIDMODE,
+                                                                       "Incorrect mode detected");
+        }
         throw std::logic_error("Incorrect mode detected");
         break;
     }
@@ -1394,7 +1433,11 @@ ConversationRepository::Impl::log(const std::string& from, const std::string& to
         cc->author = std::move(author);
         cc->parents = std::move(parents);
         git_buf signature = {}, signed_data = {};
-        if (git_commit_extract_signature(&signature, &signed_data, repository_.get(), &oid, "signature")
+        if (git_commit_extract_signature(&signature,
+                                         &signed_data,
+                                         repository_.get(),
+                                         &oid,
+                                         "signature")
             < 0) {
             JAMI_WARN("Could not extract signature for commit %s", id.c_str());
         } else {
@@ -1467,12 +1510,12 @@ ConversationRepository::Impl::getInitialMembers() const
         return {};
     }
     auto commit = firstCommit[0];
+
     auto authorDevice = commit.author.email;
     auto cert = tls::CertificateStore::instance().getCertificate(authorDevice);
-    if (!cert && cert->issuer) {
+    if (!cert)
         return {};
-    }
-    auto authorId = cert->issuer->getId().toString();
+    auto authorId = cert->getIssuerUID();
     if (mode() == ConversationMode::ONE_TO_ONE) {
         std::string err;
         Json::Value root;
@@ -1500,12 +1543,11 @@ ConversationRepository::Impl::initMembers()
     std::lock_guard<std::mutex> lk(membersMtx_);
     members_.clear();
     std::string repoPath = git_repository_workdir(repository_.get());
-    std::vector<std::string> paths = {
-        repoPath + DIR_SEPARATOR_STR + "invited",
-        repoPath + DIR_SEPARATOR_STR + "admins",
-        repoPath + DIR_SEPARATOR_STR + "members",
-        repoPath + DIR_SEPARATOR_STR + "banned" + DIR_SEPARATOR_STR + "members"
-    };
+    std::vector<std::string> paths = {repoPath + DIR_SEPARATOR_STR + "invited",
+                                      repoPath + DIR_SEPARATOR_STR + "admins",
+                                      repoPath + DIR_SEPARATOR_STR + "members",
+                                      repoPath + DIR_SEPARATOR_STR + "banned" + DIR_SEPARATOR_STR
+                                          + "members"};
     std::vector<MemberRole> roles = {
         MemberRole::INVITED,
         MemberRole::ADMIN,
@@ -1514,7 +1556,7 @@ ConversationRepository::Impl::initMembers()
     };
 
     auto i = 0;
-    for (const auto& p: paths) {
+    for (const auto& p : paths) {
         for (const auto& f : fileutils::readDirectory(p)) {
             auto pos = f.find(".crt");
             auto uri = f.substr(0, pos);
@@ -1650,6 +1692,7 @@ ConversationRepository::cloneConversation(const std::weak_ptr<JamiAccount>& acco
     }
     git_repository_free(rep);
     auto repo = std::make_unique<ConversationRepository>(account, conversationId);
+    repo->pinCertificates(); // need to load certificates to validate non known members
     if (!repo->validClone()) {
         JAMI_ERR("Error when validating remote conversation");
         return nullptr;
@@ -1670,6 +1713,10 @@ ConversationRepository::Impl::validCommits(
                 JAMI_WARN("Malformed initial commit %s. Please check you use the latest "
                           "version of Jami, or that your contact is not doing unwanted stuff.",
                           commit.id.c_str());
+                if (auto shared = account_.lock()) {
+                    emitSignal<DRing::ConversationSignal::OnConversationError>(
+                        shared->getAccountID(), id_, EVALIDFETCH, "Malformed initial commit");
+                }
                 return false;
             }
         } else if (commit.parents.size() == 1) {
@@ -1680,6 +1727,10 @@ ConversationRepository::Impl::validCommits(
                     JAMI_WARN("Malformed vote commit %s. Please check you use the latest version "
                               "of Jami, or that your contact is not doing unwanted stuff.",
                               commit.id.c_str());
+                    if (auto shared = account_.lock()) {
+                        emitSignal<DRing::ConversationSignal::OnConversationError>(
+                            shared->getAccountID(), id_, EVALIDFETCH, "Malformed vote");
+                    }
                     return false;
                 }
             } else if (type == "member") {
@@ -1692,6 +1743,10 @@ ConversationRepository::Impl::validCommits(
                                    &root,
                                    &err)) {
                     JAMI_ERR() << "Failed to parse " << err;
+                    if (auto shared = account_.lock()) {
+                        emitSignal<DRing::ConversationSignal::OnConversationError>(
+                            shared->getAccountID(), id_, EVALIDFETCH, "Malformed member commit");
+                    }
                     return false;
                 }
                 std::string action = root["action"].asString();
@@ -1702,6 +1757,13 @@ ConversationRepository::Impl::validCommits(
                             "Malformed add commit %s. Please check you use the latest version "
                             "of Jami, or that your contact is not doing unwanted stuff.",
                             commit.id.c_str());
+                        if (auto shared = account_.lock()) {
+                            emitSignal<DRing::ConversationSignal::OnConversationError>(
+                                shared->getAccountID(),
+                                id_,
+                                EVALIDFETCH,
+                                "Malformed add member commit");
+                        }
                         return false;
                     }
                 } else if (action == "join") {
@@ -1710,6 +1772,13 @@ ConversationRepository::Impl::validCommits(
                             "Malformed joins commit %s. Please check you use the latest version "
                             "of Jami, or that your contact is not doing unwanted stuff.",
                             commit.id.c_str());
+                        if (auto shared = account_.lock()) {
+                            emitSignal<DRing::ConversationSignal::OnConversationError>(
+                                shared->getAccountID(),
+                                id_,
+                                EVALIDFETCH,
+                                "Malformed join member commit");
+                        }
                         return false;
                     }
                 } else if (action == "remove") {
@@ -1721,6 +1790,13 @@ ConversationRepository::Impl::validCommits(
                             "Malformed removes commit %s. Please check you use the latest version "
                             "of Jami, or that your contact is not doing unwanted stuff.",
                             commit.id.c_str());
+                        if (auto shared = account_.lock()) {
+                            emitSignal<DRing::ConversationSignal::OnConversationError>(
+                                shared->getAccountID(),
+                                id_,
+                                EVALIDFETCH,
+                                "Malformed remove member commit");
+                        }
                         return false;
                     }
                 } else if (action == "ban") {
@@ -1730,6 +1806,13 @@ ConversationRepository::Impl::validCommits(
                             "Malformed removes commit %s. Please check you use the latest version "
                             "of Jami, or that your contact is not doing unwanted stuff.",
                             commit.id.c_str());
+                        if (auto shared = account_.lock()) {
+                            emitSignal<DRing::ConversationSignal::OnConversationError>(
+                                shared->getAccountID(),
+                                id_,
+                                EVALIDFETCH,
+                                "Malformed ban member commit");
+                        }
                         return false;
                     }
                 } else {
@@ -1738,6 +1821,10 @@ ConversationRepository::Impl::validCommits(
                               "version of Jami, or that your contact is not doing unwanted stuff.",
                               commit.id.c_str(),
                               action.c_str());
+                    if (auto shared = account_.lock()) {
+                        emitSignal<DRing::ConversationSignal::OnConversationError>(
+                            shared->getAccountID(), id_, EVALIDFETCH, "Malformed member commit");
+                    }
                     return false;
                 }
             } else {
@@ -1749,6 +1836,10 @@ ConversationRepository::Impl::validCommits(
                               "version of Jami, or that your contact is not doing unwanted stuff.",
                               type.c_str(),
                               commit.id.c_str());
+                    if (auto shared = account_.lock()) {
+                        emitSignal<DRing::ConversationSignal::OnConversationError>(
+                            shared->getAccountID(), id_, EVALIDFETCH, "Malformed commit");
+                    }
                     return false;
                 }
             }
@@ -1762,6 +1853,12 @@ ConversationRepository::Impl::validCommits(
                     "that your contact is not doing unwanted stuff. %s",
                     validUserAtCommit.c_str(),
                     commit.commit_msg.c_str());
+                if (auto shared = account_.lock()) {
+                    emitSignal<DRing::ConversationSignal::OnConversationError>(shared->getAccountID(),
+                                                                               id_,
+                                                                               EVALIDFETCH,
+                                                                               "Malformed commit");
+                }
                 return false;
             }
         } else {
@@ -1874,7 +1971,12 @@ ConversationRepository::amend(const std::string& id, const std::string& msg)
 
     // Move commit to main branch
     git_reference* ref_ptr = nullptr;
-    if (git_reference_create(&ref_ptr, pimpl_->repository_.get(), "refs/heads/main", &commit_id, true, nullptr)
+    if (git_reference_create(&ref_ptr,
+                             pimpl_->repository_.get(),
+                             "refs/heads/main",
+                             &commit_id,
+                             true,
+                             nullptr)
         < 0) {
         JAMI_WARN("Could not move commit to main");
     }
@@ -1910,7 +2012,10 @@ ConversationRepository::fetch(const std::string& remoteDeviceId)
             JAMI_ERR("Couldn't lookup for remote %s", remoteDeviceId.c_str());
             return false;
         }
-        if (git_remote_create(&remote_ptr, pimpl_->repository_.get(), remoteDeviceId.c_str(), channelName.c_str())
+        if (git_remote_create(&remote_ptr,
+                              pimpl_->repository_.get(),
+                              remoteDeviceId.c_str(),
+                              channelName.c_str())
             < 0) {
             JAMI_ERR("Could not create remote for repository for conversation %s",
                      pimpl_->id_.c_str());
@@ -1936,6 +2041,13 @@ ConversationRepository::fetch(const std::string& remoteDeviceId)
             JAMI_ERR("Could not fetch remote repository for conversation %s: %s",
                      pimpl_->id_.c_str(),
                      err->message);
+
+            if (auto shared = pimpl_->account_.lock()) {
+                emitSignal<DRing::ConversationSignal::OnConversationError>(shared->getAccountID(),
+                                                                           pimpl_->id_,
+                                                                           EFETCH,
+                                                                           err->message);
+            }
         }
         return false;
     }
@@ -2054,7 +2166,8 @@ ConversationRepository::merge(const std::string& merge_id)
     git_merge_analysis_t analysis;
     git_merge_preference_t preference;
     const git_annotated_commit* const_annotated = annotated.get();
-    if (git_merge_analysis(&analysis, &preference, pimpl_->repository_.get(), &const_annotated, 1) < 0) {
+    if (git_merge_analysis(&analysis, &preference, pimpl_->repository_.get(), &const_annotated, 1)
+        < 0) {
         JAMI_ERR("Merge operation aborted: repository analysis failed");
         return false;
     }
@@ -2091,7 +2204,8 @@ ConversationRepository::merge(const std::string& merge_id)
             return false;
         }
 
-        if (git_merge(pimpl_->repository_.get(), &const_annotated, 1, &merge_opts, &checkout_opts) < 0) {
+        if (git_merge(pimpl_->repository_.get(), &const_annotated, 1, &merge_opts, &checkout_opts)
+            < 0) {
             const git_error* err = giterr_last();
             if (err)
                 JAMI_ERR("Git merge failed: %s", err->message);
@@ -2199,7 +2313,6 @@ ConversationRepository::join()
             pimpl_->members_.emplace_back(ConversationMember {uri, MemberRole::MEMBER});
     }
 
-
     return commitMessage(Json::writeString(wbuilder, json));
 }
 
@@ -2274,7 +2387,9 @@ ConversationRepository::leave()
 
     {
         std::lock_guard<std::mutex> lk(pimpl_->membersMtx_);
-        std::remove_if(pimpl_->members_.begin(), pimpl_->members_.end(), [&](auto& member) { return member.uri == account->getUsername(); });
+        std::remove_if(pimpl_->members_.begin(), pimpl_->members_.end(), [&](auto& member) {
+            return member.uri == account->getUsername();
+        });
     }
 
     return commitMessage(Json::writeString(wbuilder, json));
@@ -2306,12 +2421,7 @@ ConversationRepository::voteKick(const std::string& uri, bool isDevice)
     if (!account)
         return {};
     auto cert = account->identity().second;
-    auto parentCert = cert->issuer;
-    if (!parentCert) {
-        JAMI_ERR("Parent cert is null!");
-        return {};
-    }
-    auto adminUri = parentCert->getId().toString();
+    auto adminUri = cert->getIssuerUID();
     if (adminUri == uri) {
         JAMI_WARN("Admin tried to ban theirself");
         return {};
@@ -2491,4 +2601,24 @@ ConversationRepository::refreshMembers() const
     return pimpl_->initMembers();
 }
 
+void
+ConversationRepository::pinCertificates()
+{
+    auto repo = pimpl_->repository();
+    if (!repo)
+        return;
+
+    std::string repoPath = git_repository_workdir(repo.get());
+    std::vector<std::string> paths = {repoPath + "admins",
+                                      repoPath + "members",
+                                      repoPath + "devices"};
+
+    for (const auto& path : paths) {
+        tls::CertificateStore::instance().pinCertificatePath(path, [](auto& ids) {
+            for (const auto& id : ids)
+                JAMI_ERR("@@@ LOADED %s", id.c_str());
+        });
+    }
+}
+
 } // namespace jami
diff --git a/src/jamidht/conversationrepository.h b/src/jamidht/conversationrepository.h
index b4a104f199a625f170cf04037da3b64b9a5be8eb..f6c2752c5a60d9f8f80e36d5ce308ab485e22f67 100644
--- a/src/jamidht/conversationrepository.h
+++ b/src/jamidht/conversationrepository.h
@@ -45,6 +45,10 @@ using GitIndexConflictIterator
 
 namespace jami {
 
+constexpr auto EFETCH = 1;
+constexpr auto EINVALIDMODE = 2;
+constexpr auto EVALIDFETCH = 3;
+
 class JamiAccount;
 class ChannelSocket;
 
@@ -255,6 +259,13 @@ public:
      */
     void refreshMembers() const;
 
+
+    /**
+     * Because conversations can contains non contacts certificates, this methods
+     * loads certificates in conversations into the cert store
+     */
+    void pinCertificates();
+
 private:
     ConversationRepository() = delete;
     class Impl;
diff --git a/src/jamidht/jamiaccount.cpp b/src/jamidht/jamiaccount.cpp
index 9b56f404345c9a456af4fc17c6226c5d52a453f4..abf36690e9349cc25764804c5019a3a582b2f378 100644
--- a/src/jamidht/jamiaccount.cpp
+++ b/src/jamidht/jamiaccount.cpp
@@ -1215,34 +1215,29 @@ JamiAccount::loadAccount(const std::string& archive_password,
                const std::string& conversationId,
                const std::vector<uint8_t>& payload,
                time_t received) {
-            dht::ThreadPool::computation().run([w = weak(),
-                                                uri,
-                                                payload = std::move(payload),
-                                                received,
-                                                conversationId] {
-                if (auto acc = w.lock()) {
-                    if (!conversationId.empty()) {
-                        std::lock_guard<std::mutex> lk(acc->conversationsRequestsMtx_);
-                        auto it = acc->conversationsRequests_.find(conversationId);
-                        if (it != acc->conversationsRequests_.end()) {
-                            JAMI_INFO(
-                                "[Account %s] Received a request for a conversation already existing. "
-                                "Ignore",
-                                acc->getAccountID().c_str());
-                            return;
+            dht::ThreadPool::computation().run(
+                [w = weak(), uri, payload = std::move(payload), received, conversationId] {
+                    if (auto acc = w.lock()) {
+                        if (!conversationId.empty()) {
+                            std::lock_guard<std::mutex> lk(acc->conversationsRequestsMtx_);
+                            auto it = acc->conversationsRequests_.find(conversationId);
+                            if (it != acc->conversationsRequests_.end()) {
+                                JAMI_INFO("[Account %s] Received a request for a conversation "
+                                          "already existing. "
+                                          "Ignore",
+                                          acc->getAccountID().c_str());
+                                return;
+                            }
+                            ConversationRequest req;
+                            req.from = uri;
+                            req.conversationId = conversationId;
+                            req.received = std::time(nullptr);
+                            acc->conversationsRequests_[conversationId] = std::move(req);
                         }
-                        ConversationRequest req;
-                        req.from = uri;
-                        req.conversationId = conversationId;
-                        req.received = std::time(nullptr);
-                        acc->conversationsRequests_[conversationId] = std::move(req);
+                        emitSignal<DRing::ConfigurationSignal::IncomingTrustRequest>(
+                            acc->getAccountID(), uri, payload, received);
                     }
-                    emitSignal<DRing::ConfigurationSignal::IncomingTrustRequest>(acc->getAccountID(),
-                                                                                 uri,
-                                                                                 payload,
-                                                                                 received);
-                }
-            });
+                });
         },
         [this](const std::map<dht::InfoHash, KnownDevice>& devices) {
             std::map<std::string, std::string> ids;
@@ -2209,11 +2204,15 @@ JamiAccount::onTrackedBuddyOnline(const dht::InfoHash& contactId)
         auto convId = getOneToOneConversation(id);
         if (convId.empty())
             return;
-        // In this case, the TrustRequest was sent but never confirmed (cause the contact was offline maybe)
-        // To avoid the contact to never receive the conv request, retry there
+        // In this case, the TrustRequest was sent but never confirmed (cause the contact was
+        // offline maybe) To avoid the contact to never receive the conv request, retry there
         std::lock_guard<std::mutex> lock(configurationMutex_);
         if (accountManager_)
-            accountManager_->sendTrustRequest(id, convId, {}); /* TODO payload?, MessageEngine not generic and will be able to move to conversation's requests */
+            accountManager_
+                ->sendTrustRequest(id,
+                                   convId,
+                                   {}); /* TODO payload?, MessageEngine not generic and will be able
+                                           to move to conversation's requests */
     }
 }
 
@@ -2469,11 +2468,11 @@ JamiAccount::doRegister_()
                     ->findCertificate(deviceId,
                                       [this, &accept](
                                           const std::shared_ptr<dht::crypto::Certificate>& cert) {
-                                          if (not cert or not cert->issuer) {
+                                          if (not cert) {
                                               accept.set_value(false);
                                               return;
                                           }
-                                          accept.set_value(cert->issuer->getId().toString()
+                                          accept.set_value(cert->getIssuerUID()
                                                            == accountManager_->getInfo()->accountId);
                                       });
                 fut.wait();
@@ -2549,21 +2548,20 @@ JamiAccount::doRegister_()
                         auto conversation = conversations_.find(conversationId);
                         if (conversation == conversations_.end()) {
                             JAMI_WARN("[Account %s] Git server requested, but for a non existing "
-                                    "conversation (%s)",
-                                    getAccountID().c_str(),
-                                    conversationId.c_str());
+                                      "conversation (%s)",
+                                      getAccountID().c_str(),
+                                      conversationId.c_str());
                             return;
                         }
                         if (conversation->second->isBanned(remoteDevice)) {
                             JAMI_WARN("[Account %s] %s is a banned device in conversation %s",
-                                    getAccountID().c_str(),
-                                    remoteDevice.c_str(),
-                                    conversationId.c_str());
+                                      getAccountID().c_str(),
+                                      remoteDevice.c_str(),
+                                      conversationId.c_str());
                             return;
                         }
                     }
 
-
                     if (gitSocket(deviceId.toString(), conversationId) == channel) {
                         // The onConnectionReady is already used as client (for retrieving messages)
                         // So it's not the server socket
@@ -3312,7 +3310,8 @@ JamiAccount::removeContact(const std::string& uri, bool ban)
                 if (conv->mode() == ConversationMode::ONE_TO_ONE) {
                     auto initMembers = conv->getInitialMembers();
                     if ((isSelf && initMembers.size() == 1)
-                        || std::find(initMembers.begin(), initMembers.end(), uri) != initMembers.end())
+                        || std::find(initMembers.begin(), initMembers.end(), uri)
+                               != initMembers.end())
                         toRm.emplace_back(key);
                 }
             } catch (const std::exception& e) {
@@ -3738,8 +3737,8 @@ JamiAccount::requestPeerConnection(
                                          isVCard,
                                          channeledConnectedCb,
                                          onChanneledCancelled);
-    //auto convId = getOneToOneConversation(info.peer);
-    //if (!convId.empty()) {
+    // auto convId = getOneToOneConversation(info.peer);
+    // if (!convId.empty()) {
     //    Json::Value value;
     //    value["tid"] = std::to_string(tid);
     //    value["type"] = "application/data-transfer+json";
@@ -3938,7 +3937,6 @@ JamiAccount::acceptConversationRequest(const std::string& conversationId)
                         lk.unlock();
                         // Save the git socket
                         addGitSocket(dev.toString(), request.conversationId, socket);
-                        // TODO when do we remove the gitSocket?
                     } else {
                         lk.unlock();
                         socket->shutdown();
@@ -3979,8 +3977,8 @@ JamiAccount::handlePendingConversations()
     for (auto it = pendingConversationsFetch_.begin(); it != pendingConversationsFetch_.end();) {
         if (it->second.ready) {
             // Clone and store conversation
+            auto conversationId = it->first;
             try {
-                auto conversationId = it->first;
                 auto conversation = std::make_shared<Conversation>(weak(),
                                                                    it->second.deviceId,
                                                                    conversationId);
@@ -4011,6 +4009,10 @@ JamiAccount::handlePendingConversations()
                                                                              conversationId);
                 }
             } catch (const std::exception& e) {
+                emitSignal<DRing::ConversationSignal::OnConversationError>(getAccountID(),
+                                                                           conversationId,
+                                                                           EFETCH,
+                                                                           e.what());
                 JAMI_WARN("Something went wrong when cloning conversation: %s", e.what());
             }
             it = pendingConversationsFetch_.erase(it);
@@ -4134,7 +4136,9 @@ JamiAccount::addConversationMember(const std::string& conversationId,
     }
 
     if (it->second->isMember(contactUri, true)) {
-        JAMI_DBG("%s is already a member of %s, resend invite", contactUri.c_str(), conversationId.c_str());
+        JAMI_DBG("%s is already a member of %s, resend invite",
+                 contactUri.c_str(),
+                 conversationId.c_str());
         // Note: This should not be necessary, but if for whatever reason the other side didn't join
         // we should not forbid new invites
         sendTextMessage(contactUri, it->second->generateInvitation());
@@ -4146,24 +4150,29 @@ JamiAccount::addConversationMember(const std::string& conversationId,
         JAMI_WARN("Couldn't add %s to %s", contactUri.c_str(), conversationId.c_str());
         return false;
     }
-    it->second->loadMessages([w=weak(), conversationId, sendRequest, contactUri, commitId](auto&& messages) {
-        auto shared = w.lock();
-        if (not shared or messages.empty())
-            return; // should not happen
-        std::lock_guard<std::mutex> lk(shared->conversationsMtx_);
-        // Add a new member in the conversation
-        auto it = shared->conversations_.find(conversationId);
-        if (it == shared->conversations_.end()) {
-            return;
-        }
-        auto message = messages.front();
-        if (message.at("type") == "member")
-            shared->announceMemberMessage(conversationId, message);
-        emitSignal<DRing::ConversationSignal::MessageReceived>(shared->getAccountID(), conversationId, message);
-        if (sendRequest)
-            shared->sendTextMessage(contactUri, it->second->generateInvitation());
-        shared->sendMessageNotification(*it->second, commitId, true);
-    }, commitId, 1);
+    it->second->loadMessages(
+        [w = weak(), conversationId, sendRequest, contactUri, commitId](auto&& messages) {
+            auto shared = w.lock();
+            if (not shared or messages.empty())
+                return; // should not happen
+            std::lock_guard<std::mutex> lk(shared->conversationsMtx_);
+            // Add a new member in the conversation
+            auto it = shared->conversations_.find(conversationId);
+            if (it == shared->conversations_.end()) {
+                return;
+            }
+            auto message = messages.front();
+            if (message.at("type") == "member")
+                shared->announceMemberMessage(conversationId, message);
+            emitSignal<DRing::ConversationSignal::MessageReceived>(shared->getAccountID(),
+                                                                   conversationId,
+                                                                   message);
+            if (sendRequest)
+                shared->sendTextMessage(contactUri, it->second->generateInvitation());
+            shared->sendMessageNotification(*it->second, commitId, true);
+        },
+        commitId,
+        1);
     return true;
 }
 
@@ -4177,24 +4186,30 @@ JamiAccount::removeConversationMember(const std::string& conversationId,
     if (conversation != conversations_.end() && conversation->second) {
         auto lastCommit = conversation->second->lastCommitId();
         if (conversation->second->removeMember(contactUri, isDevice)) {
-            conversation->second->loadMessages([w=weak(), lastCommit, conversationId] (auto&& messages) {
-                if (auto shared = w.lock()) {
-                    std::reverse(messages.begin(), messages.end());
-                    // Announce new commits
-                    for (const auto& msg : messages) {
-                        if (msg.at("type") == "member")
-                            shared->announceMemberMessage(conversationId, msg);
-                        emitSignal<DRing::ConversationSignal::MessageReceived>(shared->getAccountID(),
-                                                                                conversationId,
-                                                                                msg);
+            conversation->second->loadMessages(
+                [w = weak(), conversationId](auto&& messages) {
+                    if (auto shared = w.lock()) {
+                        std::reverse(messages.begin(), messages.end());
+                        // Announce new commits
+                        auto lastId = std::string();
+                        for (const auto& msg : messages) {
+                            if (msg.at("type") == "member")
+                                shared->announceMemberMessage(conversationId, msg);
+                            emitSignal<DRing::ConversationSignal::MessageReceived>(
+                                shared->getAccountID(), conversationId, msg);
+                        }
+                        // Get last id
+                        if (messages.size() > 0)
+                            lastId = messages.rbegin()->at("id");
+                        std::lock_guard<std::mutex> lk(shared->conversationsMtx_);
+                        auto conversation = shared->conversations_.find(conversationId);
+                        // Send notification for others
+                        if (conversation != shared->conversations_.end() && conversation->second)
+                            shared->sendMessageNotification(*conversation->second, lastId, true);
                     }
-                    std::lock_guard<std::mutex> lk(shared->conversationsMtx_);
-                    auto conversation = shared->conversations_.find(conversationId);
-                    // Send notification for others
-                    if (conversation != shared->conversations_.end() && conversation->second)
-                        shared->sendMessageNotification(*conversation->second, lastCommit, true);
-                }
-            }, "", lastCommit);
+                },
+                "",
+                lastCommit);
             return true;
         }
     }
@@ -4238,13 +4253,16 @@ JamiAccount::sendMessage(const std::string& conversationId,
         if (!announce)
             return;
         if (!commitId.empty()) {
-            conversation->second->loadMessages([w=weak(), conversationId] (auto&& messages) {
-                auto shared = w.lock();
-                if (shared && !messages.empty())
-                    emitSignal<DRing::ConversationSignal::MessageReceived>(shared->getAccountID(),
-                                                                        conversationId,
-                                                                        messages.front());
-            }, commitId, 1);
+            conversation->second->loadMessages(
+                [w = weak(), conversationId](auto&& messages) {
+                    auto shared = w.lock();
+                    if (shared && !messages.empty())
+                        emitSignal<DRing::ConversationSignal::MessageReceived>(shared->getAccountID(),
+                                                                               conversationId,
+                                                                               messages.front());
+                },
+                commitId,
+                1);
             sendMessageNotification(*conversation->second, commitId, true);
         } else {
             JAMI_ERR("Failed to send message to conversation %s", conversationId.c_str());
@@ -4263,14 +4281,17 @@ JamiAccount::loadConversationMessages(const std::string& conversationId,
     std::lock_guard<std::mutex> lk(conversationsMtx_);
     auto conversation = conversations_.find(conversationId);
     if (conversation != conversations_.end() && conversation->second) {
-        conversation->second->loadMessages([w=weak(), conversationId, id](auto&& messages) {
-            if (auto shared = w.lock()) {
-                emitSignal<DRing::ConversationSignal::ConversationLoaded>(id,
-                                                                            shared->getAccountID(),
-                                                                            conversationId,
-                                                                            messages);
-            }
-        }, fromMessage, n);
+        conversation->second->loadMessages(
+            [w = weak(), conversationId, id](auto&& messages) {
+                if (auto shared = w.lock()) {
+                    emitSignal<DRing::ConversationSignal::ConversationLoaded>(id,
+                                                                              shared->getAccountID(),
+                                                                              conversationId,
+                                                                              messages);
+                }
+            },
+            fromMessage,
+            n);
     }
     return id;
 }
@@ -4352,25 +4373,26 @@ JamiAccount::fetchNewCommits(const std::string& peer,
         };
 
         if (gitSocket(deviceId, conversationId)) {
-            conversation->second
-                ->pull(deviceId,
-                       [deviceId,
-                        conversationId,
-                        w = weak(),
-                        announceMessages = std::move(announceMessages)](bool ok, auto messages) {
-                           auto shared = w.lock();
-                           if (!shared)
-                               return;
-                           if (!ok) {
-                               JAMI_WARN("[Account %s] Could not fetch new commit from %s for %s",
-                                         shared->getAccountID().c_str(),
-                                         deviceId.c_str(),
-                                         conversationId.c_str());
-                               shared->removeGitSocket(deviceId, conversationId);
-                           }
-                           if (!messages.empty())
-                               announceMessages(messages);
-                       }, commitId);
+            conversation->second->pull(
+                deviceId,
+                [deviceId,
+                 conversationId,
+                 w = weak(),
+                 announceMessages = std::move(announceMessages)](bool ok, auto messages) {
+                    auto shared = w.lock();
+                    if (!shared)
+                        return;
+                    if (!ok) {
+                        JAMI_WARN("[Account %s] Could not fetch new commit from %s for %s",
+                                  shared->getAccountID().c_str(),
+                                  deviceId.c_str(),
+                                  conversationId.c_str());
+                        shared->removeGitSocket(deviceId, conversationId);
+                    }
+                    if (!messages.empty())
+                        announceMessages(messages);
+                },
+                commitId);
         } else {
             lk.unlock();
             // Else we need to add a new gitSocket
@@ -4384,12 +4406,8 @@ JamiAccount::fetchNewCommits(const std::string& peer,
             connectionManager_->connectDevice(
                 DeviceId(deviceId),
                 "git://" + deviceId + "/" + conversationId,
-                [this,
-                 conversationId,
-                 commitId,
-                 announceMessages = std::move(
-                     announceMessages)](std::shared_ptr<ChannelSocket> socket,
-                                        const DeviceId& deviceId) {
+                [this, conversationId, commitId, announceMessages = std::move(announceMessages)](
+                    std::shared_ptr<ChannelSocket> socket, const DeviceId& deviceId) {
                     dht::ThreadPool::io().run([w = weak(),
                                                conversationId,
                                                socket = std::move(socket),
@@ -4406,28 +4424,28 @@ JamiAccount::fetchNewCommits(const std::string& peer,
                         if (socket) {
                             shared->addGitSocket(deviceId.toString(), conversationId, socket);
 
-                            conversation->second
-                                ->pull(deviceId.toString(),
-                                       [deviceId,
-                                        conversationId,
-                                        w,
-                                        announceMessages = std::move(
-                                            announceMessages)](bool ok, auto messages) {
-                                           auto shared = w.lock();
-                                           if (!shared)
-                                               return;
-                                           if (!ok) {
-                                               JAMI_WARN("[Account %s] Could not fetch new commit "
-                                                         "from %s for %s",
-                                                         shared->getAccountID().c_str(),
-                                                         deviceId.to_c_str(),
-                                                         conversationId.c_str());
-                                               shared->removeGitSocket(deviceId.toString(),
-                                                                       conversationId);
-                                           }
-                                           if (!messages.empty())
-                                               announceMessages(messages);
-                                       }, commitId);
+                            conversation->second->pull(
+                                deviceId.toString(),
+                                [deviceId,
+                                 conversationId,
+                                 w,
+                                 announceMessages = std::move(announceMessages)](bool ok,
+                                                                                 auto messages) {
+                                    auto shared = w.lock();
+                                    if (!shared)
+                                        return;
+                                    if (!ok) {
+                                        JAMI_WARN("[Account %s] Could not fetch new commit "
+                                                  "from %s for %s",
+                                                  shared->getAccountID().c_str(),
+                                                  deviceId.to_c_str(),
+                                                  conversationId.c_str());
+                                        shared->removeGitSocket(deviceId.toString(), conversationId);
+                                    }
+                                    if (!messages.empty())
+                                        announceMessages(messages);
+                                },
+                                commitId);
                         } else {
                             JAMI_ERR("[Account %s] Couldn't open a new git channel with %s for "
                                      "conversation %s",
@@ -4948,7 +4966,6 @@ JamiAccount::cacheSyncConnection(std::shared_ptr<ChannelSocket>&& socket,
                                         // Save the git socket
                                         addGitSocket(deviceId.toString(), convId, socket);
                                         checkConversationsEvents();
-                                        // TODO when do we remove the gitSocket?
                                     } else {
                                         lk.unlock();
                                         socket->shutdown();
@@ -4971,7 +4988,8 @@ JamiAccount::cacheSyncConnection(std::shared_ptr<ChannelSocket>&& socket,
                         std::lock_guard<std::mutex> lk(conversationsMtx_);
                         auto itConv = conversations_.find(convId);
                         if (itConv != conversations_.end() && !itConv->second->isRemoving()) {
-                            emitSignal<DRing::ConversationSignal::ConversationRemoved>(accountID_, convId);
+                            emitSignal<DRing::ConversationSignal::ConversationRemoved>(accountID_,
+                                                                                       convId);
                             itConv->second->setRemovingFlag();
                         }
                     }
@@ -5255,6 +5273,7 @@ JamiAccount::addCallHistoryMessage(const std::string& uri, uint64_t duration_ms)
 void
 JamiAccount::monitor() const
 {
+    std::lock_guard<std::mutex> lkCM(connManagerMtx_);
     if (connectionManager_)
         connectionManager_->monitor();
 }
diff --git a/test/unitTest/conversation/conversation.cpp b/test/unitTest/conversation/conversation.cpp
index f9345749904ca6617d7b307724173fe2cc29dfc3..36d7b77f9e0afb7ee6f3d8fa05200d727d533391 100644
--- a/test/unitTest/conversation/conversation.cpp
+++ b/test/unitTest/conversation/conversation.cpp
@@ -26,6 +26,7 @@
 #include <streambuf>
 #include <git2.h>
 #include <filesystem>
+#include <msgpack.hpp>
 
 #include "manager.h"
 #include "jamidht/conversation.h"
@@ -40,6 +41,16 @@
 using namespace std::string_literals;
 using namespace DRing::Account;
 
+struct ConvInfoTest
+{
+    std::string id {};
+    time_t created {0};
+    time_t removed {0};
+    time_t erased {0};
+
+    MSGPACK_DEFINE_MAP(id, created, removed, erased)
+};
+
 namespace jami {
 namespace test {
 
@@ -50,9 +61,13 @@ public:
     static std::string name() { return "Conversation"; }
     void setUp();
     void tearDown();
-    void generateFakeVote(std::shared_ptr<JamiAccount> account,
-                          const std::string& convId,
-                          const std::string& votedUri);
+    void addVote(std::shared_ptr<JamiAccount> account,
+                 const std::string& convId,
+                 const std::string& votedUri,
+                 const std::string& content);
+    void simulateRemoval(std::shared_ptr<JamiAccount> account,
+                         const std::string& convId,
+                         const std::string& votedUri);
     void generateFakeInvite(std::shared_ptr<JamiAccount> account,
                             const std::string& convId,
                             const std::string& uri);
@@ -64,6 +79,10 @@ public:
     void commit(std::shared_ptr<JamiAccount> account,
                 const std::string& convId,
                 Json::Value& message);
+    std::string commitInRepo(const std::string& repoPath,
+                             std::shared_ptr<JamiAccount> account,
+                             const std::string& message);
+    std::string createFakeConversation(std::shared_ptr<JamiAccount> account);
 
     std::string aliceId;
     std::string bobId;
@@ -78,6 +97,7 @@ private:
     void testRemoveConversationNoMember();
     void testRemoveConversationWithMember();
     void testAddMember();
+    void testMemberAddedNoBadFile();
     void testAddOfflineMemberThenConnects();
     void testGetMembers();
     void testSendMessage();
@@ -88,33 +108,31 @@ private:
     void testSendMessageToMultipleParticipants();
     void testPingPongMessages();
     void testRemoveMember();
-    // void testBanDevice();
+    void testMemberBanNoBadFile();
     void testMemberTryToRemoveAdmin();
     void testBannedMemberCannotSendMessage();
     void testAddBannedMember();
     void testMemberCannotBanOther();
     void testCheckAdminFakeAVoteIsDetected();
+    void testVoteNonEmpty();
     void testAdminCannotKickTheirself();
-    // TODO void testBannedDeviceCannotSendMessageButMemberCan();
-    // TODO void testRevokedDeviceCannotSendMessage();
+    void testCommitUnauthorizedUser();
+    // LATER void testBanDevice();
+    // LATER void testBannedDeviceCannotSendMessageButMemberCan();
+    // LATER void testRevokedDeviceCannotSendMessage();
     // LATER void test2AdminsCannotBanEachOthers();
     // LATER void test2AdminsBanMembers();
     // LATER void test2AdminsBanOtherAdmin();
     // LATER void testAdminRemoveConversationShouldPromoteOther();
 
-    // TODO2 testCommitUnauthorizedUser
-    // TODO2 testNoBadFileInInitialCommit
+    void testNoBadFileInInitialCommit();
     void testPlainTextNoBadFile();
-    // TODO2 testVoteFromNonAdmin
     void testVoteNoBadFile();
     void testETooBigClone();
     void testETooBigFetch();
-    // TODO2 testVoteNonEmpty
-    // TODO2 testMemberAddedNoCertificate
-    // TODO2 void testMemberAddedNoBadFile();
-    // TODO2 void testMemberJoinsNoBadFile();
-    // TODO2 testMemberJoinsInviteRemoved
-    // TODO2 testMemberBanNoBadFile
+    void testMemberJoinsNoBadFile();
+    void testMemberAddedNoCertificate();
+    void testMemberJoinsInviteRemoved();
     void testAddContact();
     void testAddContactDeleteAndReAdd();
     void testFailAddMemberInOneToOne();
@@ -134,6 +152,7 @@ private:
     CPPUNIT_TEST(testRemoveConversationNoMember);
     CPPUNIT_TEST(testRemoveConversationWithMember);
     CPPUNIT_TEST(testAddMember);
+    CPPUNIT_TEST(testMemberAddedNoBadFile);
     CPPUNIT_TEST(testAddOfflineMemberThenConnects);
     CPPUNIT_TEST(testGetMembers);
     CPPUNIT_TEST(testSendMessage);
@@ -143,16 +162,23 @@ private:
     CPPUNIT_TEST(testSendMessageToMultipleParticipants);
     CPPUNIT_TEST(testPingPongMessages);
     CPPUNIT_TEST(testRemoveMember);
+    CPPUNIT_TEST(testMemberBanNoBadFile);
     CPPUNIT_TEST(testMemberTryToRemoveAdmin);
     CPPUNIT_TEST(testBannedMemberCannotSendMessage);
     CPPUNIT_TEST(testAddBannedMember);
     CPPUNIT_TEST(testMemberCannotBanOther);
     CPPUNIT_TEST(testCheckAdminFakeAVoteIsDetected);
+    CPPUNIT_TEST(testVoteNonEmpty);
     CPPUNIT_TEST(testAdminCannotKickTheirself);
+    CPPUNIT_TEST(testCommitUnauthorizedUser);
+    CPPUNIT_TEST(testNoBadFileInInitialCommit);
     CPPUNIT_TEST(testPlainTextNoBadFile);
     CPPUNIT_TEST(testVoteNoBadFile);
     CPPUNIT_TEST(testETooBigClone);
     CPPUNIT_TEST(testETooBigFetch);
+    CPPUNIT_TEST(testMemberJoinsNoBadFile);
+    CPPUNIT_TEST(testMemberAddedNoCertificate);
+    CPPUNIT_TEST(testMemberJoinsInviteRemoved);
     CPPUNIT_TEST(testAddContact);
     CPPUNIT_TEST(testAddContactDeleteAndReAdd);
     CPPUNIT_TEST(testFailAddMemberInOneToOne);
@@ -307,7 +333,6 @@ void
 ConversationTest::testGetConversation()
 {
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
-    auto aliceDeviceId = aliceAccount->currentDeviceId();
     auto uri = aliceAccount->getUsername();
     auto convId = aliceAccount->startConversation();
 
@@ -320,7 +345,6 @@ void
 ConversationTest::testGetConversationsAfterRm()
 {
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
-    auto aliceDeviceId = aliceAccount->currentDeviceId();
     auto uri = aliceAccount->getUsername();
 
     std::mutex mtx;
@@ -352,7 +376,6 @@ void
 ConversationTest::testRemoveInvalidConversation()
 {
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
-    auto aliceDeviceId = aliceAccount->currentDeviceId();
     auto uri = aliceAccount->getUsername();
 
     std::mutex mtx;
@@ -384,7 +407,6 @@ void
 ConversationTest::testRemoveConversationNoMember()
 {
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
-    auto aliceDeviceId = aliceAccount->currentDeviceId();
     auto uri = aliceAccount->getUsername();
 
     std::mutex mtx;
@@ -558,6 +580,56 @@ ConversationTest::testAddMember()
     CPPUNIT_ASSERT(fileutils::isFile(bobMember));
 }
 
+void
+ConversationTest::testMemberAddedNoBadFile()
+{
+    auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
+    auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
+    auto bobUri = bobAccount->getUsername();
+    auto convId = aliceAccount->startConversation();
+    std::mutex mtx;
+    std::unique_lock<std::mutex> lk {mtx};
+    std::condition_variable cv;
+    std::map<std::string, std::shared_ptr<DRing::CallbackWrapperBase>> confHandlers;
+    bool conversationReady = false, requestReceived = false, errorDetected = false;
+    confHandlers.insert(
+        DRing::exportable_callback<DRing::ConversationSignal::ConversationRequestReceived>(
+            [&](const std::string& /*accountId*/,
+                const std::string& /* conversationId */,
+                std::map<std::string, std::string> /*metadatas*/) {
+                requestReceived = true;
+                cv.notify_one();
+            }));
+    confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::ConversationReady>(
+        [&](const std::string& accountId, const std::string& /* conversationId */) {
+            if (accountId == bobId) {
+                conversationReady = true;
+                cv.notify_one();
+            }
+        }));
+    confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::OnConversationError>(
+        [&](const std::string& accountId,
+            const std::string& conversationId,
+            int code,
+            const std::string& /* what */) {
+            if (accountId == bobId && conversationId == convId && code == 1)
+                errorDetected = true;
+            cv.notify_one();
+        }));
+    DRing::registerSignalHandlers(confHandlers);
+    addFile(aliceAccount, convId, "BADFILE");
+    generateFakeInvite(aliceAccount, convId, bobUri);
+    // Generate conv request
+    aliceAccount->sendTextMessage(bobUri,
+                                  {{"application/invite+json",
+                                    "{\"conversationId\":\"" + convId + "\"}"}});
+    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; }));
+    errorDetected = false;
+    bobAccount->acceptConversationRequest(convId);
+    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return errorDetected; }));
+    DRing::unregisterSignalHandlers();
+}
+
 void
 ConversationTest::testAddOfflineMemberThenConnects()
 {
@@ -595,11 +667,11 @@ ConversationTest::testAddOfflineMemberThenConnects()
     CPPUNIT_ASSERT(requestReceived);
 
     carlaAccount->acceptConversationRequest(convId);
-    cv.wait_for(lk, std::chrono::seconds(30));
-    CPPUNIT_ASSERT(conversationReady);
+    cv.wait_for(lk, std::chrono::seconds(30), [&]() { return conversationReady; });
     auto clonedPath = fileutils::get_data_dir() + DIR_SEPARATOR_STR + carlaAccount->getAccountID()
                       + DIR_SEPARATOR_STR + "conversations" + DIR_SEPARATOR_STR + convId;
     CPPUNIT_ASSERT(fileutils::isDirectory(clonedPath));
+    DRing::unregisterSignalHandlers();
 }
 
 void
@@ -671,6 +743,7 @@ ConversationTest::testGetMembers()
     CPPUNIT_ASSERT(members[0]["role"] == "admin");
     CPPUNIT_ASSERT(members[1]["uri"] == bobUri);
     CPPUNIT_ASSERT(members[1]["role"] == "member");
+    DRing::unregisterSignalHandlers();
 }
 
 void
@@ -730,7 +803,7 @@ ConversationTest::testSendMessage()
     // Wait that alice sees Bob
     cv.wait_for(lk, std::chrono::seconds(30), [&]() { return messageAliceReceived == 1; });
 
-    aliceAccount->sendMessage(convId, std::string("hi"));
+    aliceAccount->sendMessage(convId, "hi"s);
     cv.wait_for(lk, std::chrono::seconds(30), [&]() { return messageBobReceived == 1; });
     DRing::unregisterSignalHandlers();
 }
@@ -760,10 +833,9 @@ ConversationTest::testSendMessageTriggerMessageReceived()
     DRing::registerSignalHandlers(confHandlers);
 
     auto convId = aliceAccount->startConversation();
-    cv.wait_for(lk, std::chrono::seconds(30));
-    CPPUNIT_ASSERT(conversationReady);
+    cv.wait_for(lk, std::chrono::seconds(30), [&] { return conversationReady; });
 
-    aliceAccount->sendMessage(convId, std::string("hi"));
+    aliceAccount->sendMessage(convId, "hi"s);
     cv.wait_for(lk, std::chrono::seconds(30), [&] { return messageReceived == 1; });
     CPPUNIT_ASSERT(messageReceived == 1);
     DRing::unregisterSignalHandlers();
@@ -793,7 +865,7 @@ ConversationTest::testMergeTwoDifferentHeads()
     confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::MessageReceived>(
         [&](const std::string& accountId,
             const std::string& conversationId,
-            std::map<std::string, std::string> message) {
+            std::map<std::string, std::string> /* message */) {
             if (accountId == carlaId && conversationId == convId) {
                 carlaGotMessage = true;
             }
@@ -819,9 +891,9 @@ ConversationTest::testMergeTwoDifferentHeads()
     ConversationRepository repo(carlaAccount, convId);
     repo.join();
 
-    aliceAccount->sendMessage(convId, std::string("hi"));
-    aliceAccount->sendMessage(convId, std::string("sup"));
-    aliceAccount->sendMessage(convId, std::string("jami"));
+    aliceAccount->sendMessage(convId, "hi"s);
+    aliceAccount->sendMessage(convId, "sup"s);
+    aliceAccount->sendMessage(convId, "jami"s);
 
     // Start Carla, should merge and all messages should be there
     Manager::instance().sendRegister(carlaId, true);
@@ -859,6 +931,7 @@ ConversationTest::testGetRequests()
     auto requests = bobAccount->getConversationRequests();
     CPPUNIT_ASSERT(requests.size() == 1);
     CPPUNIT_ASSERT(requests.front()["id"] == convId);
+    DRing::unregisterSignalHandlers();
 }
 
 void
@@ -892,6 +965,7 @@ ConversationTest::testDeclineRequest()
     // Decline request
     auto requests = bobAccount->getConversationRequests();
     CPPUNIT_ASSERT(requests.size() == 0);
+    DRing::unregisterSignalHandlers();
 }
 
 void
@@ -982,7 +1056,7 @@ ConversationTest::testSendMessageToMultipleParticipants()
                + DIR_SEPARATOR_STR + "conversations" + DIR_SEPARATOR_STR + convId;
     CPPUNIT_ASSERT(fileutils::isDirectory(repoPath));
 
-    aliceAccount->sendMessage(convId, std::string("hi"));
+    aliceAccount->sendMessage(convId, "hi"s);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(60), [&]() {
         return messageReceivedBob >= 1 && messageReceivedCarla >= 1;
     }));
@@ -1045,19 +1119,19 @@ ConversationTest::testPingPongMessages()
     CPPUNIT_ASSERT(fileutils::isDirectory(repoPath));
     messageBobReceived = 0;
     messageAliceReceived = 0;
-    aliceAccount->sendMessage(convId, std::string("ping"));
+    aliceAccount->sendMessage(convId, "ping"s);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() {
         return messageBobReceived == 1 && messageAliceReceived == 1;
     }));
-    bobAccount->sendMessage(convId, std::string("pong"));
+    bobAccount->sendMessage(convId, "pong"s);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() {
         return messageBobReceived == 2 && messageAliceReceived == 2;
     }));
-    bobAccount->sendMessage(convId, std::string("ping"));
+    bobAccount->sendMessage(convId, "ping"s);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() {
         return messageBobReceived == 3 && messageAliceReceived == 3;
     }));
-    aliceAccount->sendMessage(convId, std::string("pong"));
+    aliceAccount->sendMessage(convId, "pong"s);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() {
         return messageBobReceived == 4 && messageAliceReceived == 4;
     }));
@@ -1125,6 +1199,93 @@ ConversationTest::testRemoveMember()
     CPPUNIT_ASSERT(members.size() == 1);
     CPPUNIT_ASSERT(members[0]["uri"] == aliceAccount->getUsername());
     CPPUNIT_ASSERT(members[0]["role"] == "admin");
+    DRing::unregisterSignalHandlers();
+}
+
+void
+ConversationTest::testMemberBanNoBadFile()
+{
+    auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
+    auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
+    auto aliceUri = aliceAccount->getUsername();
+    auto bobUri = bobAccount->getUsername();
+    auto convId = aliceAccount->startConversation();
+    auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
+    auto carlaUri = carlaAccount->getUsername();
+    aliceAccount->trackBuddyPresence(carlaUri, true);
+    Manager::instance().sendRegister(carlaId, true);
+
+    std::mutex mtx;
+    std::unique_lock<std::mutex> lk {mtx};
+    std::condition_variable cv;
+    std::map<std::string, std::shared_ptr<DRing::CallbackWrapperBase>> confHandlers;
+    bool conversationReady = false, requestReceived = false, memberMessageGenerated = false,
+         voteMessageGenerated = false, messageBobReceived = false, errorDetected = false;
+    confHandlers.insert(
+        DRing::exportable_callback<DRing::ConversationSignal::ConversationRequestReceived>(
+            [&](const std::string& /*accountId*/,
+                const std::string& /* conversationId */,
+                std::map<std::string, std::string> /*metadatas*/) {
+                requestReceived = true;
+                cv.notify_one();
+            }));
+    confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::ConversationReady>(
+        [&](const std::string& accountId, const std::string& conversationId) {
+            if (accountId == bobId && conversationId == convId) {
+                conversationReady = true;
+                cv.notify_one();
+            }
+        }));
+    confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::MessageReceived>(
+        [&](const std::string& accountId,
+            const std::string& conversationId,
+            std::map<std::string, std::string> message) {
+            if (accountId == aliceId && conversationId == convId && message["type"] == "vote") {
+                voteMessageGenerated = true;
+            } else if (accountId == aliceId && conversationId == convId
+                       && message["type"] == "member") {
+                memberMessageGenerated = true;
+            } else if (accountId == bobId && conversationId == convId) {
+                messageBobReceived = true;
+            }
+            cv.notify_one();
+        }));
+    confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::OnConversationError>(
+        [&](const std::string& accountId,
+            const std::string& conversationId,
+            int code,
+            const std::string& /* what */) {
+            if (accountId == bobId && conversationId == convId && code == 3)
+                errorDetected = true;
+            cv.notify_one();
+        }));
+    DRing::registerSignalHandlers(confHandlers);
+    CPPUNIT_ASSERT(aliceAccount->addConversationMember(convId, bobUri));
+    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() {
+        return requestReceived && memberMessageGenerated;
+    }));
+    memberMessageGenerated = false;
+    bobAccount->acceptConversationRequest(convId);
+    CPPUNIT_ASSERT(
+        cv.wait_for(lk, std::chrono::seconds(30), [&]() { return memberMessageGenerated; }));
+    requestReceived = false;
+    CPPUNIT_ASSERT(aliceAccount->addConversationMember(convId, carlaUri));
+    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() {
+        return requestReceived && memberMessageGenerated;
+    }));
+    memberMessageGenerated = false;
+    messageBobReceived = false;
+    carlaAccount->acceptConversationRequest(convId);
+    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() {
+        return memberMessageGenerated && messageBobReceived;
+    }));
+
+    memberMessageGenerated = false;
+    voteMessageGenerated = false;
+    addFile(aliceAccount, convId, "BADFILE");
+    aliceAccount->removeConversationMember(convId, carlaUri);
+    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return errorDetected; }));
+    DRing::unregisterSignalHandlers();
 }
 
 /*void
@@ -1290,6 +1451,7 @@ ConversationTest::testMemberTryToRemoveAdmin()
     bobAccount->removeConversationMember(convId, aliceUri);
     auto members = aliceAccount->getConversationMembers(convId);
     CPPUNIT_ASSERT(members.size() == 2 && !memberMessageGenerated);
+    DRing::unregisterSignalHandlers();
 }
 
 void
@@ -1357,9 +1519,10 @@ ConversationTest::testBannedMemberCannotSendMessage()
 
     // Now check that alice doesn't receive a message from Bob
     aliceMessageReceived = false;
-    bobAccount->sendMessage(convId, std::string("hi"));
+    bobAccount->sendMessage(convId, "hi"s);
     CPPUNIT_ASSERT(
         !cv.wait_for(lk, std::chrono::seconds(30), [&]() { return aliceMessageReceived; }));
+    DRing::unregisterSignalHandlers();
 }
 
 void
@@ -1424,16 +1587,46 @@ ConversationTest::testAddBannedMember()
 
     // Then check that bobUri cannot be re-added
     CPPUNIT_ASSERT(!aliceAccount->addConversationMember(convId, bobUri));
+    DRing::unregisterSignalHandlers();
 }
 
 void
-ConversationTest::generateFakeVote(std::shared_ptr<JamiAccount> account,
-                                   const std::string& convId,
-                                   const std::string& votedUri)
+ConversationTest::addVote(std::shared_ptr<JamiAccount> account,
+                          const std::string& convId,
+                          const std::string& votedUri,
+                          const std::string& content)
+{
+    auto repoPath = fileutils::get_data_dir() + DIR_SEPARATOR_STR + account->getAccountID()
+                    + DIR_SEPARATOR_STR + "conversations" + DIR_SEPARATOR_STR + convId;
+    auto voteDirectory = repoPath + DIR_SEPARATOR_STR + "votes" + DIR_SEPARATOR_STR + "members";
+    auto voteFile = voteDirectory + DIR_SEPARATOR_STR + votedUri;
+    if (!fileutils::recursive_mkdir(voteDirectory, 0700)) {
+        return;
+    }
+
+    std::ofstream file(voteFile);
+    if (file.is_open()) {
+        file << content;
+        file.close();
+    }
+
+    Json::Value json;
+    json["uri"] = votedUri;
+    json["type"] = "vote";
+    Json::StreamWriterBuilder wbuilder;
+    wbuilder["commentStyle"] = "None";
+    wbuilder["indentation"] = "";
+    ConversationRepository cr(account->weak(), convId);
+    cr.commitMessage(Json::writeString(wbuilder, json));
+}
+
+void
+ConversationTest::simulateRemoval(std::shared_ptr<JamiAccount> account,
+                                  const std::string& convId,
+                                  const std::string& votedUri)
 {
     auto repoPath = fileutils::get_data_dir() + DIR_SEPARATOR_STR + account->getAccountID()
                     + DIR_SEPARATOR_STR + "conversations" + DIR_SEPARATOR_STR + convId;
-    // remove from member & add into banned without voting for the ban
     auto memberFile = repoPath + DIR_SEPARATOR_STR + "members" + DIR_SEPARATOR_STR + votedUri
                       + ".crt";
     auto bannedFile = repoPath + DIR_SEPARATOR_STR + "banned" + DIR_SEPARATOR_STR + "members"
@@ -1447,7 +1640,7 @@ ConversationTest::generateFakeVote(std::shared_ptr<JamiAccount> account,
 
     // git add -A
     git_index* index_ptr = nullptr;
-    git_strarray array = {0};
+    git_strarray array = {nullptr, 0};
     if (git_repository_index(&index_ptr, repo) < 0)
         return;
     GitIndex index {index_ptr, git_index_free};
@@ -1465,7 +1658,7 @@ ConversationTest::generateFakeVote(std::shared_ptr<JamiAccount> account,
     wbuilder["indentation"] = "";
     cr.commitMessage(Json::writeString(wbuilder, json));
 
-    account->sendMessage(convId, std::string("trigger the fake history to be pulled"));
+    account->sendMessage(convId, "trigger the fake history to be pulled"s);
 }
 
 void
@@ -1489,7 +1682,7 @@ ConversationTest::generateFakeInvite(std::shared_ptr<JamiAccount> account,
 
     // git add -A
     git_index* index_ptr = nullptr;
-    git_strarray array = {0};
+    git_strarray array = {nullptr, 0};
     if (git_repository_index(&index_ptr, repo) < 0)
         return;
     GitIndex index {index_ptr, git_index_free};
@@ -1507,8 +1700,9 @@ ConversationTest::generateFakeInvite(std::shared_ptr<JamiAccount> account,
     wbuilder["indentation"] = "";
     cr.commitMessage(Json::writeString(wbuilder, json));
 
-    account->sendMessage(convId, std::string("trigger the fake history to be pulled"));
+    account->sendMessage(convId, "trigger the fake history to be pulled"s);
 }
+
 void
 ConversationTest::addAll(std::shared_ptr<JamiAccount> account, const std::string& convId)
 {
@@ -1522,7 +1716,7 @@ ConversationTest::addAll(std::shared_ptr<JamiAccount> account, const std::string
 
     // git add -A
     git_index* index_ptr = nullptr;
-    git_strarray array = {0};
+    git_strarray array = {nullptr, 0};
     if (git_repository_index(&index_ptr, repo) < 0)
         return;
     GitIndex index {index_ptr, git_index_free};
@@ -1543,6 +1737,265 @@ ConversationTest::commit(std::shared_ptr<JamiAccount> account,
     cr.commitMessage(Json::writeString(wbuilder, message));
 }
 
+std::string
+ConversationTest::commitInRepo(const std::string& path,
+                               std::shared_ptr<JamiAccount> account,
+                               const std::string& msg)
+{
+    auto deviceId = std::string(account->currentDeviceId());
+    auto name = account->getDisplayName();
+    if (name.empty())
+        name = deviceId;
+
+    git_signature* sig_ptr = nullptr;
+    // Sign commit's buffer
+    if (git_signature_new(&sig_ptr, name.c_str(), deviceId.c_str(), std::time(nullptr), 0) < 0) {
+        JAMI_ERR("Unable to create a commit signature.");
+        return {};
+    }
+    GitSignature sig {sig_ptr, git_signature_free};
+
+    // Retrieve current index
+    git_index* index_ptr = nullptr;
+    git_repository* repo = nullptr;
+    // TODO share this repo with GitServer
+    if (git_repository_open(&repo, path.c_str()) != 0) {
+        JAMI_ERR("Could not open repository");
+        return {};
+    }
+
+    if (git_repository_index(&index_ptr, repo) < 0) {
+        JAMI_ERR("Could not open repository index");
+        return {};
+    }
+    GitIndex index {index_ptr, git_index_free};
+
+    git_oid tree_id;
+    if (git_index_write_tree(&tree_id, index.get()) < 0) {
+        JAMI_ERR("Unable to write initial tree from index");
+        return {};
+    }
+
+    git_tree* tree_ptr = nullptr;
+    if (git_tree_lookup(&tree_ptr, repo, &tree_id) < 0) {
+        JAMI_ERR("Could not look up initial tree");
+        return {};
+    }
+    GitTree tree = {tree_ptr, git_tree_free};
+
+    git_oid commit_id;
+    if (git_reference_name_to_id(&commit_id, repo, "HEAD") < 0) {
+        JAMI_ERR("Cannot get reference for HEAD");
+        return {};
+    }
+
+    git_commit* head_ptr = nullptr;
+    if (git_commit_lookup(&head_ptr, repo, &commit_id) < 0) {
+        JAMI_ERR("Could not look up HEAD commit");
+        return {};
+    }
+    GitCommit head_commit {head_ptr, git_commit_free};
+
+    git_buf to_sign = {};
+    const git_commit* head_ref[1] = {head_commit.get()};
+    if (git_commit_create_buffer(
+            &to_sign, repo, sig.get(), sig.get(), nullptr, msg.c_str(), tree.get(), 1, &head_ref[0])
+        < 0) {
+        JAMI_ERR("Could not create commit buffer");
+        return {};
+    }
+
+    // git commit -S
+    auto to_sign_vec = std::vector<uint8_t>(to_sign.ptr, to_sign.ptr + to_sign.size);
+    auto signed_buf = account->identity().first->sign(to_sign_vec);
+    std::string signed_str = base64::encode(signed_buf);
+    if (git_commit_create_with_signature(&commit_id,
+                                         repo,
+                                         to_sign.ptr,
+                                         signed_str.c_str(),
+                                         "signature")
+        < 0) {
+        JAMI_ERR("Could not sign commit");
+        return {};
+    }
+
+    // Move commit to main branch
+    git_reference* ref_ptr = nullptr;
+    if (git_reference_create(&ref_ptr, repo, "refs/heads/main", &commit_id, true, nullptr) < 0) {
+        JAMI_WARN("Could not move commit to main");
+    }
+    git_reference_free(ref_ptr);
+    git_repository_free(repo);
+
+    auto commit_str = git_oid_tostr_s(&commit_id);
+    if (commit_str) {
+        JAMI_INFO("New message added with id: %s", commit_str);
+        return commit_str;
+    }
+
+    return {};
+}
+
+std::string
+ConversationTest::createFakeConversation(std::shared_ptr<JamiAccount> account)
+{
+    auto repoPath = fileutils::get_data_dir() + DIR_SEPARATOR_STR + account->getAccountID()
+                    + DIR_SEPARATOR_STR + "conversations" + DIR_SEPARATOR_STR + "tmp";
+
+    git_repository* repo_ptr = nullptr;
+    git_repository_init_options opts;
+    git_repository_init_options_init(&opts, GIT_REPOSITORY_INIT_OPTIONS_VERSION);
+    opts.flags |= GIT_REPOSITORY_INIT_MKPATH;
+    opts.initial_head = "main";
+    if (git_repository_init_ext(&repo_ptr, repoPath.c_str(), &opts) < 0) {
+        JAMI_ERR("Couldn't create a git repository in %s", repoPath.c_str());
+    }
+    GitRepository repo {std::move(repo_ptr), git_repository_free};
+
+    // Add files
+    auto deviceId = std::string(account->currentDeviceId());
+
+    repoPath = git_repository_workdir(repo.get());
+    std::string adminsPath = repoPath + "admins";
+    std::string devicesPath = repoPath + "devices";
+    std::string crlsPath = repoPath + "CRLs" + DIR_SEPARATOR_STR + deviceId;
+
+    if (!fileutils::recursive_mkdir(adminsPath, 0700)) {
+        JAMI_ERR("Error when creating %s. Abort create conversations", adminsPath.c_str());
+    }
+
+    auto cert = account->identity().second;
+    auto deviceCert = cert->toString(false);
+    auto parentCert = cert->issuer;
+    if (!parentCert) {
+        JAMI_ERR("Parent cert is null!");
+    }
+
+    // /admins
+    std::string adminPath = adminsPath + DIR_SEPARATOR_STR + parentCert->getId().toString()
+                            + ".crt";
+    auto file = fileutils::ofstream(adminPath, std::ios::trunc | std::ios::binary);
+    if (!file.is_open()) {
+        JAMI_ERR("Could not write data to %s", adminPath.c_str());
+    }
+    file << parentCert->toString(true);
+    file.close();
+
+    if (!fileutils::recursive_mkdir(devicesPath, 0700)) {
+        JAMI_ERR("Error when creating %s. Abort create conversations", devicesPath.c_str());
+    }
+
+    // /devices
+    std::string devicePath = devicesPath + DIR_SEPARATOR_STR + cert->getId().toString() + ".crt";
+    file = fileutils::ofstream(devicePath, std::ios::trunc | std::ios::binary);
+    if (!file.is_open()) {
+        JAMI_ERR("Could not write data to %s", devicePath.c_str());
+    }
+    file << deviceCert;
+    file.close();
+
+    if (!fileutils::recursive_mkdir(crlsPath, 0700)) {
+        JAMI_ERR("Error when creating %s. Abort create conversations", crlsPath.c_str());
+    }
+
+    std::string badFile = repoPath + DIR_SEPARATOR_STR + "BAD";
+    file = fileutils::ofstream(badFile, std::ios::trunc | std::ios::binary);
+
+    addAll(account, "tmp");
+
+    JAMI_INFO("Initial files added in %s", repoPath.c_str());
+
+    std::string name = account->getDisplayName();
+    if (name.empty())
+        name = deviceId;
+
+    git_signature* sig_ptr = nullptr;
+    git_index* index_ptr = nullptr;
+    git_oid tree_id, commit_id;
+    git_tree* tree_ptr = nullptr;
+    git_buf to_sign = {};
+
+    // Sign commit's buffer
+    if (git_signature_new(&sig_ptr, name.c_str(), deviceId.c_str(), std::time(nullptr), 0) < 0) {
+        JAMI_ERR("Unable to create a commit signature.");
+    }
+    GitSignature sig {sig_ptr, git_signature_free};
+
+    if (git_repository_index(&index_ptr, repo.get()) < 0) {
+        JAMI_ERR("Could not open repository index");
+    }
+    GitIndex index {index_ptr, git_index_free};
+
+    if (git_index_write_tree(&tree_id, index.get()) < 0) {
+        JAMI_ERR("Unable to write initial tree from index");
+    }
+
+    if (git_tree_lookup(&tree_ptr, repo.get(), &tree_id) < 0) {
+        JAMI_ERR("Could not look up initial tree");
+    }
+    GitTree tree = {tree_ptr, git_tree_free};
+
+    Json::Value json;
+    json["mode"] = 1;
+    json["type"] = "initial";
+    Json::StreamWriterBuilder wbuilder;
+    wbuilder["commentStyle"] = "None";
+    wbuilder["indentation"] = "";
+
+    if (git_commit_create_buffer(&to_sign,
+                                 repo.get(),
+                                 sig.get(),
+                                 sig.get(),
+                                 nullptr,
+                                 Json::writeString(wbuilder, json).c_str(),
+                                 tree.get(),
+                                 0,
+                                 nullptr)
+        < 0) {
+        JAMI_ERR("Could not create initial buffer");
+        return {};
+    }
+
+    auto to_sign_vec = std::vector<uint8_t>(to_sign.ptr, to_sign.ptr + to_sign.size);
+    auto signed_buf = account->identity().first->sign(to_sign_vec);
+    std::string signed_str = base64::encode(signed_buf);
+
+    // git commit -S
+    if (git_commit_create_with_signature(&commit_id,
+                                         repo.get(),
+                                         to_sign.ptr,
+                                         signed_str.c_str(),
+                                         "signature")
+        < 0) {
+        JAMI_ERR("Could not sign initial commit");
+        return {};
+    }
+
+    // Move commit to main branch
+    git_commit* commit = nullptr;
+    if (git_commit_lookup(&commit, repo.get(), &commit_id) == 0) {
+        git_reference* ref = nullptr;
+        git_branch_create(&ref, repo.get(), "main", commit, true);
+        git_commit_free(commit);
+        git_reference_free(ref);
+    }
+
+    auto commit_str = git_oid_tostr_s(&commit_id);
+
+    auto finalRepo = fileutils::get_data_dir() + DIR_SEPARATOR_STR + account->getAccountID()
+                     + DIR_SEPARATOR_STR + "conversations" + DIR_SEPARATOR_STR + commit_str;
+    std::rename(repoPath.c_str(), finalRepo.c_str());
+
+    file = std::ofstream(fileutils::get_data_dir() + DIR_SEPARATOR_STR + account->getAccountID()
+                             + DIR_SEPARATOR_STR + "convInfo",
+                         std::ios::trunc | std::ios::binary);
+
+    std::vector<ConvInfoTest> test;
+    test.emplace_back(ConvInfoTest {commit_str, std::time(nullptr), 0, 0});
+    msgpack::pack(file, test);
+    return commit_str;
+}
+
 void
 ConversationTest::addFile(std::shared_ptr<JamiAccount> account,
                           const std::string& convId,
@@ -1567,7 +2020,7 @@ ConversationTest::addFile(std::shared_ptr<JamiAccount> account,
 
     // git add -A
     git_index* index_ptr = nullptr;
-    git_strarray array = {0};
+    git_strarray array = {nullptr, 0};
     if (git_repository_index(&index_ptr, repo) < 0)
         return;
     GitIndex index {index_ptr, git_index_free};
@@ -1593,7 +2046,7 @@ ConversationTest::testMemberCannotBanOther()
     std::condition_variable cv;
     std::map<std::string, std::shared_ptr<DRing::CallbackWrapperBase>> confHandlers;
     bool conversationReady = false, requestReceived = false, memberMessageGenerated = false,
-         voteMessageGenerated = false, messageBobReceived = false;
+         voteMessageGenerated = false, messageBobReceived = false, errorDetected = false;
     confHandlers.insert(
         DRing::exportable_callback<DRing::ConversationSignal::ConversationRequestReceived>(
             [&](const std::string& /*accountId*/,
@@ -1623,6 +2076,15 @@ ConversationTest::testMemberCannotBanOther()
             }
             cv.notify_one();
         }));
+    confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::OnConversationError>(
+        [&](const std::string& /* accountId */,
+            const std::string& /* conversationId */,
+            int code,
+            const std::string& /* what */) {
+            if (code == 3)
+                errorDetected = true;
+            cv.notify_one();
+        }));
     DRing::registerSignalHandlers(confHandlers);
     CPPUNIT_ASSERT(aliceAccount->addConversationMember(convId, bobUri));
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() {
@@ -1645,11 +2107,13 @@ ConversationTest::testMemberCannotBanOther()
     }));
 
     // Now Carla remove Bob as a member
+    errorDetected = false;
     messageBobReceived = false;
-    generateFakeVote(carlaAccount, convId, bobUri);
-    CPPUNIT_ASSERT(!cv.wait_for(lk, std::chrono::seconds(30), [&]() { return messageBobReceived; }));
+    // remove from member & add into banned without voting for the ban
+    simulateRemoval(carlaAccount, convId, bobUri);
+    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return errorDetected; }));
 
-    aliceAccount->sendMessage(convId, std::string("hi"));
+    aliceAccount->sendMessage(convId, "hi"s);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return messageBobReceived; }));
 }
 
@@ -1671,7 +2135,7 @@ ConversationTest::testCheckAdminFakeAVoteIsDetected()
     std::condition_variable cv;
     std::map<std::string, std::shared_ptr<DRing::CallbackWrapperBase>> confHandlers;
     bool conversationReady = false, requestReceived = false, memberMessageGenerated = false,
-         voteMessageGenerated = false, messageBobReceived = false;
+         voteMessageGenerated = false, messageBobReceived = false, errorDetected = false;
     confHandlers.insert(
         DRing::exportable_callback<DRing::ConversationSignal::ConversationRequestReceived>(
             [&](const std::string& /*accountId*/,
@@ -1701,6 +2165,15 @@ ConversationTest::testCheckAdminFakeAVoteIsDetected()
             }
             cv.notify_one();
         }));
+    confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::OnConversationError>(
+        [&](const std::string& /* accountId */,
+            const std::string& /* conversationId */,
+            int code,
+            const std::string& /* what */) {
+            if (code == 3)
+                errorDetected = true;
+            cv.notify_one();
+        }));
     DRing::registerSignalHandlers(confHandlers);
     CPPUNIT_ASSERT(aliceAccount->addConversationMember(convId, bobUri));
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() {
@@ -1723,19 +2196,189 @@ ConversationTest::testCheckAdminFakeAVoteIsDetected()
     }));
 
     // Now Alice remove Carla without a vote. Bob will not receive the message
-    messageBobReceived = false;
-    generateFakeVote(aliceAccount, convId, carlaUri);
-    CPPUNIT_ASSERT(!cv.wait_for(lk, std::chrono::seconds(30), [&]() { return messageBobReceived; }));
+    errorDetected = false;
+    simulateRemoval(aliceAccount, convId, carlaUri);
+    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return errorDetected; }));
 }
 
 void
-ConversationTest::testAdminCannotKickTheirself()
+ConversationTest::testVoteNonEmpty()
 {
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
+    auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto aliceUri = aliceAccount->getUsername();
+    auto bobUri = bobAccount->getUsername();
     auto convId = aliceAccount->startConversation();
-    std::mutex mtx;
-    std::unique_lock<std::mutex> lk {mtx};
+    auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
+    auto carlaUri = carlaAccount->getUsername();
+    aliceAccount->trackBuddyPresence(carlaUri, true);
+    Manager::instance().sendRegister(carlaId, true);
+
+    std::mutex mtx;
+    std::unique_lock<std::mutex> lk {mtx};
+    std::condition_variable cv;
+    std::map<std::string, std::shared_ptr<DRing::CallbackWrapperBase>> confHandlers;
+    bool conversationReady = false, requestReceived = false, memberMessageGenerated = false,
+         voteMessageGenerated = false, messageBobReceived = false, errorDetected = false;
+    confHandlers.insert(
+        DRing::exportable_callback<DRing::ConversationSignal::ConversationRequestReceived>(
+            [&](const std::string& /*accountId*/,
+                const std::string& /* conversationId */,
+                std::map<std::string, std::string> /*metadatas*/) {
+                requestReceived = true;
+                cv.notify_one();
+            }));
+    confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::ConversationReady>(
+        [&](const std::string& accountId, const std::string& conversationId) {
+            if (accountId == bobId && conversationId == convId) {
+                conversationReady = true;
+                cv.notify_one();
+            }
+        }));
+    confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::MessageReceived>(
+        [&](const std::string& accountId,
+            const std::string& conversationId,
+            std::map<std::string, std::string> message) {
+            if (accountId == aliceId && conversationId == convId && message["type"] == "vote") {
+                voteMessageGenerated = true;
+            } else if (accountId == aliceId && conversationId == convId
+                       && message["type"] == "member") {
+                memberMessageGenerated = true;
+            } else if (accountId == bobId && conversationId == convId) {
+                messageBobReceived = true;
+            }
+            cv.notify_one();
+        }));
+    confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::OnConversationError>(
+        [&](const std::string& /* accountId */,
+            const std::string& /* conversationId */,
+            int code,
+            const std::string& /* what */) {
+            if (code == 3)
+                errorDetected = true;
+            cv.notify_one();
+        }));
+    DRing::registerSignalHandlers(confHandlers);
+    CPPUNIT_ASSERT(aliceAccount->addConversationMember(convId, bobUri));
+    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() {
+        return requestReceived && memberMessageGenerated;
+    }));
+    memberMessageGenerated = false;
+    bobAccount->acceptConversationRequest(convId);
+    CPPUNIT_ASSERT(
+        cv.wait_for(lk, std::chrono::seconds(30), [&]() { return memberMessageGenerated; }));
+    requestReceived = false;
+    CPPUNIT_ASSERT(aliceAccount->addConversationMember(convId, carlaUri));
+    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() {
+        return requestReceived && memberMessageGenerated;
+    }));
+    memberMessageGenerated = false;
+    messageBobReceived = false;
+    carlaAccount->acceptConversationRequest(convId);
+    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() {
+        return memberMessageGenerated && messageBobReceived;
+    }));
+
+    // Now Alice removes Carla with a non empty file
+    errorDetected = false;
+    addVote(aliceAccount, convId, carlaUri, "CONTENT");
+    simulateRemoval(aliceAccount, convId, carlaUri);
+    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return errorDetected; }));
+}
+
+void
+ConversationTest::testCommitUnauthorizedUser()
+{
+    auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
+    auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
+    auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
+    auto bobUri = bobAccount->getUsername();
+
+    std::mutex mtx;
+    std::unique_lock<std::mutex> lk {mtx};
+    std::condition_variable cv;
+    std::map<std::string, std::shared_ptr<DRing::CallbackWrapperBase>> confHandlers;
+    auto messageBobReceived = 0, messageAliceReceived = 0;
+    bool requestReceived = false;
+    bool conversationReady = false;
+    bool errorDetected = false;
+    confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::MessageReceived>(
+        [&](const std::string& accountId,
+            const std::string& /* conversationId */,
+            std::map<std::string, std::string> /*message*/) {
+            if (accountId == bobId) {
+                messageBobReceived += 1;
+            } else {
+                messageAliceReceived += 1;
+            }
+            cv.notify_one();
+        }));
+    confHandlers.insert(
+        DRing::exportable_callback<DRing::ConversationSignal::ConversationRequestReceived>(
+            [&](const std::string& /*accountId*/,
+                const std::string& /* conversationId */,
+                std::map<std::string, std::string> /*metadatas*/) {
+                requestReceived = true;
+                cv.notify_one();
+            }));
+    confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::ConversationReady>(
+        [&](const std::string& accountId, const std::string& /* conversationId */) {
+            if (accountId == bobId) {
+                conversationReady = true;
+                cv.notify_one();
+            }
+        }));
+    confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::OnConversationError>(
+        [&](const std::string& /* accountId */,
+            const std::string& /* conversationId */,
+            int code,
+            const std::string& /* what */) {
+            if (code == 3)
+                errorDetected = true;
+            cv.notify_one();
+        }));
+    DRing::registerSignalHandlers(confHandlers);
+
+    auto convId = aliceAccount->startConversation();
+
+    CPPUNIT_ASSERT(aliceAccount->addConversationMember(convId, bobUri));
+    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; }));
+
+    bobAccount->acceptConversationRequest(convId);
+    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return conversationReady; }));
+
+    // Assert that repository exists
+    auto repoPath = fileutils::get_data_dir() + DIR_SEPARATOR_STR + bobAccount->getAccountID()
+                    + DIR_SEPARATOR_STR + "conversations" + DIR_SEPARATOR_STR + convId;
+    CPPUNIT_ASSERT(fileutils::isDirectory(repoPath));
+    // Wait that alice sees Bob
+    CPPUNIT_ASSERT(
+        cv.wait_for(lk, std::chrono::seconds(30), [&]() { return messageAliceReceived == 1; }));
+
+    // Add commit from invalid user
+    Json::Value root;
+    root["type"] = "text/plain";
+    root["body"] = "hi";
+    Json::StreamWriterBuilder wbuilder;
+    wbuilder["commentStyle"] = "None";
+    wbuilder["indentation"] = "";
+    auto message = Json::writeString(wbuilder, root);
+    commitInRepo(repoPath, carlaAccount, message);
+
+    errorDetected = false;
+    bobAccount->sendMessage(convId, "hi"s);
+    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return errorDetected; }));
+    DRing::unregisterSignalHandlers();
+}
+
+void
+ConversationTest::testAdminCannotKickTheirself()
+{
+    auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
+    auto aliceUri = aliceAccount->getUsername();
+    auto convId = aliceAccount->startConversation();
+    std::mutex mtx;
+    std::unique_lock<std::mutex> lk {mtx};
     std::condition_variable cv;
     std::map<std::string, std::shared_ptr<DRing::CallbackWrapperBase>> confHandlers;
     bool conversationReady = false, requestReceived = false, memberMessageGenerated = false,
@@ -1779,11 +2422,14 @@ ConversationTest::testAdminCannotKickTheirself()
 }
 
 void
-ConversationTest::testPlainTextNoBadFile()
+ConversationTest::testNoBadFileInInitialCommit()
 {
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
-    auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
-    auto bobUri = bobAccount->getUsername();
+    auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
+    auto carlaUri = carlaAccount->getUsername();
+    auto aliceUri = aliceAccount->getUsername();
+
+    auto convId = createFakeConversation(carlaAccount);
 
     std::mutex mtx;
     std::unique_lock<std::mutex> lk {mtx};
@@ -1791,7 +2437,8 @@ ConversationTest::testPlainTextNoBadFile()
     std::map<std::string, std::shared_ptr<DRing::CallbackWrapperBase>> confHandlers;
     auto messageBobReceived = 0, messageAliceReceived = 0;
     bool requestReceived = false;
-    bool conversationReady = false;
+    bool carlaConnected = false;
+    bool errorDetected = false;
     confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::MessageReceived>(
         [&](const std::string& accountId,
             const std::string& /* conversationId */,
@@ -1811,6 +2458,74 @@ ConversationTest::testPlainTextNoBadFile()
                 requestReceived = true;
                 cv.notify_one();
             }));
+    confHandlers.insert(
+        DRing::exportable_callback<DRing::ConfigurationSignal::VolatileDetailsChanged>(
+            [&](const std::string&, const std::map<std::string, std::string>&) {
+                auto details = carlaAccount->getVolatileAccountDetails();
+                auto daemonStatus = details[DRing::Account::ConfProperties::Registration::STATUS];
+                if (daemonStatus == "REGISTERED") {
+                    carlaConnected = true;
+                    cv.notify_one();
+                }
+            }));
+    confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::OnConversationError>(
+        [&](const std::string& /* accountId */,
+            const std::string& /* conversationId */,
+            int code,
+            const std::string& /* what */) {
+            if (code == 3)
+                errorDetected = true;
+            cv.notify_one();
+        }));
+    DRing::registerSignalHandlers(confHandlers);
+
+    Manager::instance().sendRegister(carlaId, true);
+    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&] { return carlaConnected; }));
+    JAMI_ERR("@@@@@@@@@@@22");
+    CPPUNIT_ASSERT(carlaAccount->addConversationMember(convId, aliceUri));
+    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; }));
+
+    aliceAccount->acceptConversationRequest(convId);
+    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return errorDetected; }));
+}
+
+void
+ConversationTest::testPlainTextNoBadFile()
+{
+    auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
+    auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
+    auto bobUri = bobAccount->getUsername();
+
+    std::mutex mtx;
+    std::unique_lock<std::mutex> lk {mtx};
+    std::condition_variable cv;
+    std::map<std::string, std::shared_ptr<DRing::CallbackWrapperBase>> confHandlers;
+    auto messageBobReceived = 0;
+    bool memberMessageGenerated = false;
+    bool requestReceived = false;
+    bool conversationReady = false;
+    bool errorDetected = false;
+    std::string convId = aliceAccount->startConversation();
+    confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::MessageReceived>(
+        [&](const std::string& accountId,
+            const std::string& conversationId,
+            std::map<std::string, std::string> message) {
+            if (accountId == bobId) {
+                messageBobReceived += 1;
+            } else if (accountId == aliceId && conversationId == convId
+                       && message["type"] == "member") {
+                memberMessageGenerated = true;
+            }
+            cv.notify_one();
+        }));
+    confHandlers.insert(
+        DRing::exportable_callback<DRing::ConversationSignal::ConversationRequestReceived>(
+            [&](const std::string& /*accountId*/,
+                const std::string& /* conversationId */,
+                std::map<std::string, std::string> /*metadatas*/) {
+                requestReceived = true;
+                cv.notify_one();
+            }));
     confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::ConversationReady>(
         [&](const std::string& accountId, const std::string& /* conversationId */) {
             if (accountId == bobId) {
@@ -1818,24 +2533,37 @@ ConversationTest::testPlainTextNoBadFile()
                 cv.notify_one();
             }
         }));
+    confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::OnConversationError>(
+        [&](const std::string& /* accountId */,
+            const std::string& /* conversationId */,
+            int code,
+            const std::string& /* what */) {
+            if (code == 3)
+                errorDetected = true;
+            cv.notify_one();
+        }));
     DRing::registerSignalHandlers(confHandlers);
 
-    auto convId = aliceAccount->startConversation();
-
     CPPUNIT_ASSERT(aliceAccount->addConversationMember(convId, bobUri));
-    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() {
+        return requestReceived && memberMessageGenerated;
+    }));
+    memberMessageGenerated = false;
 
     bobAccount->acceptConversationRequest(convId);
-    cv.wait_for(lk, std::chrono::seconds(30));
-    CPPUNIT_ASSERT(conversationReady);
-    // Wait that alice sees Bob
-    cv.wait_for(lk, std::chrono::seconds(30), [&]() { return messageAliceReceived == 1; });
+    cv.wait_for(lk, std::chrono::seconds(30), [&] {
+        return conversationReady && memberMessageGenerated;
+    });
 
     addFile(aliceAccount, convId, "BADFILE");
-    aliceAccount->sendMessage(convId, std::string("hi"));
+    Json::Value root;
+    root["type"] = "text/plain";
+    root["body"] = "hi";
+    commit(aliceAccount, convId, root);
+    errorDetected = false;
+    aliceAccount->sendMessage(convId, "hi"s);
     // Check not received due to the unwanted file
-    CPPUNIT_ASSERT(
-        !cv.wait_for(lk, std::chrono::seconds(30), [&]() { return messageBobReceived == 1; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return errorDetected; }));
     DRing::unregisterSignalHandlers();
 }
 
@@ -1924,7 +2652,7 @@ ConversationTest::testVoteNoBadFile()
     }));
 
     messageCarlaReceived = false;
-    bobAccount->sendMessage(convId, std::string("final"));
+    bobAccount->sendMessage(convId, "final"s);
     CPPUNIT_ASSERT(
         cv.wait_for(lk, std::chrono::seconds(30), [&]() { return messageCarlaReceived; }));
 }
@@ -1943,6 +2671,7 @@ ConversationTest::testETooBigClone()
     auto messageBobReceived = 0, messageAliceReceived = 0;
     bool requestReceived = false;
     bool conversationReady = false;
+    bool errorDetected = false;
     confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::MessageReceived>(
         [&](const std::string& accountId,
             const std::string& /* conversationId */,
@@ -1969,6 +2698,15 @@ ConversationTest::testETooBigClone()
                 cv.notify_one();
             }
         }));
+    confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::OnConversationError>(
+        [&](const std::string& /* accountId */,
+            const std::string& /* conversationId */,
+            int code,
+            const std::string& /* what */) {
+            if (code == 3)
+                errorDetected = true;
+            cv.notify_one();
+        }));
     DRing::registerSignalHandlers(confHandlers);
 
     auto convId = aliceAccount->startConversation();
@@ -1987,8 +2725,9 @@ ConversationTest::testETooBigClone()
     CPPUNIT_ASSERT(aliceAccount->addConversationMember(convId, bobUri));
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; }));
 
+    errorDetected = false;
     bobAccount->acceptConversationRequest(convId);
-    CPPUNIT_ASSERT(!cv.wait_for(lk, std::chrono::seconds(30), [&]() { return conversationReady; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return errorDetected; }));
     DRing::unregisterSignalHandlers();
 }
 
@@ -2006,6 +2745,7 @@ ConversationTest::testETooBigFetch()
     auto messageBobReceived = 0, messageAliceReceived = 0;
     bool requestReceived = false;
     bool conversationReady = false;
+    bool errorDetected = false;
     confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::MessageReceived>(
         [&](const std::string& accountId,
             const std::string& /* conversationId */,
@@ -2032,6 +2772,15 @@ ConversationTest::testETooBigFetch()
                 cv.notify_one();
             }
         }));
+    confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::OnConversationError>(
+        [&](const std::string& /* accountId */,
+            const std::string& /* conversationId */,
+            int code,
+            const std::string& /* what */) {
+            if (code == 3)
+                errorDetected = true;
+            cv.notify_one();
+        }));
     DRing::registerSignalHandlers(confHandlers);
 
     auto convId = aliceAccount->startConversation();
@@ -2049,6 +2798,7 @@ ConversationTest::testETooBigFetch()
                     + DIR_SEPARATOR_STR + "conversations" + DIR_SEPARATOR_STR + convId;
     std::ofstream bad(repoPath + DIR_SEPARATOR_STR + "BADFILE");
     CPPUNIT_ASSERT(bad.is_open());
+    errorDetected = false;
     for (int i = 0; i < 300 * 1024 * 1024; ++i)
         bad << "A";
     bad.close();
@@ -2059,9 +2809,247 @@ ConversationTest::testETooBigFetch()
     json["type"] = "text/plain";
     commit(aliceAccount, convId, json);
 
-    aliceAccount->sendMessage(convId, std::string("hi"));
-    CPPUNIT_ASSERT(
-        !cv.wait_for(lk, std::chrono::seconds(30), [&]() { return messageBobReceived == 1; }));
+    aliceAccount->sendMessage(convId, "hi"s);
+    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return errorDetected; }));
+    DRing::unregisterSignalHandlers();
+}
+
+void
+ConversationTest::testMemberJoinsNoBadFile()
+{
+    auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
+    auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
+    auto carlaUri = carlaAccount->getUsername();
+    aliceAccount->trackBuddyPresence(carlaUri, true);
+    auto convId = aliceAccount->startConversation();
+
+    std::mutex mtx;
+    std::unique_lock<std::mutex> lk {mtx};
+    std::condition_variable cv;
+    std::map<std::string, std::shared_ptr<DRing::CallbackWrapperBase>> confHandlers;
+    bool conversationReady = false, errorDetected = false, carlaConnected = false;
+    confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::ConversationReady>(
+        [&](const std::string& accountId, const std::string& /* conversationId */) {
+            if (accountId == carlaId) {
+                conversationReady = true;
+                cv.notify_one();
+            }
+        }));
+    confHandlers.insert(
+        DRing::exportable_callback<DRing::ConfigurationSignal::VolatileDetailsChanged>(
+            [&](const std::string&, const std::map<std::string, std::string>&) {
+                auto details = carlaAccount->getVolatileAccountDetails();
+                auto daemonStatus = details[DRing::Account::ConfProperties::Registration::STATUS];
+                if (daemonStatus == "REGISTERED") {
+                    carlaConnected = true;
+                    cv.notify_one();
+                }
+            }));
+    confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::OnConversationError>(
+        [&](const std::string& /* accountId */,
+            const std::string& /* conversationId */,
+            int code,
+            const std::string& /* what */) {
+            if (code == 3)
+                errorDetected = true;
+            cv.notify_one();
+        }));
+    DRing::registerSignalHandlers(confHandlers);
+
+    CPPUNIT_ASSERT(aliceAccount->addConversationMember(convId, carlaUri, false));
+
+    // Cp conversations & convInfo
+    auto repoPathAlice = fileutils::get_data_dir() + DIR_SEPARATOR_STR
+                         + aliceAccount->getAccountID() + DIR_SEPARATOR_STR + "conversations";
+    auto repoPathCarla = fileutils::get_data_dir() + DIR_SEPARATOR_STR
+                         + carlaAccount->getAccountID() + DIR_SEPARATOR_STR + "conversations";
+    std::filesystem::copy(repoPathAlice, repoPathCarla, std::filesystem::copy_options::recursive);
+    auto ciPathAlice = fileutils::get_data_dir() + DIR_SEPARATOR_STR + aliceAccount->getAccountID()
+                       + DIR_SEPARATOR_STR + "convInfo";
+    auto ciPathCarla = fileutils::get_data_dir() + DIR_SEPARATOR_STR + carlaAccount->getAccountID()
+                       + DIR_SEPARATOR_STR + "convInfo";
+    std::filesystem::copy(ciPathAlice, ciPathCarla);
+
+    // Accept for alice and makes different heads
+    addFile(carlaAccount, convId, "BADFILE");
+    ConversationRepository repo(carlaAccount, convId);
+    repo.join();
+
+    // Start Carla, should merge and all messages should be there
+    Manager::instance().sendRegister(carlaId, true);
+    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&] { return carlaConnected; }));
+
+    carlaAccount->sendMessage(convId, "hi"s);
+    errorDetected = false;
+
+    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(60), [&] { return errorDetected; }));
+    DRing::unregisterSignalHandlers();
+}
+
+void
+ConversationTest::testMemberAddedNoCertificate()
+{
+    auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
+    auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
+    auto carlaUri = carlaAccount->getUsername();
+    aliceAccount->trackBuddyPresence(carlaUri, true);
+    auto convId = aliceAccount->startConversation();
+
+    std::mutex mtx;
+    std::unique_lock<std::mutex> lk {mtx};
+    std::condition_variable cv;
+    std::map<std::string, std::shared_ptr<DRing::CallbackWrapperBase>> confHandlers;
+    bool conversationReady = false, errorDetected = false, carlaConnected = false;
+    confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::ConversationReady>(
+        [&](const std::string& accountId, const std::string& /* conversationId */) {
+            if (accountId == carlaId) {
+                conversationReady = true;
+                cv.notify_one();
+            }
+        }));
+    confHandlers.insert(
+        DRing::exportable_callback<DRing::ConfigurationSignal::VolatileDetailsChanged>(
+            [&](const std::string&, const std::map<std::string, std::string>&) {
+                auto details = carlaAccount->getVolatileAccountDetails();
+                auto daemonStatus = details[DRing::Account::ConfProperties::Registration::STATUS];
+                if (daemonStatus == "REGISTERED") {
+                    carlaConnected = true;
+                    cv.notify_one();
+                }
+            }));
+    confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::OnConversationError>(
+        [&](const std::string& /* accountId */,
+            const std::string& /* conversationId */,
+            int code,
+            const std::string& /* what */) {
+            if (code == 3)
+                errorDetected = true;
+            cv.notify_one();
+        }));
+    DRing::registerSignalHandlers(confHandlers);
+
+    CPPUNIT_ASSERT(aliceAccount->addConversationMember(convId, carlaUri, false));
+
+    // Cp conversations & convInfo
+    auto repoPathAlice = fileutils::get_data_dir() + DIR_SEPARATOR_STR
+                         + aliceAccount->getAccountID() + DIR_SEPARATOR_STR + "conversations";
+    auto repoPathCarla = fileutils::get_data_dir() + DIR_SEPARATOR_STR
+                         + carlaAccount->getAccountID() + DIR_SEPARATOR_STR + "conversations";
+    std::filesystem::copy(repoPathAlice, repoPathCarla, std::filesystem::copy_options::recursive);
+    auto ciPathAlice = fileutils::get_data_dir() + DIR_SEPARATOR_STR + aliceAccount->getAccountID()
+                       + DIR_SEPARATOR_STR + "convInfo";
+    auto ciPathCarla = fileutils::get_data_dir() + DIR_SEPARATOR_STR + carlaAccount->getAccountID()
+                       + DIR_SEPARATOR_STR + "convInfo";
+    std::filesystem::copy(ciPathAlice, ciPathCarla);
+
+    // Remove invite but do not add member certificate
+    std::string invitedPath = repoPathCarla + "invited";
+    fileutils::remove(fileutils::getFullPath(invitedPath, carlaUri));
+
+    Json::Value json;
+    json["action"] = "join";
+    json["uri"] = carlaUri;
+    json["type"] = "member";
+    Json::StreamWriterBuilder wbuilder;
+    wbuilder["commentStyle"] = "None";
+    wbuilder["indentation"] = "";
+    ConversationRepository cr(carlaAccount->weak(), convId);
+    cr.commitMessage(Json::writeString(wbuilder, json));
+
+    // Start Carla, should merge and all messages should be there
+    Manager::instance().sendRegister(carlaId, true);
+    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&] { return carlaConnected; }));
+
+    carlaAccount->sendMessage(convId, "hi"s);
+    errorDetected = false;
+
+    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(60), [&] { return errorDetected; }));
+    DRing::unregisterSignalHandlers();
+}
+
+void
+ConversationTest::testMemberJoinsInviteRemoved()
+{
+    auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
+    auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
+    auto carlaUri = carlaAccount->getUsername();
+    aliceAccount->trackBuddyPresence(carlaUri, true);
+    auto convId = aliceAccount->startConversation();
+
+    std::mutex mtx;
+    std::unique_lock<std::mutex> lk {mtx};
+    std::condition_variable cv;
+    std::map<std::string, std::shared_ptr<DRing::CallbackWrapperBase>> confHandlers;
+    bool conversationReady = false, errorDetected = false, carlaConnected = false;
+    confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::ConversationReady>(
+        [&](const std::string& accountId, const std::string& /* conversationId */) {
+            if (accountId == carlaId) {
+                conversationReady = true;
+                cv.notify_one();
+            }
+        }));
+    confHandlers.insert(
+        DRing::exportable_callback<DRing::ConfigurationSignal::VolatileDetailsChanged>(
+            [&](const std::string&, const std::map<std::string, std::string>&) {
+                auto details = carlaAccount->getVolatileAccountDetails();
+                auto daemonStatus = details[DRing::Account::ConfProperties::Registration::STATUS];
+                if (daemonStatus == "REGISTERED") {
+                    carlaConnected = true;
+                    cv.notify_one();
+                }
+            }));
+    confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::OnConversationError>(
+        [&](const std::string& /* accountId */,
+            const std::string& /* conversationId */,
+            int code,
+            const std::string& /* what */) {
+            if (code == 3)
+                errorDetected = true;
+            cv.notify_one();
+        }));
+    DRing::registerSignalHandlers(confHandlers);
+
+    CPPUNIT_ASSERT(aliceAccount->addConversationMember(convId, carlaUri, false));
+
+    // Cp conversations & convInfo
+    auto repoPathAlice = fileutils::get_data_dir() + DIR_SEPARATOR_STR
+                         + aliceAccount->getAccountID() + DIR_SEPARATOR_STR + "conversations";
+    auto repoPathCarla = fileutils::get_data_dir() + DIR_SEPARATOR_STR
+                         + carlaAccount->getAccountID() + DIR_SEPARATOR_STR + "conversations";
+    std::filesystem::copy(repoPathAlice, repoPathCarla, std::filesystem::copy_options::recursive);
+    auto ciPathAlice = fileutils::get_data_dir() + DIR_SEPARATOR_STR + aliceAccount->getAccountID()
+                       + DIR_SEPARATOR_STR + "convInfo";
+    auto ciPathCarla = fileutils::get_data_dir() + DIR_SEPARATOR_STR + carlaAccount->getAccountID()
+                       + DIR_SEPARATOR_STR + "convInfo";
+    std::filesystem::copy(ciPathAlice, ciPathCarla);
+
+    // Let invited, but add /members + /devices
+    auto cert = carlaAccount->identity().second;
+    auto parentCert = cert->issuer;
+    auto uri = parentCert->getId().toString();
+    std::string membersPath = repoPathCarla + "members" + DIR_SEPARATOR_STR;
+    std::string memberFile = membersPath + DIR_SEPARATOR_STR + carlaUri + ".crt";
+    // Add members/uri.crt
+    fileutils::recursive_mkdir(membersPath, 0700);
+    auto file = fileutils::ofstream(memberFile, std::ios::trunc | std::ios::binary);
+    file << parentCert->toString(true);
+    file.close();
+
+    addAll(carlaAccount, convId);
+    Json::Value json;
+    json["action"] = "join";
+    json["uri"] = carlaUri;
+    json["type"] = "member";
+    commit(carlaAccount, convId, json);
+
+    // Start Carla, should merge and all messages should be there
+    Manager::instance().sendRegister(carlaId, true);
+    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&] { return carlaConnected; }));
+
+    carlaAccount->sendMessage(convId, "hi"s);
+    errorDetected = false;
+
+    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(60), [&] { return errorDetected; }));
     DRing::unregisterSignalHandlers();
 }
 
@@ -2264,7 +3252,8 @@ ConversationTest::testUnknownModeDetected()
     std::unique_lock<std::mutex> lk {mtx};
     std::condition_variable cv;
     std::map<std::string, std::shared_ptr<DRing::CallbackWrapperBase>> confHandlers;
-    bool conversationReady = false, requestReceived = false, memberMessageGenerated = false;
+    bool conversationReady = false, requestReceived = false, memberMessageGenerated = false,
+         errorDetected = false;
     confHandlers.insert(
         DRing::exportable_callback<DRing::ConversationSignal::ConversationRequestReceived>(
             [&](const std::string& /*accountId*/,
@@ -2289,11 +3278,21 @@ ConversationTest::testUnknownModeDetected()
                 cv.notify_one();
             }
         }));
+    confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::OnConversationError>(
+        [&](const std::string& /* accountId */,
+            const std::string& /* conversationId */,
+            int code,
+            const std::string& /* what */) {
+            if (code == 1)
+                errorDetected = true;
+            cv.notify_one();
+        }));
     DRing::registerSignalHandlers(confHandlers);
     CPPUNIT_ASSERT(aliceAccount->addConversationMember(convId, bobUri));
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; }));
+    errorDetected = false;
     bobAccount->acceptConversationRequest(convId);
-    CPPUNIT_ASSERT(!cv.wait_for(lk, std::chrono::seconds(30), [&]() { return conversationReady; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return errorDetected; }));
 }
 
 void
@@ -2418,7 +3417,6 @@ ConversationTest::testBanContact()
 
     memberMessageGenerated = false;
     bobAccount->removeContact(aliceUri, true);
-    cv.wait_for(lk, std::chrono::seconds(10));
     CPPUNIT_ASSERT(
         !cv.wait_for(lk, std::chrono::seconds(30), [&]() { return memberMessageGenerated; }));
     auto repoPath = fileutils::get_data_dir() + DIR_SEPARATOR_STR + bobAccount->getAccountID()
@@ -2440,7 +3438,7 @@ ConversationTest::testOneToOneFetchWithNewMemberRefused()
     std::condition_variable cv;
     std::map<std::string, std::shared_ptr<DRing::CallbackWrapperBase>> confHandlers;
     bool conversationReady = false, requestReceived = false, memberMessageGenerated = false,
-         messageBob = false;
+         messageBob = false, errorDetected = false;
     std::string convId = "";
     confHandlers.insert(DRing::exportable_callback<DRing::ConfigurationSignal::IncomingTrustRequest>(
         [&](const std::string& account_id,
@@ -2472,6 +3470,15 @@ ConversationTest::testOneToOneFetchWithNewMemberRefused()
             }
             cv.notify_one();
         }));
+    confHandlers.insert(DRing::exportable_callback<DRing::ConversationSignal::OnConversationError>(
+        [&](const std::string& /* accountId */,
+            const std::string& /* conversationId */,
+            int code,
+            const std::string& /* what */) {
+            if (code == 3)
+                errorDetected = true;
+            cv.notify_one();
+        }));
     DRing::registerSignalHandlers(confHandlers);
     aliceAccount->addContact(bobUri);
     aliceAccount->sendTrustRequest(bobUri, {});
@@ -2484,9 +3491,9 @@ ConversationTest::testOneToOneFetchWithNewMemberRefused()
         return conversationReady && memberMessageGenerated;
     }));
 
-    messageBob = false;
+    errorDetected = false;
     generateFakeInvite(aliceAccount, convId, carlaUri);
-    CPPUNIT_ASSERT(!cv.wait_for(lk, std::chrono::seconds(30), [&]() { return messageBob; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return errorDetected; }));
 }
 
 void