diff --git a/bin/dbus/cx.ring.Ring.ConfigurationManager.xml b/bin/dbus/cx.ring.Ring.ConfigurationManager.xml
index 5e578d02acb4a213ee90534b2a1e4611cef0683e..d42b6094b5ff48a1b0cffa89ff50544852111014 100644
--- a/bin/dbus/cx.ring.Ring.ConfigurationManager.xml
+++ b/bin/dbus/cx.ring.Ring.ConfigurationManager.xml
@@ -1969,6 +1969,33 @@
            </arg>
        </signal>
 
+       <signal name="conversationMemberEvent" tp:name-for-bindings="conversationMemberEvent">
+           <tp:added version="10.0.0"/>
+           <tp:docstring>
+               Notify clients when a member is invited/added/removed/banned
+           </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="s" name="member_uri">
+               <tp:docstring>
+                   The URI of the member added
+               </tp:docstring>
+           </arg>
+           <arg type="i" name="event">
+               <tp:docstring>
+                   event: 0 = add, 1 = joins, 2 = leave, 3 = banned
+               </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 5771092feb6be72ae8f56f1923825c344514da3e..adb3b3d81e3432c04724a715e93289f3dc38664c 100644
--- a/bin/dbus/dbusclient.cpp
+++ b/bin/dbus/dbusclient.cpp
@@ -301,6 +301,8 @@ DBusClient::initLibrary(int flags)
             bind(&DBusConfigurationManager::conversationReady, confM, _1, _2)),
         exportable_callback<ConversationSignal::ConversationRemoved>(
             bind(&DBusConfigurationManager::conversationRemoved, confM, _1, _2)),
+        exportable_callback<ConversationSignal::ConversationMemberEvent>(
+            bind(&DBusConfigurationManager::conversationMemberEvent, confM, _1, _2, _3, _4)),
     };
 
 #ifdef ENABLE_VIDEO
diff --git a/bin/jni/conversation.i b/bin/jni/conversation.i
index 36fed2c044ece2d2cd28ee18cffdb951f4c97130..40b249a44dca21da1946c5c761f0c418c1ce99d8 100644
--- a/bin/jni/conversation.i
+++ b/bin/jni/conversation.i
@@ -30,6 +30,7 @@ public:
     virtual void conversationRequestReceived(const std::string& /*accountId*/, const std::string& /* conversationId */, std::map<std::string, std::string> /*metadatas*/){}
     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 */){}
 };
 %}
 
@@ -64,4 +65,5 @@ public:
     virtual void conversationRequestReceived(const std::string& /*accountId*/, const std::string& /* conversationId */, std::map<std::string, std::string> /*metadatas*/){}
     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 */){}
 };
\ No newline at end of file
diff --git a/bin/jni/jni_interface.i b/bin/jni/jni_interface.i
index f846733a60b44286cc0a6161541a7a60b1d9db3a..10953578e6c31e25355bf6d8f466dd6d7ec66970 100644
--- a/bin/jni/jni_interface.i
+++ b/bin/jni/jni_interface.i
@@ -318,7 +318,8 @@ void init(ConfigurationCallback* confM, Callback* callM, PresenceCallback* presM
         exportable_callback<ConversationSignal::MessageReceived>(bind(&ConversationCallback::messageReceived, convM, _1, _2, _3)),
         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::ConversationRemoved>(bind(&ConversationCallback::conversationRemoved, convM, _1, _2)),
+        exportable_callback<ConversationSignal::ConversationMemberEvent>(bind(&ConversationCallback::conversationMemberEvent, 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 2123934d38cda4710a55b0d41f4542f0aa701203..04377ee7fe0adfc48d41d4781e4eb9066b44638e 100644
--- a/bin/nodejs/conversation.i
+++ b/bin/nodejs/conversation.i
@@ -30,6 +30,7 @@ public:
     virtual void conversationRequestReceived(const std::string& /*accountId*/, const std::string& /* conversationId */, std::map<std::string, std::string> /*metadatas*/){}
     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 */){}
 };
 %}
 
@@ -77,4 +78,5 @@ public:
     virtual void conversationRequestReceived(const std::string& /*accountId*/, const std::string& /* conversationId */, std::map<std::string, std::string> /*metadatas*/){}
     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 */){}
 };
\ No newline at end of file
diff --git a/src/client/ring_signal.cpp b/src/client/ring_signal.cpp
index a6e8a949669cd24e550079844c4ad40f1de7fdb0..5d612b01dc76f4228b50923224f4003a2fb400a5 100644
--- a/src/client/ring_signal.cpp
+++ b/src/client/ring_signal.cpp
@@ -133,6 +133,7 @@ getSignalHandlers()
         exported_callback<DRing::ConversationSignal::ConversationRequestReceived>(),
         exported_callback<DRing::ConversationSignal::ConversationReady>(),
         exported_callback<DRing::ConversationSignal::ConversationRemoved>(),
+        exported_callback<DRing::ConversationSignal::ConversationMemberEvent>(),
     };
 
     return handlers;
diff --git a/src/dring/conversation_interface.h b/src/dring/conversation_interface.h
index ed7927e0cd6769c7415d5cb8e41ab3f166aaf3d5..5bda2bed21ddac94219da4205f658fcf114ed1b7 100644
--- a/src/dring/conversation_interface.h
+++ b/src/dring/conversation_interface.h
@@ -59,9 +59,9 @@ DRING_PUBLIC void sendMessage(const std::string& accountId,
                               const std::string& message,
                               const std::string& parent);
 DRING_PUBLIC uint32_t loadConversationMessages(const std::string& accountId,
-                                           const std::string& conversationId,
-                                           const std::string& fromMessage,
-                                           size_t n);
+                                               const std::string& conversationId,
+                                               const std::string& fromMessage,
+                                               size_t n);
 
 struct DRING_PUBLIC ConversationSignal
 {
@@ -99,6 +99,14 @@ struct DRING_PUBLIC ConversationSignal
         using cb_type = void(const std::string& /*accountId*/,
                              const std::string& /* conversationId */);
     };
+    struct DRING_PUBLIC ConversationMemberEvent
+    {
+        constexpr static const char* name = "ConversationMemberEvent";
+        using cb_type = void(const std::string& /*accountId*/,
+                             const std::string& /* conversationId */,
+                             const std::string& /* memberUri */,
+                             int /* event 0 = add, 1 = joins, 2 = leave, 3 = banned */);
+    };
 };
 
 } // namespace DRing
diff --git a/src/gittransport.cpp b/src/gittransport.cpp
index 473e6053354cee760a74c5b07dd094d7a2c4f93b..5d5eb42b9c7af8b7a94a97fd7e90d4c9bc08546f 100644
--- a/src/gittransport.cpp
+++ b/src/gittransport.cpp
@@ -120,7 +120,7 @@ P2PStreamWrite(git_smart_subtransport_stream* stream, const char* buffer, size_t
     }
     std::error_code ec;
     auto written = fs->socket->write(reinterpret_cast<const unsigned char*>(buffer), len, ec);
-    if (written < 0) {
+    if (ec) {
         giterr_set_str(GITERR_NET, ec.message().c_str());
         return -1;
     }
diff --git a/src/ice_transport.cpp b/src/ice_transport.cpp
index 93dec33835cd021c1ef66da04a7d2c9eac4aa920..302db1b3ec7a5f9fd37502bd1a153d543b39531e 100644
--- a/src/ice_transport.cpp
+++ b/src/ice_transport.cpp
@@ -942,7 +942,7 @@ void
 IceTransport::Impl::setDefaultRemoteAddress(unsigned comp_id, const IpAddr& addr)
 {
     // Component ID must be valid.
-    assert(comp_id < component_count_);
+    assert(static_cast<unsigned>(comp_id) < component_count_);
 
     iceDefaultRemoteAddr_[comp_id] = addr;
     // The port does not matter. Set it 0 to avoid confusion.
@@ -953,7 +953,7 @@ const IpAddr&
 IceTransport::Impl::getDefaultRemoteAddress(unsigned comp_id) const
 {
     // Component ID must be valid.
-    assert(comp_id < component_count_);
+    assert(static_cast<unsigned>(comp_id) < component_count_);
 
     return iceDefaultRemoteAddr_[comp_id];
 }
diff --git a/src/jamidht/channeled_transfers.cpp b/src/jamidht/channeled_transfers.cpp
index ccf022ce696b9dd821ce831fad223c4aee0f8695..461ec4f5d08c675ba350b2d5b5ae952b12c9979a 100644
--- a/src/jamidht/channeled_transfers.cpp
+++ b/src/jamidht/channeled_transfers.cpp
@@ -31,8 +31,8 @@ namespace jami {
 
 ChanneledOutgoingTransfer::ChanneledOutgoingTransfer(const std::shared_ptr<ChannelSocket>& channel,
                                                      OnStateChangedCb&& cb)
-    : channel_(channel)
-    , stateChangedCb_(cb)
+    : stateChangedCb_(cb)
+    , channel_(channel)
 {}
 
 ChanneledOutgoingTransfer::~ChanneledOutgoingTransfer()
diff --git a/src/jamidht/connectionmanager.cpp b/src/jamidht/connectionmanager.cpp
index 6d6484d62a1fedef2a58504ab907f6a6aab645e3..0abc7872185125d8e3a8ef678458173477e94fd6 100644
--- a/src/jamidht/connectionmanager.cpp
+++ b/src/jamidht/connectionmanager.cpp
@@ -797,7 +797,7 @@ ConnectionManager::Impl::onRequestOnNegoDone(const PeerConnectionRequest& req)
 
 void
 ConnectionManager::Impl::onDhtPeerRequest(const PeerConnectionRequest& req,
-                                          const std::shared_ptr<dht::crypto::Certificate>& cert)
+                                          const std::shared_ptr<dht::crypto::Certificate>& /*cert*/)
 {
     auto deviceId = req.from.toString();
     JAMI_INFO() << account << "New connection requested by " << deviceId.c_str();
diff --git a/src/jamidht/conversationrepository.cpp b/src/jamidht/conversationrepository.cpp
index 05e000c91b56bc56dda7e530ca52fc09d3275bf6..94abeebc22a9de180d6203b0bc9c6d70bda49de1 100644
--- a/src/jamidht/conversationrepository.cpp
+++ b/src/jamidht/conversationrepository.cpp
@@ -427,8 +427,7 @@ 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;
     }
@@ -486,12 +485,7 @@ 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");
         }
@@ -523,12 +517,7 @@ 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;
@@ -1405,11 +1394,7 @@ 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 {
@@ -1482,7 +1467,6 @@ 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) {
@@ -1890,12 +1874,7 @@ 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");
     }
@@ -1931,10 +1910,7 @@ 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());
@@ -2078,8 +2054,7 @@ 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;
     }
@@ -2116,8 +2091,7 @@ 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);
diff --git a/src/jamidht/jamiaccount.cpp b/src/jamidht/jamiaccount.cpp
index e77c5a78cd30ffd1d0da350e41100efe5dbb5d9b..a56372339bbe08c761ea838000793f7628ba7952 100644
--- a/src/jamidht/jamiaccount.cpp
+++ b/src/jamidht/jamiaccount.cpp
@@ -2580,13 +2580,27 @@ JamiAccount::doRegister_()
                         return;
                     }
 
-                    if (!isConversation(conversationId)) {
-                        JAMI_WARN("[Account %s] Git server requested, but for a non existing "
-                                  "conversation (%s)",
-                                  getAccountID().c_str(),
-                                  conversationId.c_str());
-                        return;
+                    {
+                        // Check if pull from banned device
+                        std::unique_lock<std::mutex> lk(conversationsMtx_);
+                        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());
+                            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());
+                            return;
+                        }
                     }
+
+
                     if (gitSocket(deviceId.toString(), conversationId) == channel) {
                         // The onConnectionReady is already used as client (for retrieving messages)
                         // So it's not the server socket
@@ -4187,6 +4201,8 @@ JamiAccount::addConversationMember(const std::string& conversationId,
             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());
@@ -4210,6 +4226,8 @@ JamiAccount::removeConversationMember(const std::string& conversationId,
                     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);
@@ -4249,6 +4267,8 @@ JamiAccount::sendMessage(const std::string& conversationId,
     auto conversation = conversations_.find(conversationId);
     if (conversation != conversations_.end() && conversation->second) {
         auto commitId = conversation->second->sendMessage(message, type, parent);
+        if (!announce)
+            return;
         if (!commitId.empty()) {
             conversation->second->loadMessages([w=weak(), conversationId] (auto&& messages) {
                 auto shared = w.lock();
@@ -4336,16 +4356,18 @@ JamiAccount::fetchNewCommits(const std::string& peer,
             return;
         }
 
-        auto announceMessages = [w = weak(), conversationId](const std::vector<std::map<std::string, std::string>>& messages) {
+        auto announceMessages = [w = weak(), conversationId](
+                                    const std::vector<std::map<std::string, std::string>>& messages) {
             auto shared = w.lock();
             if (!shared)
                 return;
             std::unique_lock<std::mutex> lk(shared->conversationsMtx_);
             auto conversation = shared->conversations_.find(conversationId);
             if (conversation != shared->conversations_.end() && conversation->second) {
-                // Do a diff between last message, and current new message when merged
                 lk.unlock();
                 for (const auto& message : messages) {
+                    if (message.at("type") == "member")
+                        shared->announceMemberMessage(conversationId, message);
                     JAMI_DBG("[Account %s] New message received for conversation %s with id %s",
                              shared->getAccountID().c_str(),
                              conversationId.c_str(),
@@ -4929,7 +4951,6 @@ JamiAccount::cacheSyncConnection(std::shared_ptr<ChannelSocket>&& socket,
                     std::lock_guard<std::mutex> lk(conversationsRequestsMtx_);
                     conversationsRequests_.erase(convId);
                 }
-                auto itConv = conversations_.find(convId);
                 if (not removed) {
                     if (!isConversation(convId)) {
                         {
@@ -5219,4 +5240,31 @@ JamiAccount::getOneToOneConversation(const std::string& uri) const
     return {};
 }
 
+void
+JamiAccount::announceMemberMessage(const std::string& convId,
+                                   const std::map<std::string, std::string>& message) const
+{
+    if (message.at("type") == "member") {
+        if (message.find("uri") == message.end() || message.find("action") == message.end())
+            return;
+        auto uri = message.at("uri");
+        auto actionStr = message.at("action");
+        auto action = 0;
+        if (actionStr == "add")
+            action = 0;
+        else if (actionStr == "join")
+            action = 1;
+        else if (actionStr == "remove")
+            action = 2;
+        else if (actionStr == "ban")
+            action = 3;
+        else
+            return;
+
+        emitSignal<DRing::ConversationSignal::ConversationMemberEvent>(getAccountID(),
+                                                                       convId,
+                                                                       uri,
+                                                                       action);
+    }
+}
 } // namespace jami
diff --git a/src/jamidht/jamiaccount.h b/src/jamidht/jamiaccount.h
index 17a669568f7c90e4b634c0f9fb0aad96b8c94c5a..e4f1e7055dd54d788bf8ba1dc3068919862e0fbd 100644
--- a/src/jamidht/jamiaccount.h
+++ b/src/jamidht/jamiaccount.h
@@ -969,6 +969,9 @@ private:
      * @return the conversation id if found else empty
      */
     std::string getOneToOneConversation(const std::string& uri) const;
+
+    void announceMemberMessage(const std::string& convId,
+                               const std::map<std::string, std::string>& message) const;
 };
 
 static inline std::ostream&
diff --git a/src/manager.cpp b/src/manager.cpp
index ac4c0236bc92854b8ce07afb4586eb68bba84e1b..caee34f38291b27bf6e8d8353dc3839a6b6eb63f 100644
--- a/src/manager.cpp
+++ b/src/manager.cpp
@@ -856,7 +856,6 @@ Manager::init(const std::string& config_file)
 void
 Manager::finish() noexcept
 {
-    git_libgit2_shutdown();
     bool expected = false;
     if (not pimpl_->finished_.compare_exchange_strong(expected, true))
         return;
@@ -911,6 +910,7 @@ Manager::finish() noexcept
         }
 
         pj_shutdown();
+        git_libgit2_shutdown();
 
         if (!pimpl_->ioContext_->stopped()) {
             pimpl_->ioContext_->reset(); // allow to finish
diff --git a/test/unitTest/conversation/conversation.cpp b/test/unitTest/conversation/conversation.cpp
index 4855318ddad22799a8030766bbf51b92b8c8a865..9cad8f1255e7adc5f4417d089778f76434f1c0ab 100644
--- a/test/unitTest/conversation/conversation.cpp
+++ b/test/unitTest/conversation/conversation.cpp
@@ -124,6 +124,7 @@ private:
     void testOneToOneFetchWithNewMemberRefused();
     void testAddOfflineContactThenConnect();
     void testDeclineTrustRequestDoNotGenerateAnother();
+    void testConversationMemberEvent();
 
     CPPUNIT_TEST_SUITE(ConversationTest);
     CPPUNIT_TEST(testCreateConversation);
@@ -161,6 +162,8 @@ private:
     CPPUNIT_TEST(testOneToOneFetchWithNewMemberRefused);
     CPPUNIT_TEST(testAddOfflineContactThenConnect);
     CPPUNIT_TEST(testDeclineTrustRequestDoNotGenerateAnother);
+    CPPUNIT_TEST(testConversationMemberEvent);
+    CPPUNIT_TEST_SUITE_END();
 };
 
 CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(ConversationTest, ConversationTest::name());
@@ -231,6 +234,7 @@ ConversationTest::setUp()
 void
 ConversationTest::tearDown()
 {
+    DRing::unregisterSignalHandlers();
     auto currentAccSize = Manager::instance().getAccountList().size();
     Manager::instance().removeAccount(aliceId, true);
     Manager::instance().removeAccount(bobId, true);
@@ -448,11 +452,14 @@ ConversationTest::testRemoveConversationWithMember()
         [&](const std::string& accountId,
             const std::string& conversationId,
             std::map<std::string, std::string> message) {
-            if (accountId == aliceId && conversationId == convId && message["type"] == "member") {
+            auto itFind = message.find("type");
+            if (itFind == message.end())
+                return;
+            if (accountId == aliceId && conversationId == convId && itFind->second == "member") {
                 memberMessageGenerated = true;
                 cv.notify_one();
             } else if (accountId == bobId && conversationId == convId
-                       && message["type"] == "member") {
+                       && itFind->second == "member") {
                 bobSeeAliceRemoved = true;
                 cv.notify_one();
             }
@@ -460,7 +467,8 @@ ConversationTest::testRemoveConversationWithMember()
     DRing::registerSignalHandlers(confHandlers);
 
     CPPUNIT_ASSERT(aliceAccount->addConversationMember(convId, bobUri));
-    CPPUNIT_ASSERT(memberMessageGenerated);
+    CPPUNIT_ASSERT(
+        cv.wait_for(lk, std::chrono::seconds(30), [&]() { return memberMessageGenerated; }));
 
     // Assert that repository exists
     auto repoPath = fileutils::get_data_dir() + DIR_SEPARATOR_STR + aliceAccount->getAccountID()
@@ -528,7 +536,8 @@ ConversationTest::testAddMember()
         }));
     DRing::registerSignalHandlers(confHandlers);
     CPPUNIT_ASSERT(aliceAccount->addConversationMember(convId, bobUri));
-    CPPUNIT_ASSERT(memberMessageGenerated);
+    CPPUNIT_ASSERT(
+        cv.wait_for(lk, std::chrono::seconds(30), [&]() { return memberMessageGenerated; }));
     // Assert that repository exists
     auto repoPath = fileutils::get_data_dir() + DIR_SEPARATOR_STR + aliceAccount->getAccountID()
                     + DIR_SEPARATOR_STR + "conversations" + DIR_SEPARATOR_STR + convId;
@@ -709,12 +718,10 @@ ConversationTest::testSendMessage()
     auto convId = aliceAccount->startConversation();
 
     CPPUNIT_ASSERT(aliceAccount->addConversationMember(convId, bobUri));
-    cv.wait_for(lk, std::chrono::seconds(30));
-    CPPUNIT_ASSERT(requestReceived);
+    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; }));
 
     bobAccount->acceptConversationRequest(convId);
-    cv.wait_for(lk, std::chrono::seconds(30));
-    CPPUNIT_ASSERT(conversationReady);
+    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()
@@ -847,8 +854,7 @@ ConversationTest::testGetRequests()
     auto convId = aliceAccount->startConversation();
 
     CPPUNIT_ASSERT(aliceAccount->addConversationMember(convId, bobUri));
-    cv.wait_for(lk, std::chrono::seconds(30));
-    CPPUNIT_ASSERT(requestReceived);
+    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; }));
 
     auto requests = bobAccount->getConversationRequests();
     CPPUNIT_ASSERT(requests.size() == 1);
@@ -880,8 +886,7 @@ ConversationTest::testDeclineRequest()
     auto convId = aliceAccount->startConversation();
 
     CPPUNIT_ASSERT(aliceAccount->addConversationMember(convId, bobUri));
-    cv.wait_for(lk, std::chrono::seconds(30));
-    CPPUNIT_ASSERT(requestReceived);
+    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; }));
 
     bobAccount->declineConversationRequest(convId);
     // Decline request
@@ -1028,8 +1033,7 @@ ConversationTest::testPingPongMessages()
     DRing::registerSignalHandlers(confHandlers);
     auto convId = aliceAccount->startConversation();
     CPPUNIT_ASSERT(aliceAccount->addConversationMember(convId, bobUri));
-    cv.wait_for(lk, std::chrono::seconds(30));
-    CPPUNIT_ASSERT(requestReceived);
+    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; }));
     messageAliceReceived = 0;
     bobAccount->acceptConversationRequest(convId);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(60), [&]() {
@@ -1171,7 +1175,9 @@ ConversationTest::testBanDevice()
         }));
     DRing::registerSignalHandlers(confHandlers);
     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);
     CPPUNIT_ASSERT(
@@ -1210,6 +1216,7 @@ ConversationTest::testBanDevice()
     // Now check that alice, has the only admin, can remove bob
     memberMessageGenerated = false;
     voteMessageGenerated = false;
+    bobGetMessage = false;
     auto members = aliceAccount->getConversationMembers(convId);
     CPPUNIT_ASSERT(members.size() == 2);
     aliceAccount->removeConversationMember(convId, bobDeviceId, true);
@@ -1270,7 +1277,9 @@ ConversationTest::testMemberTryToRemoveAdmin()
         }));
     DRing::registerSignalHandlers(confHandlers);
     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);
     CPPUNIT_ASSERT(
@@ -1329,7 +1338,9 @@ ConversationTest::testBannedMemberCannotSendMessage()
         }));
     DRing::registerSignalHandlers(confHandlers);
     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);
     CPPUNIT_ASSERT(
@@ -1395,7 +1406,9 @@ ConversationTest::testAddBannedMember()
         }));
     DRing::registerSignalHandlers(confHandlers);
     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);
     CPPUNIT_ASSERT(
@@ -1454,39 +1467,6 @@ ConversationTest::generateFakeVote(std::shared_ptr<JamiAccount> account,
 
     account->sendMessage(convId, "trigger the fake history to be pulled");
 }
-void
-ConversationTest::addAll(std::shared_ptr<JamiAccount> account, const std::string& convId)
-{
-    auto repoPath = fileutils::get_data_dir() + DIR_SEPARATOR_STR + account->getAccountID()
-                    + DIR_SEPARATOR_STR + "conversations" + DIR_SEPARATOR_STR + convId;
-
-    git_repository* repo = nullptr;
-    if (git_repository_open(&repo, repoPath.c_str()) != 0)
-        return;
-    GitRepository rep = {std::move(repo), git_repository_free};
-
-    // git add -A
-    git_index* index_ptr = nullptr;
-    git_strarray array = {0};
-    if (git_repository_index(&index_ptr, repo) < 0)
-        return;
-    GitIndex index {index_ptr, git_index_free};
-    git_index_add_all(index.get(), &array, 0, nullptr, nullptr);
-    git_index_write(index.get());
-}
-
-void
-ConversationTest::commit(std::shared_ptr<JamiAccount> account,
-                         const std::string& convId,
-                         Json::Value& message)
-{
-    ConversationRepository cr(account->weak(), convId);
-
-    Json::StreamWriterBuilder wbuilder;
-    wbuilder["commentStyle"] = "None";
-    wbuilder["indentation"] = "";
-    cr.commitMessage(Json::writeString(wbuilder, message));
-}
 
 void
 ConversationTest::generateFakeInvite(std::shared_ptr<JamiAccount> account,
@@ -1645,14 +1625,18 @@ ConversationTest::testMemberCannotBanOther()
         }));
     DRing::registerSignalHandlers(confHandlers);
     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);
     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; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() {
+        return requestReceived && memberMessageGenerated;
+    }));
     memberMessageGenerated = false;
     messageBobReceived = false;
     carlaAccount->acceptConversationRequest(convId);
@@ -1719,14 +1703,18 @@ ConversationTest::testCheckAdminFakeAVoteIsDetected()
         }));
     DRing::registerSignalHandlers(confHandlers);
     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);
     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; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() {
+        return requestReceived && memberMessageGenerated;
+    }));
     memberMessageGenerated = false;
     messageBobReceived = false;
     carlaAccount->acceptConversationRequest(convId);
@@ -1835,8 +1823,7 @@ ConversationTest::testPlainTextNoBadFile()
     auto convId = aliceAccount->startConversation();
 
     CPPUNIT_ASSERT(aliceAccount->addConversationMember(convId, bobUri));
-    cv.wait_for(lk, std::chrono::seconds(30));
-    CPPUNIT_ASSERT(requestReceived);
+    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; }));
 
     bobAccount->acceptConversationRequest(convId);
     cv.wait_for(lk, std::chrono::seconds(30));
@@ -1909,14 +1896,18 @@ ConversationTest::testVoteNoBadFile()
         }));
     DRing::registerSignalHandlers(confHandlers);
     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);
     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; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() {
+        return requestReceived && memberMessageGenerated;
+    }));
     memberMessageGenerated = false;
     messageBobReceived = false;
     carlaAccount->acceptConversationRequest(convId);
@@ -2125,9 +2116,6 @@ ConversationTest::testAddContact()
     auto repoPath = fileutils::get_data_dir() + DIR_SEPARATOR_STR + aliceAccount->getAccountID()
                     + DIR_SEPARATOR_STR + "conversations" + DIR_SEPARATOR_STR + convId;
     CPPUNIT_ASSERT(fileutils::isDirectory(repoPath));
-    // Check created files
-    auto bobInvited = repoPath + DIR_SEPARATOR_STR + "invited" + DIR_SEPARATOR_STR + bobUri;
-    CPPUNIT_ASSERT(fileutils::isFile(bobInvited));
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return requestReceived; }));
     CPPUNIT_ASSERT(bobAccount->acceptTrustRequest(aliceUri));
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() {
@@ -2136,8 +2124,6 @@ ConversationTest::testAddContact()
     auto clonedPath = fileutils::get_data_dir() + DIR_SEPARATOR_STR + bobAccount->getAccountID()
                       + DIR_SEPARATOR_STR + "conversations" + DIR_SEPARATOR_STR + convId;
     CPPUNIT_ASSERT(fileutils::isDirectory(clonedPath));
-    bobInvited = clonedPath + DIR_SEPARATOR_STR + "invited" + DIR_SEPARATOR_STR + bobUri;
-    CPPUNIT_ASSERT(!fileutils::isFile(bobInvited));
     auto bobMember = clonedPath + DIR_SEPARATOR_STR + "members" + DIR_SEPARATOR_STR + bobUri
                      + ".crt";
     CPPUNIT_ASSERT(fileutils::isFile(bobMember));
@@ -2503,6 +2489,68 @@ ConversationTest::testOneToOneFetchWithNewMemberRefused()
     CPPUNIT_ASSERT(!cv.wait_for(lk, std::chrono::seconds(30), [&]() { return messageBob; }));
 }
 
+void
+ConversationTest::testConversationMemberEvent()
+{
+    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, memberAddGenerated = 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::ConversationMemberEvent>(
+            [&](const std::string& accountId,
+                const std::string& conversationId,
+                const std::string& uri,
+                int event) {
+                if (accountId == aliceId && conversationId == convId && uri == bobUri
+                    && event == 0) {
+                    memberAddGenerated = true;
+                }
+                cv.notify_one();
+            }));
+    DRing::registerSignalHandlers(confHandlers);
+    CPPUNIT_ASSERT(aliceAccount->addConversationMember(convId, bobUri));
+    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&]() { return memberAddGenerated; }));
+    // Assert that repository exists
+    auto repoPath = fileutils::get_data_dir() + DIR_SEPARATOR_STR + aliceAccount->getAccountID()
+                    + DIR_SEPARATOR_STR + "conversations" + DIR_SEPARATOR_STR + convId;
+    CPPUNIT_ASSERT(fileutils::isDirectory(repoPath));
+    // Check created files
+    auto bobInvited = repoPath + DIR_SEPARATOR_STR + "invited" + DIR_SEPARATOR_STR + bobUri;
+    CPPUNIT_ASSERT(fileutils::isFile(bobInvited));
+    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; }));
+    auto clonedPath = fileutils::get_data_dir() + DIR_SEPARATOR_STR + bobAccount->getAccountID()
+                      + DIR_SEPARATOR_STR + "conversations" + DIR_SEPARATOR_STR + convId;
+    CPPUNIT_ASSERT(fileutils::isDirectory(clonedPath));
+    bobInvited = clonedPath + DIR_SEPARATOR_STR + "invited" + DIR_SEPARATOR_STR + bobUri;
+    CPPUNIT_ASSERT(!fileutils::isFile(bobInvited));
+    auto bobMember = clonedPath + DIR_SEPARATOR_STR + "members" + DIR_SEPARATOR_STR + bobUri
+                     + ".crt";
+    CPPUNIT_ASSERT(fileutils::isFile(bobMember));
+}
+
 void
 ConversationTest::testAddOfflineContactThenConnect()
 {