diff --git a/CMakeLists.txt b/CMakeLists.txt
index f536e45a943d4d8e53777fc3ec6f5e1a9a26ae1e..d980f58567add76c5e38ae53e56419598fc8d38c 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,7 +1,7 @@
 cmake_minimum_required(VERSION 3.16)
 
 project(jami-core
-    VERSION 13.11.0
+    VERSION 14.0.0
     LANGUAGES C CXX)
 set(PACKAGE_NAME "Jami Daemon")
 set (CMAKE_CXX_STANDARD 17)
diff --git a/bin/dbus/cx.ring.Ring.ConfigurationManager.xml b/bin/dbus/cx.ring.Ring.ConfigurationManager.xml
index c1643d2d843e416c52cee89f392b246f71daf87f..e7f7aa8973d630232868bec4f72a153874e68072 100644
--- a/bin/dbus/cx.ring.Ring.ConfigurationManager.xml
+++ b/bin/dbus/cx.ring.Ring.ConfigurationManager.xml
@@ -1848,9 +1848,21 @@
             <arg type="u" name="id" direction="out"/>
         </method>
 
-        <method name="loadConversationUntil" tp:name-for-bindings="loadConversationUntil">
-            <tp:added version="10.0.0"/>
-            <tp:docstring>
+       <method name="loadConversation" tp:name-for-bindings="loadConversation">
+           <tp:added version="14.0.0"/>
+           <tp:docstring>
+               Load messages from a conversation
+           </tp:docstring>
+           <arg type="s" name="accountId" direction="in"/>
+           <arg type="s" name="conversationId" direction="in"/>
+           <arg type="s" name="fromMessage" direction="in"/>
+           <arg type="u" name="n" direction="in"/>
+           <arg type="u" name="id" direction="out"/>
+       </method>
+
+       <method name="loadConversationUntil" tp:name-for-bindings="loadConversationUntil">
+           <tp:added version="10.0.0"/>
+           <tp:docstring>
                Load messages from a conversation
             </tp:docstring>
             <arg type="s" name="accountId" direction="in"/>
@@ -1873,6 +1885,15 @@
             <arg type="u" name="count" direction="out"/>
         </method>
 
+        <method name="clearCache" tp:name-for-bindings="clearCache">
+            <tp:added version="14.0.0"/>
+            <tp:docstring>
+               Clear interactions loaded from daemon. Used by dbus to reload interactions
+            </tp:docstring>
+            <arg type="s" name="accountId" direction="in"/>
+            <arg type="s" name="conversationId" direction="in"/>
+        </method>
+
         <method name="searchConversation" tp:name-for-bindings="searchConversation">
             <tp:added version="13.4.0"/>
             <tp:docstring>
@@ -1992,9 +2013,37 @@
             </arg>
         </signal>
 
-        <signal name="messagesFound" tp:name-for-bindings="messagesFound">
-            <tp:added version="10.0.0"/>
-            <tp:docstring>
+       <signal name="swarmLoaded" tp:name-for-bindings="swarmLoaded">
+           <tp:added version="14.0.0"/>
+           <tp:docstring>
+               Notify clients when a conversation is loaded
+           </tp:docstring>
+           <arg type="u" name="id">
+               <tp:docstring>
+                   Id of the related loadConversationMessages's request
+               </tp:docstring>
+           </arg>
+           <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>
+           <annotation name="org.qtproject.QtDBus.QtTypeName.Out3" value="VectorSwarmMessage"/>
+           <arg type="a(sssa{ss}aa{ss}aa{ss})" name="messages">
+               <tp:docstring>
+                    Messages of the conversation
+               </tp:docstring>
+           </arg>
+       </signal>
+
+       <signal name="messagesFound" tp:name-for-bindings="messagesFound">
+           <tp:added version="10.0.0"/>
+           <tp:docstring>
                Notify clients when messages matching a regex are found
             </tp:docstring>
             <arg type="u" name="id">
@@ -2043,9 +2092,111 @@
             </arg>
         </signal>
 
-        <signal name="conversationProfileUpdated" tp:name-for-bindings="conversationProfileUpdated">
-            <tp:added version="13.4.0"/>
-            <tp:docstring>
+       <signal name="swarmMessageReceived" tp:name-for-bindings="swarmMessageReceived">
+           <tp:added version="14.0.0"/>
+           <tp:docstring>
+               Notify clients when a conversation receives a new message
+           </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>
+           <annotation name="org.qtproject.QtDBus.QtTypeName.Out2" value="SwarmMessage"/>
+           <arg type="(sssa{ss}aa{ss}aa{ss})" name="message">
+               <tp:docstring>
+                    The new message
+               </tp:docstring>
+           </arg>
+       </signal>
+
+       <signal name="swarmMessageUpdated" tp:name-for-bindings="swarmMessageUpdated">
+           <tp:added version="14.0.0"/>
+           <tp:docstring>
+               Notify clients when a conversation receives a new message
+           </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>
+           <annotation name="org.qtproject.QtDBus.QtTypeName.Out2" value="SwarmMessage"/>
+           <arg type="(sssa{ss}aa{ss}aa{ss})" name="message">
+               <tp:docstring>
+                    The new message
+               </tp:docstring>
+           </arg>
+       </signal>
+
+       <signal name="reactionAdded" tp:name-for-bindings="reactionAdded">
+           <tp:added version="14.0.0"/>
+           <tp:docstring>
+               Notify clients when a conversation receives a new message
+           </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="message_id">
+               <tp:docstring>
+                   Conversation id
+               </tp:docstring>
+           </arg>
+           <annotation name="org.qtproject.QtDBus.QtTypeName.Out3" value="MapStringString"/>
+           <arg type="a{ss}" name="reaction">
+               <tp:docstring>
+                    The new message
+               </tp:docstring>
+           </arg>
+       </signal>
+
+       <signal name="reactionRemoved" tp:name-for-bindings="reactionRemoved">
+           <tp:added version="14.0.0"/>
+           <tp:docstring>
+               Notify clients when a conversation receives a new message
+           </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="message_id">
+               <tp:docstring>
+                   Message's id
+               </tp:docstring>
+           </arg>
+           <arg type="s" name="reaction_id">
+               <tp:docstring>
+                   Reaction's id
+               </tp:docstring>
+           </arg>
+       </signal>
+
+
+       <signal name="conversationProfileUpdated" tp:name-for-bindings="conversationProfileUpdated">
+           <tp:added version="13.4.0"/>
+           <tp:docstring>
                Notify clients when a conversation got its profile changed.
             </tp:docstring>
             <arg type="s" name="account_id">
diff --git a/bin/dbus/dbusconfigurationmanager.hpp b/bin/dbus/dbusconfigurationmanager.hpp
index 8fddb461bfe834857a7e38674312fc7b75929c06..638d08fad8a7d69c6afe4b74a7d747d61e6c43b5 100644
--- a/bin/dbus/dbusconfigurationmanager.hpp
+++ b/bin/dbus/dbusconfigurationmanager.hpp
@@ -29,6 +29,7 @@
 class DBusConfigurationManager : public sdbus::AdaptorInterfaces<cx::ring::Ring::ConfigurationManager_adaptor>
 {
 public:
+    using DBusSwarmMessage = sdbus::Struct<std::string, std::string, std::string, std::map<std::string, std::string>, std::vector<std::map<std::string, std::string>>, std::vector<std::map<std::string, std::string>>>;
     DBusConfigurationManager(sdbus::IConnection& connection)
         : AdaptorInterfaces(connection, "/cx/ring/Ring/ConfigurationManager")
     {
@@ -931,6 +932,15 @@ public:
         return libjami::loadConversationMessages(accountId, conversationId, fromMessage, n);
     }
 
+    uint32_t
+    loadConversation(const std::string& accountId,
+                             const std::string& conversationId,
+                             const std::string& fromMessage,
+                             const uint32_t& n)
+    {
+        return libjami::loadConversation(accountId, conversationId, fromMessage, n);
+    }
+
     uint32_t
     loadConversationUntil(const std::string& accountId,
                           const std::string& conversationId,
@@ -950,6 +960,13 @@ public:
         return libjami::countInteractions(accountId, conversationId, toId, fromId, authorUri);
     }
 
+    void
+    clearCache(const std::string& accountId,
+                      const std::string& conversationId)
+    {
+        return libjami::clearCache(accountId, conversationId);
+    }
+
     uint32_t
     searchConversation(const std::string& accountId,
                        const std::string& conversationId,
@@ -1122,10 +1139,30 @@ private:
         const std::map<std::string, SharedCallback> convEvHandlers = {
             exportable_serialized_callback<ConversationSignal::ConversationLoaded>(
                 std::bind(&DBusConfigurationManager::emitConversationLoaded, this, _1, _2, _3, _4)),
+            exportable_serialized_callback<ConversationSignal::SwarmLoaded>([this](const uint32_t& id, const std::string& account_id, const std::string& conversation_id, const std::vector<libjami::SwarmMessage>& messages) {
+                std::vector<DBusSwarmMessage> msgList;
+                for (const auto& message: messages) {
+                    DBusSwarmMessage msg {message.id, message.type, message.linearizedParent, message.body, message.reactions, message.editions};
+                    msgList.push_back(msg);
+                }
+                DBusConfigurationManager::emitSwarmLoaded(id, account_id, conversation_id, msgList);
+            }),
             exportable_serialized_callback<ConversationSignal::MessagesFound>(
                 std::bind(&DBusConfigurationManager::emitMessagesFound, this, _1, _2, _3, _4)),
             exportable_serialized_callback<ConversationSignal::MessageReceived>(
                 std::bind(&DBusConfigurationManager::emitMessageReceived, this, _1, _2, _3)),
+            exportable_serialized_callback<ConversationSignal::SwarmMessageReceived>([this](const std::string& account_id, const std::string& conversation_id, const libjami::SwarmMessage& message) {
+                DBusSwarmMessage msg {message.id, message.type, message.linearizedParent, message.body, message.reactions, message.editions};
+                DBusConfigurationManager::emitSwarmMessageReceived(account_id, conversation_id, msg);
+            }),
+            exportable_serialized_callback<ConversationSignal::SwarmMessageUpdated>([this](const std::string& account_id, const std::string& conversation_id, const libjami::SwarmMessage& message) {
+                DBusSwarmMessage msg {message.id, message.type, message.linearizedParent, message.body, message.reactions, message.editions};
+                DBusConfigurationManager::emitSwarmMessageUpdated(account_id, conversation_id, msg);
+            }),
+            exportable_serialized_callback<ConversationSignal::ReactionAdded>(
+                std::bind(&DBusConfigurationManager::emitReactionAdded, this, _1, _2, _3, _4)),
+            exportable_serialized_callback<ConversationSignal::ReactionRemoved>(
+                std::bind(&DBusConfigurationManager::emitReactionRemoved, this, _1, _2, _3, _4)),
             exportable_serialized_callback<ConversationSignal::ConversationProfileUpdated>(
                 std::bind(&DBusConfigurationManager::emitConversationProfileUpdated, this, _1, _2, _3)),
             exportable_serialized_callback<ConversationSignal::ConversationRequestReceived>(
diff --git a/bin/jni/conversation.i b/bin/jni/conversation.i
index 30c10efdee0ae93dcb6c89e42bfc9d187aa3601d..fdecb07e2ed4b20922d1bc4c68a454a3a41e9ba4 100644
--- a/bin/jni/conversation.i
+++ b/bin/jni/conversation.i
@@ -26,8 +26,13 @@ class ConversationCallback {
 public:
     virtual ~ConversationCallback(){}
     virtual void conversationLoaded(uint32_t /* id */, const std::string& /*accountId*/, const std::string& /* conversationId */, std::vector<std::map<std::string, std::string>> /*messages*/){}
+    virtual void swarmLoaded(uint32_t /* id */, const std::string& /*accountId*/, const std::string& /* conversationId */, std::vector<libjami::SwarmMessage> /*messages*/){}
     virtual void messagesFound(uint32_t /* id */, const std::string& /*accountId*/, const std::string& /* conversationId */, std::vector<std::map<std::string, std::string>> /*messages*/){}
     virtual void messageReceived(const std::string& /*accountId*/, const std::string& /* conversationId */, std::map<std::string, std::string> /*message*/){}
+    virtual void swarmMessageReceived(const std::string& /*accountId*/, const std::string& /* conversationId */, const libjami::SwarmMessage& /*message*/){}
+    virtual void swarmMessageUpdated(const std::string& /*accountId*/, const std::string& /* conversationId */, const libjami::SwarmMessage& /*message*/){}
+    virtual void reactionAdded(const std::string& /*accountId*/, const std::string& /* conversationId */, const std::string& /* messageId */, std::map<std::string, std::string> /*reaction*/){}
+    virtual void reactionRemoved(const std::string& /*accountId*/, const std::string& /* conversationId */, const std::string& /* messageId */, const std::string& /* reactionId */){}
     virtual void conversationProfileUpdated(const std::string& /*accountId*/, const std::string& /* conversationId */, std::map<std::string, std::string> /*profile*/){}
     virtual void conversationRequestReceived(const std::string& /*accountId*/, const std::string& /* conversationId */, std::map<std::string, std::string> /*metadatas*/){}
     virtual void conversationRequestDeclined(const std::string& /*accountId*/, const std::string& /* conversationId */){}
@@ -43,6 +48,16 @@ public:
 
 namespace libjami {
 
+struct SwarmMessage
+{
+    std::string id;
+    std::string type;
+    std::string linearizedParent;
+    std::map<std::string, std::string> body;
+    std::vector<std::map<std::string, std::string>> reactions;
+    std::vector<std::map<std::string, std::string>> editions;
+};
+
   // Conversation management
   std::string startConversation(const std::string& accountId);
   void acceptConversationRequest(const std::string& accountId, const std::string& conversationId);
@@ -64,8 +79,10 @@ namespace libjami {
   // Message send/load
   void sendMessage(const std::string& accountId, const std::string& conversationId, const std::string& message, const std::string& replyTo, const int32_t& flag);
   uint32_t loadConversationMessages(const std::string& accountId, const std::string& conversationId, const std::string& fromMessage, size_t n);
+  uint32_t loadConversation(const std::string& accountId, const std::string& conversationId, const std::string& fromMessage, size_t n);
   uint32_t loadConversationUntil(const std::string& accountId, const std::string& conversationId, const std::string& fromMessage, const std::string& toMessage);
   uint32_t countInteractions(const std::string& accountId, const std::string& conversationId, const std::string& toId, const std::string& fromId, const std::string& authorUri);
+  void clearCache(const std::string& accountId, const std::string& conversationId);
   uint32_t searchConversation(const std::string& accountId,
            const std::string& conversationId,
            const std::string& author,
@@ -82,8 +99,13 @@ class ConversationCallback {
 public:
     virtual ~ConversationCallback(){}
     virtual void conversationLoaded(uint32_t /* id */, const std::string& /*accountId*/, const std::string& /* conversationId */, std::vector<std::map<std::string, std::string>> /*messages*/){}
+    virtual void swarmLoaded(uint32_t /* id */, const std::string& /*accountId*/, const std::string& /* conversationId */, std::vector<libjami::SwarmMessage> /*messages*/){}
     virtual void messagesFound(uint32_t /* id */, const std::string& /*accountId*/, const std::string& /* conversationId */, std::vector<std::map<std::string, std::string>> /*messages*/){}
     virtual void messageReceived(const std::string& /*accountId*/, const std::string& /* conversationId */, std::map<std::string, std::string> /*message*/){}
+    virtual void swarmMessageReceived(const std::string& /*accountId*/, const std::string& /* conversationId */, const libjami::SwarmMessage& /*message*/){}
+    virtual void swarmMessageUpdated(const std::string& /*accountId*/, const std::string& /* conversationId */, const libjami::SwarmMessage& /*message*/){}
+    virtual void reactionAdded(const std::string& /*accountId*/, const std::string& /* conversationId */, const std::string& /* messageId */, std::map<std::string, std::string> /*messageId*/){}
+    virtual void reactionRemoved(const std::string& /*accountId*/, const std::string& /* conversationId */, const std::string& /* messageId */, const std::string& /* reactionId */){}
     virtual void conversationProfileUpdated(const std::string& /*accountId*/, const std::string& /* conversationId */, std::map<std::string, std::string> /*profile*/){}
     virtual void conversationRequestReceived(const std::string& /*accountId*/, const std::string& /* conversationId */, std::map<std::string, std::string> /*metadatas*/){}
     virtual void conversationRequestDeclined(const std::string& /*accountId*/, const std::string& /* conversationId */){}
diff --git a/bin/jni/jni_interface.i b/bin/jni/jni_interface.i
index 6b549324d90339f51c3469e762e59c63ced7bd50..9e54cda7589e851da117ee7c5201694ef00a2da2 100644
--- a/bin/jni/jni_interface.i
+++ b/bin/jni/jni_interface.i
@@ -325,8 +325,13 @@ void init(ConfigurationCallback* confM, Callback* callM, PresenceCallback* presM
 
     const std::map<std::string, SharedCallback> conversationHandlers = {
         exportable_callback<ConversationSignal::ConversationLoaded>(bind(&ConversationCallback::conversationLoaded, convM, _1, _2, _3, _4)),
+        exportable_callback<ConversationSignal::SwarmLoaded>(bind(&ConversationCallback::swarmLoaded, convM, _1, _2, _3, _4)),
         exportable_callback<ConversationSignal::MessagesFound>(bind(&ConversationCallback::messagesFound, convM, _1, _2, _3, _4)),
         exportable_callback<ConversationSignal::MessageReceived>(bind(&ConversationCallback::messageReceived, convM, _1, _2, _3)),
+        exportable_callback<ConversationSignal::SwarmMessageReceived>(bind(&ConversationCallback::swarmMessageReceived, convM, _1, _2, _3)),
+        exportable_callback<ConversationSignal::SwarmMessageUpdated>(bind(&ConversationCallback::swarmMessageUpdated, convM, _1, _2, _3)),
+        exportable_callback<ConversationSignal::ReactionAdded>(bind(&ConversationCallback::reactionAdded, convM, _1, _2, _3, _4)),
+        exportable_callback<ConversationSignal::ReactionRemoved>(bind(&ConversationCallback::reactionRemoved, convM, _1, _2, _3, _4)),
         exportable_callback<ConversationSignal::ConversationProfileUpdated>(bind(&ConversationCallback::conversationProfileUpdated, convM, _1, _2, _3)),
         exportable_callback<ConversationSignal::ConversationRequestReceived>(bind(&ConversationCallback::conversationRequestReceived, convM, _1, _2, _3)),
         exportable_callback<ConversationSignal::ConversationRequestDeclined>(bind(&ConversationCallback::conversationRequestDeclined, convM, _1, _2)),
diff --git a/bin/nodejs/callback.h b/bin/nodejs/callback.h
index 7b2a4d69b2a75e1c79522a45a1d5428ed012f041..154d6d77d66fc332fb36879c00cfb551dfcf80c4 100644
--- a/bin/nodejs/callback.h
+++ b/bin/nodejs/callback.h
@@ -31,8 +31,10 @@ Persistent<Function> incomingMessageCb;
 Persistent<Function> incomingCallCb;
 Persistent<Function> incomingCallWithMediaCb;
 Persistent<Function> conversationLoadedCb;
+Persistent<Function> swarmLoadedCb;
 Persistent<Function> messagesFoundCb;
 Persistent<Function> messageReceivedCb;
+Persistent<Function> swarmMessageReceivedCb;
 Persistent<Function> conversationProfileUpdatedCb;
 Persistent<Function> conversationRequestReceivedCb;
 Persistent<Function> conversationRequestDeclinedCb;
@@ -99,10 +101,14 @@ getPresistentCb(std::string_view signal)
         return &incomingCallWithMediaCb;
     else if (signal == "ConversationLoaded")
         return &conversationLoadedCb;
+    else if (signal == "SwarmLoaded")
+        return &swarmLoadedCb;
     else if (signal == "MessagesFound")
         return &messagesFoundCb;
     else if (signal == "MessageReceived")
         return &messageReceivedCb;
+    else if (signal == "SwarmMessageReceived")
+        return &swarmMessageReceivedCb;
     else if (signal == "ConversationProfileUpdated")
         return &conversationProfileUpdatedCb;
     else if (signal == "ConversationReady")
diff --git a/bin/nodejs/conversation.i b/bin/nodejs/conversation.i
index 803fcd3b229d9f2ca8373b270be20f8878fb131a..6673a8c4e34a1e405c22959c4cea4dfc980be4ab 100644
--- a/bin/nodejs/conversation.i
+++ b/bin/nodejs/conversation.i
@@ -26,8 +26,13 @@ class ConversationCallback {
 public:
     virtual ~ConversationCallback(){}
     virtual void conversationLoaded(uint32_t /* id */, const std::string& /*accountId*/, const std::string& /* conversationId */, std::vector<std::map<std::string, std::string>> /*messages*/){}
+    virtual void swarmLoaded(uint32_t /* id */, const std::string& /*accountId*/, const std::string& /* conversationId */, std::vector<libjami::SwarmMessage> /*messages*/){}
     virtual void messagesFound(uint32_t /* id */, const std::string& /*accountId*/, const std::string& /* conversationId */, std::vector<std::map<std::string, std::string>> /*messages*/){}
     virtual void messageReceived(const std::string& /*accountId*/, const std::string& /* conversationId */, std::map<std::string, std::string> /*message*/){}
+    virtual void swarmMessageReceived(const std::string& /*accountId*/, const std::string& /* conversationId */, const libjami::SwarmMessage& /*message*/){}
+    virtual void swarmMessageUpdated(const std::string& /*accountId*/, const std::string& /* conversationId */, const libjami::SwarmMessage& /*message*/){}
+    virtual void reactionAdded(const std::string& /*accountId*/, const std::string& /* conversationId */, const std::string& /* messageId */, std::map<std::string, std::string> /*reaction*/){}
+    virtual void reactionRemoved(const std::string& /*accountId*/, const std::string& /* conversationId */, const std::string& /* messageId */, const std::string& /* reactionId */){}
     virtual void conversationProfileUpdated(const std::string& /*accountId*/, const std::string& /* conversationId */, std::map<std::string, std::string> /*profile*/){}
     virtual void conversationRequestReceived(const std::string& /*accountId*/, const std::string& /* conversationId */, std::map<std::string, std::string> /*metadatas*/){}
     virtual void conversationRequestDeclined(const std::string& /*accountId*/, const std::string& /* conversationId */){}
@@ -43,6 +48,16 @@ public:
 
 namespace libjami {
 
+struct SwarmMessage
+{
+    std::string id;
+    std::string type;
+    std::string linearizedParent;
+    std::map<std::string, std::string> body;
+    std::vector<std::map<std::string, std::string>> reactions;
+    std::vector<std::map<std::string, std::string>> editions;
+};
+
   // Conversation management
   std::string startConversation(const std::string& accountId);
   void acceptConversationRequest(const std::string& accountId, const std::string& conversationId);
@@ -64,7 +79,9 @@ namespace libjami {
   // Message send/load
   void sendMessage(const std::string& accountId, const std::string& conversationId, const std::string& message, const std::string& replyTo, const int32_t& flag);
   uint32_t loadConversationMessages(const std::string& accountId, const std::string& conversationId, const std::string& fromMessage, size_t n);
+  uint32_t loadConversation(const std::string& accountId, const std::string& conversationId, const std::string& fromMessage, size_t n);
   uint32_t loadConversationUntil(const std::string& accountId, const std::string& conversationId, const std::string& fromMessage, const std::string& toMessage);
+  void clearCache(const std::string& accountId, const std::string& conversationId);
   uint32_t countInteractions(const std::string& accountId, const std::string& conversationId, const std::string& toId, const std::string& fromId, const std::string& authorUri);
   uint32_t searchConversation(const std::string& accountId,
            const std::string& conversationId,
@@ -83,8 +100,13 @@ class ConversationCallback {
 public:
     virtual ~ConversationCallback(){}
     virtual void conversationLoaded(uint32_t /* id */, const std::string& /*accountId*/, const std::string& /* conversationId */, std::vector<std::map<std::string, std::string>> /*messages*/){}
+    virtual void swarmLoaded(uint32_t /* id */, const std::string& /*accountId*/, const std::string& /* conversationId */, std::vector<libjami::SwarmMessage> /*messages*/){}
     virtual void messagesFound(uint32_t /* id */, const std::string& /*accountId*/, const std::string& /* conversationId */, std::vector<std::map<std::string, std::string>> /*messages*/){}
     virtual void messageReceived(const std::string& /*accountId*/, const std::string& /* conversationId */, std::map<std::string, std::string> /*message*/){}
+    virtual void swarmMessageReceived(const std::string& /*accountId*/, const std::string& /* conversationId */, const libjami::SwarmMessage& /*message*/){}
+    virtual void swarmMessageUpdated(const std::string& /*accountId*/, const std::string& /* conversationId */, const libjami::SwarmMessage& /*message*/){}
+    virtual void reactionAdded(const std::string& /*accountId*/, const std::string& /* conversationId */, const std::string& /* messageId */, std::map<std::string, std::string> /*reaction*/){}
+    virtual void reactionRemoved(const std::string& /*accountId*/, const std::string& /* conversationId */, const std::string& /* messageId */, const std::string& /* reactionId */){}
     virtual void conversationProfileUpdated(const std::string& /*accountId*/, const std::string& /* conversationId */, std::map<std::string, std::string> /*profile*/){}
     virtual void conversationRequestReceived(const std::string& /*accountId*/, const std::string& /* conversationId */, std::map<std::string, std::string> /*metadatas*/){}
     virtual void conversationRequestDeclined(const std::string& /*accountId*/, const std::string& /* conversationId */){}
diff --git a/bin/nodejs/nodejs_interface.i b/bin/nodejs/nodejs_interface.i
index 13a3d911cfa140966bcaecd21513bf965aeceead..42a73c39eaecc8acce7ef88367eb035e7ce9dd45 100644
--- a/bin/nodejs/nodejs_interface.i
+++ b/bin/nodejs/nodejs_interface.i
@@ -150,8 +150,13 @@ void init(const SWIGV8_VALUE& funcMap){
 
     const std::map<std::string, SharedCallback> conversationHandlers = {
         exportable_callback<ConversationSignal::ConversationLoaded>(bind(&conversationLoaded, _1, _2, _3, _4)),
+        exportable_callback<ConversationSignal::SwarmLoaded>(bind(&swarmLoaded, _1, _2, _3, _4)),
         exportable_callback<ConversationSignal::MessagesFound>(bind(&messagesFound, _1, _2, _3, _4)),
         exportable_callback<ConversationSignal::MessageReceived>(bind(&messageReceived, _1, _2, _3)),
+        exportable_callback<ConversationSignal::SwarmMessageReceived>(bind(&swarmMessageReceived, _1, _2, _3)),
+        exportable_callback<ConversationSignal::SwarmMessageUpdated>(bind(&swarmMessageUpdated, _1, _2, _3)),
+        exportable_callback<ConversationSignal::ReactionAdded>(bind(&reactionAdded, _1, _2, _3, _4)),
+        exportable_callback<ConversationSignal::ReactionRemoved>(bind(&reactionRemoved, _1, _2, _3, _4)),
         exportable_callback<ConversationSignal::ConversationProfileUpdated>(bind(&conversationProfileUpdated, _1, _2, _3)),
         exportable_callback<ConversationSignal::ConversationRequestReceived>(bind(&conversationRequestReceived, _1, _2, _3)),
         exportable_callback<ConversationSignal::ConversationRequestDeclined>(bind(&conversationRequestDeclined, _1, _2)),
diff --git a/configure.ac b/configure.ac
index f94bf70f779775da81a8f2d8d327b5012dbeef31..5c7467037ee207374a9c8b3826243a01699b98b0 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2,7 +2,7 @@ dnl Jami - configure.ac
 
 dnl Process this file with autoconf to produce a configure script.
 AC_PREREQ([2.69])
-AC_INIT([Jami Daemon],[13.11.0],[jami@gnu.org],[jami])
+AC_INIT([Jami Daemon],[14.0.0],[jami@gnu.org],[jami])
 
 dnl Clear the implicit flags that default to '-g -O2', otherwise they
 dnl take precedence over the values we set via the
diff --git a/meson.build b/meson.build
index 73c16b77456bf43885c5c9ea7da960964a61dcca..97ae791a12169ad7488f29df30379dbb5a390e42 100644
--- a/meson.build
+++ b/meson.build
@@ -1,5 +1,5 @@
 project('jami-daemon', ['c', 'cpp'],
-        version: '13.11.0',
+        version: '14.0.0',
         license: 'GPL3+',
         default_options: ['cpp_std=gnu++17', 'buildtype=debugoptimized'],
         meson_version:'>= 0.56'
diff --git a/src/client/conversation_interface.cpp b/src/client/conversation_interface.cpp
index 661832b96f6d53e832c7649446b60d3f47b95043..0fb86ab66a6668a016509d0312aee4d61a96d231 100644
--- a/src/client/conversation_interface.cpp
+++ b/src/client/conversation_interface.cpp
@@ -195,6 +195,18 @@ loadConversationMessages(const std::string& accountId,
     return 0;
 }
 
+uint32_t
+loadConversation(const std::string& accountId,
+                         const std::string& conversationId,
+                         const std::string& fromMessage,
+                         size_t n)
+{
+    if (auto acc = jami::Manager::instance().getAccount<jami::JamiAccount>(accountId))
+        if (auto convModule = acc->convModule())
+            return convModule->loadConversation(conversationId, fromMessage, n);
+    return 0;
+}
+
 uint32_t
 loadConversationUntil(const std::string& accountId,
                       const std::string& conversationId,
@@ -220,6 +232,14 @@ countInteractions(const std::string& accountId,
     return 0;
 }
 
+void
+clearCache(const std::string& accountId, const std::string& conversationId)
+{
+    if (auto acc = jami::Manager::instance().getAccount<jami::JamiAccount>(accountId))
+        if (auto convModule = acc->convModule())
+            convModule->clearCache(conversationId);
+}
+
 uint32_t
 searchConversation(const std::string& accountId,
                    const std::string& conversationId,
diff --git a/src/client/ring_signal.cpp b/src/client/ring_signal.cpp
index 5576d32b3d439765536be15d728d45bbd6195a42..9d86696ca93fa65fc94173dd86b96910c25085b3 100644
--- a/src/client/ring_signal.cpp
+++ b/src/client/ring_signal.cpp
@@ -129,8 +129,13 @@ getSignalHandlers()
 
         /* Conversation */
         exported_callback<libjami::ConversationSignal::ConversationLoaded>(),
+        exported_callback<libjami::ConversationSignal::SwarmLoaded>(),
         exported_callback<libjami::ConversationSignal::MessagesFound>(),
         exported_callback<libjami::ConversationSignal::MessageReceived>(),
+        exported_callback<libjami::ConversationSignal::SwarmMessageReceived>(),
+        exported_callback<libjami::ConversationSignal::SwarmMessageUpdated>(),
+        exported_callback<libjami::ConversationSignal::ReactionAdded>(),
+        exported_callback<libjami::ConversationSignal::ReactionRemoved>(),
         exported_callback<libjami::ConversationSignal::ConversationProfileUpdated>(),
         exported_callback<libjami::ConversationSignal::ConversationRequestReceived>(),
         exported_callback<libjami::ConversationSignal::ConversationRequestDeclined>(),
diff --git a/src/jami/conversation_interface.h b/src/jami/conversation_interface.h
index aed802231ec25c12b49755691f1c27476962bb1a..26d932c775a89fb6ab7676ccf572d0934c374bb9 100644
--- a/src/jami/conversation_interface.h
+++ b/src/jami/conversation_interface.h
@@ -26,11 +26,28 @@
 #include <vector>
 #include <map>
 #include <string>
+#include <list>
 
 #include "jami.h"
 
 namespace libjami {
 
+struct SwarmMessage
+{
+    std::string id;
+    std::string type;
+    std::string linearizedParent;
+    std::map<std::string, std::string> body;
+    std::vector<std::map<std::string, std::string>> reactions;
+    std::vector<std::map<std::string, std::string>> editions;
+
+    void fromMapStringString(const std::map<std::string, std::string>& commit) {
+        id = commit.at("id");
+        type = commit.at("type");
+        body = commit; // TODO erase type/id?
+    }
+};
+
 // Conversation management
 LIBJAMI_PUBLIC std::string startConversation(const std::string& accountId);
 LIBJAMI_PUBLIC void acceptConversationRequest(const std::string& accountId,
@@ -79,6 +96,10 @@ LIBJAMI_PUBLIC uint32_t loadConversationMessages(const std::string& accountId,
                                                  const std::string& conversationId,
                                                  const std::string& fromMessage,
                                                  size_t n);
+LIBJAMI_PUBLIC uint32_t loadConversation(const std::string& accountId,
+                                                 const std::string& conversationId,
+                                                 const std::string& fromMessage,
+                                                 size_t n);
 LIBJAMI_PUBLIC uint32_t loadConversationUntil(const std::string& accountId,
                                               const std::string& conversationId,
                                               const std::string& fromMessage,
@@ -88,6 +109,7 @@ LIBJAMI_PUBLIC uint32_t countInteractions(const std::string& accountId,
                                           const std::string& toId,
                                           const std::string& fromId,
                                           const std::string& authorUri);
+LIBJAMI_PUBLIC void clearCache(const std::string& accountId, const std::string& conversationId);
 LIBJAMI_PUBLIC uint32_t searchConversation(const std::string& accountId,
                                            const std::string& conversationId,
                                            const std::string& author,
@@ -110,6 +132,14 @@ struct LIBJAMI_PUBLIC ConversationSignal
                              const std::string& /* conversationId */,
                              std::vector<std::map<std::string, std::string>> /*messages*/);
     };
+    struct LIBJAMI_PUBLIC SwarmLoaded
+    {
+        constexpr static const char* name = "SwarmLoaded";
+        using cb_type = void(uint32_t /* id */,
+                             const std::string& /*accountId*/,
+                             const std::string& /* conversationId */,
+                             std::vector<SwarmMessage> /*messages*/);
+    };
     struct LIBJAMI_PUBLIC MessagesFound
     {
         constexpr static const char* name = "MessagesFound";
@@ -125,6 +155,36 @@ struct LIBJAMI_PUBLIC ConversationSignal
                              const std::string& /* conversationId */,
                              std::map<std::string, std::string> /*message*/);
     };
+    struct LIBJAMI_PUBLIC SwarmMessageReceived
+    {
+        constexpr static const char* name = "SwarmMessageReceived";
+        using cb_type = void(const std::string& /*accountId*/,
+                             const std::string& /* conversationId */,
+                             const SwarmMessage& /*message*/);
+    };
+    struct LIBJAMI_PUBLIC SwarmMessageUpdated
+    {
+        constexpr static const char* name = "SwarmMessageUpdated";
+        using cb_type = void(const std::string& /*accountId*/,
+                             const std::string& /* conversationId */,
+                             const SwarmMessage& /*message*/);
+    };
+    struct LIBJAMI_PUBLIC ReactionAdded
+    {
+        constexpr static const char* name = "ReactionAdded";
+        using cb_type = void(const std::string& /*accountId*/,
+                             const std::string& /* conversationId */,
+                             const std::string& /* messageId */,
+                             std::map<std::string, std::string> /*reaction*/);
+    };
+    struct LIBJAMI_PUBLIC ReactionRemoved
+    {
+        constexpr static const char* name = "ReactionRemoved";
+        using cb_type = void(const std::string& /*accountId*/,
+                             const std::string& /* conversationId */,
+                             const std::string& /* messageId */,
+                             const std::string& /* reactionId */);
+    };
     struct LIBJAMI_PUBLIC ConversationProfileUpdated
     {
         constexpr static const char* name = "ConversationProfileUpdated";
diff --git a/src/jamidht/conversation.cpp b/src/jamidht/conversation.cpp
index 629c115d8367301321d88e7043710e49d8690349..0d72a0e5be0c8893f58539840521ce9f23fea18d 100644
--- a/src/jamidht/conversation.cpp
+++ b/src/jamidht/conversation.cpp
@@ -29,12 +29,14 @@
 #include <string_view>
 #include <opendht/thread_pool.h>
 #include <tuple>
+#include <optional>
 #include "swarm/swarm_manager.h"
 #ifdef ENABLE_PLUGIN
 #include "manager.h"
 #include "plugin/jamipluginmanager.h"
 #include "plugin/streamdata.h"
 #endif
+#include "jami/conversation_interface.h"
 
 namespace jami {
 
@@ -119,6 +121,16 @@ ConversationRequest::toMap() const
     return result;
 }
 
+using MessageList = std::list<std::shared_ptr<libjami::SwarmMessage>>;
+
+struct History
+{
+    MessageList messageList {};
+    std::map<std::string, std::shared_ptr<libjami::SwarmMessage>> quickAccess {};
+    std::map<std::string, std::list<std::shared_ptr<libjami::SwarmMessage>>> pendingEditions {};
+    std::map<std::string, std::list<std::map<std::string, std::string>>> pendingReactions {};
+};
+
 class Conversation::Impl
 {
 public:
@@ -369,6 +381,7 @@ public:
         auto convId = repository_->id();
         auto ok = !commits.empty();
         auto lastId = ok ? commits.rbegin()->at(ConversationMapKeys::ID) : "";
+        addToHistory(commits, true);
         if (ok) {
             bool announceMember = false;
             for (const auto& c : commits) {
@@ -602,6 +615,7 @@ public:
     std::weak_ptr<JamiAccount> account_;
     std::atomic_bool isRemoving_ {false};
     std::vector<std::map<std::string, std::string>> loadMessages(const LogOptions& options);
+    std::vector<libjami::SwarmMessage> loadMessages2(const LogOptions& options, History* optHistory = nullptr);
     void pull();
     std::vector<std::map<std::string, std::string>> mergeHistory(const std::string& uri);
 
@@ -642,6 +656,24 @@ public:
     std::unique_ptr<asio::steady_timer> fallbackTimer_;
 
     bool isMobile {false};
+
+    /**
+     * Loaded history represents the linearized history to show for clients
+     */
+    mutable History loadedHistory_ {};
+    std::vector<libjami::SwarmMessage> addToHistory(const std::vector<std::map<std::string, std::string>>& commits,
+                        bool messageReceived = false,
+                        History* history = nullptr) const;
+    // While loading the history, we need to avoid:
+    // - reloading history (can just be ignored)
+    // - adding new commits (should wait for history to be loaded)
+    bool isLoadingHistory_ {false};
+    mutable std::mutex historyMtx_ {};
+    mutable std::condition_variable historyCv_ {};
+
+    void handleReaction(History& history, const std::shared_ptr<libjami::SwarmMessage>& sharedCommit) const;
+    void handleEdition(History& history, const std::shared_ptr<libjami::SwarmMessage>& sharedCommit, bool messageReceived) const;
+    bool handleMessage(History& history, const std::shared_ptr<libjami::SwarmMessage>& sharedCommit, bool messageReceived) const;
 };
 
 bool
@@ -738,9 +770,280 @@ Conversation::Impl::loadMessages(const LogOptions& options)
 {
     if (!repository_)
         return {};
-    std::vector<ConversationCommit> convCommits;
-    convCommits = repository_->log(options);
-    return repository_->convCommitToMap(convCommits);
+    std::vector<ConversationCommit> commits;
+    auto startLogging = options.from == "";
+    auto breakLogging = false;
+    repository_->log([&](const auto& id, const auto& author, const auto& commit) {
+            if (!commits.empty()) {
+                // Set linearized parent
+                commits.rbegin()->linearized_parent = id;
+            }
+            if (options.skipMerge && git_commit_parentcount(commit.get()) > 1) {
+                return CallbackResult::Skip;
+            }
+            if ((options.nbOfCommits != 0
+                && commits.size() == options.nbOfCommits))
+                return CallbackResult::Break; // Stop logging
+            if (breakLogging)
+                return CallbackResult::Break; // Stop logging
+            if (id == options.to) {
+                if (options.includeTo)
+                    breakLogging = true; // For the next commit
+                else
+                    return CallbackResult::Break; // Stop logging
+            }
+
+            if (!startLogging && options.from != "" && options.from == id)
+                startLogging = true;
+            if (!startLogging)
+                return CallbackResult::Skip; // Start logging after this one
+
+            if (options.fastLog) {
+                if (options.authorUri != "") {
+                    if (options.authorUri == repository_->uriFromDevice(author.email)) {
+                        return CallbackResult::Break; // Found author, stop
+                    }
+                }
+                // Used to only count commit
+                commits.emplace(commits.end(), ConversationCommit {});
+                return CallbackResult::Skip;
+            }
+
+            return CallbackResult::Ok; // Continue
+        },
+        [&](auto&& cc) {
+            commits.emplace(commits.end(), std::forward<decltype(cc)>(cc));
+        },
+        [](auto, auto, auto) { return false; },
+        options.from,
+        options.logIfNotFound);
+    return repository_->convCommitToMap(commits);
+}
+
+std::vector<libjami::SwarmMessage>
+Conversation::Impl::loadMessages2(const LogOptions& options, History* optHistory)
+{
+    {
+        std::lock_guard<std::mutex> lock(historyMtx_);
+        if (!repository_ || (!optHistory && isLoadingHistory_))
+            return {};
+        if (!optHistory)
+            isLoadingHistory_ = true;
+    }
+
+    auto startLogging = options.from == "";
+    auto breakLogging = false;
+    auto currentHistorySize = loadedHistory_.messageList.size();
+    std::vector<std::string> replies;
+    std::vector<libjami::SwarmMessage> ret;
+    repository_->log([&](const auto& id, const auto& author, const auto& commit) {
+            if (options.skipMerge && git_commit_parentcount(commit.get()) > 1) {
+                return CallbackResult::Skip;
+            }
+            if (replies.empty()) { // This avoid load until
+            // NOTE: in the future, we may want to add "Reply-Body" in commit to avoid to load until this commit
+                if ((options.nbOfCommits != 0
+                    && (loadedHistory_.messageList.size() - currentHistorySize) == options.nbOfCommits))
+                    return CallbackResult::Break; // Stop logging
+                if (breakLogging)
+                    return CallbackResult::Break; // Stop logging
+                if (id == options.to) {
+                    if (options.includeTo)
+                        breakLogging = true; // For the next commit
+                    else
+                        return CallbackResult::Break; // Stop logging
+                }
+            }
+
+            if (!startLogging && options.from != "" && options.from == id)
+                startLogging = true;
+            if (!startLogging)
+                return CallbackResult::Skip; // Start logging after this one
+
+            if (options.fastLog) {
+                if (options.authorUri != "") {
+                    if (options.authorUri == repository_->uriFromDevice(author.email)) {
+                        return CallbackResult::Break; // Found author, stop
+                    }
+                }
+            }
+
+            return CallbackResult::Ok; // Continue
+        },
+        [&](auto&& cc) {
+            std::map<std::string, std::string> map = *repository_->convCommitToMap(cc);
+            if (map.find("reply-to") != map.end()) {
+                replies.emplace_back(map.at("reply-to"));
+            }
+            auto it = std::find(replies.begin(), replies.end(), map.at("id"));
+            if (it != replies.end()) {
+                replies.erase(it);
+            }
+            auto added = addToHistory({map}, false, optHistory);
+            ret.insert(ret.end(), added.begin(), added.end());
+        },
+        [](auto, auto, auto) { return false; },
+        options.from,
+        options.logIfNotFound);
+
+    if (!optHistory) {
+        std::lock_guard<std::mutex> lock(historyMtx_);
+        isLoadingHistory_ = false;
+        historyCv_.notify_all();
+    }
+
+    return ret;
+}
+
+void
+Conversation::Impl::handleReaction(History& history, const std::shared_ptr<libjami::SwarmMessage>& sharedCommit) const
+{
+    auto it = history.quickAccess.find(sharedCommit->body.at("react-to"));
+    auto peditIt = history.pendingEditions.find(sharedCommit->id);
+    if (peditIt != history.pendingEditions.end()) {
+        auto oldBody = sharedCommit->body;
+        sharedCommit->body["body"] = peditIt->second.front()->body["body"];
+        if (sharedCommit->body.at("body").empty())
+            return;
+        history.pendingEditions.erase(peditIt);
+    }
+    if (it != history.quickAccess.end()) {
+        it->second->reactions.emplace_back(sharedCommit->body);
+        emitSignal<libjami::ConversationSignal::ReactionAdded>(accountId_, repository_->id(), it->second->id, sharedCommit->body);
+    } else {
+        history.pendingReactions[sharedCommit->body.at("react-to")].emplace_back(sharedCommit->body);
+    }
+}
+
+void
+Conversation::Impl::handleEdition(History& history, const std::shared_ptr<libjami::SwarmMessage>& sharedCommit, bool messageReceived) const
+{
+    auto editId = sharedCommit->body.at("edit");
+    auto it = history.quickAccess.find(editId);
+    if (it != history.quickAccess.end()) {
+        auto baseCommit = it->second;
+        if (baseCommit) {
+            auto itReact = baseCommit->body.find("react-to");
+            auto body = sharedCommit->body.at("body");
+            // Edit reaction
+            if (itReact != baseCommit->body.end()) {
+                baseCommit->body["body"] = body; // Replace body if pending
+                it = history.quickAccess.find(itReact->second);
+                auto itPending = history.pendingReactions.find(itReact->second);
+                if (it != history.quickAccess.end()) {
+                    baseCommit = it->second; // Base commit
+                    auto itPreviousReact = std::find_if(baseCommit->reactions.begin(), baseCommit->reactions.end(), [&](const auto& reaction) {
+                        return reaction.at("id") == editId;
+                    });
+                    if (itPreviousReact != baseCommit->reactions.end()) {
+                        (*itPreviousReact)["body"] = body;
+                        if (body.empty()) {
+                            baseCommit->reactions.erase(itPreviousReact);
+                            emitSignal<libjami::ConversationSignal::ReactionRemoved>(accountId_, repository_->id(), baseCommit->id, editId);
+                        }
+                    }
+                } else if (itPending != history.pendingReactions.end()) {
+                    // Else edit if pending
+                    auto itReaction = std::find_if(itPending->second.begin(), itPending->second.end(), [&](const auto& reaction) {
+                        return reaction.at("id") == editId;
+                    });
+                    if (itReaction != itPending->second.end()) {
+                        (*itReaction)["body"] = body;
+                        if (body.empty())
+                            itPending->second.erase(itReaction);
+                    }
+                } else {
+                    // Add to pending edtions
+                    messageReceived? history.pendingEditions[editId].emplace_front(sharedCommit)
+                                    : history.pendingEditions[editId].emplace_back(sharedCommit);
+                }
+            } else {
+                // Normal message
+                it->second->editions.emplace(it->second->editions.begin(), it->second->body);
+                it->second->body["body"] = sharedCommit->body["body"];
+                emitSignal<libjami::ConversationSignal::SwarmMessageUpdated>(accountId_, repository_->id(), *it->second);
+            }
+        }
+    } else {
+        messageReceived? history.pendingEditions[editId].emplace_front(sharedCommit)
+        : history.pendingEditions[editId].emplace_back(sharedCommit);
+    }
+}
+
+bool
+Conversation::Impl::handleMessage(History& history, const std::shared_ptr<libjami::SwarmMessage>& sharedCommit, bool messageReceived) const
+{
+    if (messageReceived) {
+        // For a received message, we place it at the beginning of the list
+        if (!history.messageList.empty())
+            sharedCommit->linearizedParent = (*history.messageList.begin())->id;
+        history.messageList.emplace_front(sharedCommit);
+    } else {
+        // For a loaded message, we load from newest to oldest
+        // So we change the parent of the last message.
+        if (!history.messageList.empty())
+            (*history.messageList.rbegin())->linearizedParent = sharedCommit->id;
+        history.messageList.emplace_back(sharedCommit);
+    }
+    // Handle pending reactions/editions
+    auto reactIt = history.pendingReactions.find(sharedCommit->id);
+    if (reactIt != history.pendingReactions.end()) {
+        for (const auto& commitBody: reactIt->second)
+            sharedCommit->reactions.emplace_back(commitBody);
+        history.pendingReactions.erase(reactIt);
+    }
+    auto peditIt = history.pendingEditions.find(sharedCommit->id);
+    if (peditIt != history.pendingEditions.end()) {
+        auto oldBody = sharedCommit->body;
+        sharedCommit->body["body"] = peditIt->second.front()->body["body"];
+        peditIt->second.pop_front();
+        for (const auto& commit: peditIt->second) {
+            sharedCommit->editions.emplace_back(commit->body);
+        }
+        sharedCommit->editions.emplace_back(oldBody);
+        history.pendingEditions.erase(peditIt);
+    }
+    // Announce to client
+    if (messageReceived)
+        emitSignal<libjami::ConversationSignal::SwarmMessageReceived>(accountId_, repository_->id(), *sharedCommit);
+    return !messageReceived;
+}
+
+std::vector<libjami::SwarmMessage>
+Conversation::Impl::addToHistory(const std::vector<std::map<std::string, std::string>>& commits, bool messageReceived, History* optHistory) const
+{
+    if (messageReceived && (!optHistory && isLoadingHistory_)) {
+        std::unique_lock<std::mutex> lk(historyMtx_);
+        historyCv_.wait(lk, [&] { return !isLoadingHistory_; });
+    }
+    std::vector<libjami::SwarmMessage> messages;
+    auto addCommit = [&](const auto& commit) {
+        auto* history = optHistory ? optHistory : &loadedHistory_;
+        auto commitId = commit.at("id");
+        if (history->quickAccess.find(commitId) != history->quickAccess.end())
+            return; // Already present
+        auto typeIt = commit.find("type");
+        auto reactToIt = commit.find("react-to");
+        auto editIt = commit.find("edit");
+        // Nothing to show for the client, skip
+        if (typeIt != commit.end() && typeIt->second == "merge")
+            return;
+
+        auto sharedCommit = std::make_shared<libjami::SwarmMessage>();
+        sharedCommit->fromMapStringString(commit);
+        history->quickAccess[commitId] = sharedCommit;
+
+        if (reactToIt != commit.end() && !reactToIt->second.empty()) {
+            handleReaction(*history, sharedCommit);
+        } else if (editIt != commit.end() && !editIt->second.empty()) {
+            handleEdition(*history, sharedCommit, messageReceived);
+        } else if (handleMessage(*history, sharedCommit, messageReceived)) {
+            messages.emplace_back(*sharedCommit);
+        }
+    };
+    std::for_each(commits.begin(), commits.end(), addCommit);
+
+    return messages;
 }
 
 Conversation::Conversation(const std::shared_ptr<JamiAccount>& account,
@@ -1178,7 +1481,7 @@ Conversation::getCommit(const std::string& commitId) const
 }
 
 void
-Conversation::loadMessages(const OnLoadMessages& cb, const LogOptions& options)
+Conversation::loadMessages(OnLoadMessages cb, const LogOptions& options)
 {
     if (!cb)
         return;
@@ -1189,6 +1492,27 @@ Conversation::loadMessages(const OnLoadMessages& cb, const LogOptions& options)
     });
 }
 
+void
+Conversation::loadMessages2(const OnLoadMessages2& cb, const LogOptions& options)
+{
+    if (!cb)
+        return;
+    dht::ThreadPool::io().run([w = weak(), cb = std::move(cb), options] {
+        if (auto sthis = w.lock()) {
+            cb(sthis->pimpl_->loadMessages2(options));
+        }
+    });
+}
+
+void
+Conversation::clearCache()
+{
+    pimpl_->loadedHistory_.messageList.clear();
+    pimpl_->loadedHistory_.quickAccess.clear();
+    pimpl_->loadedHistory_.pendingEditions.clear();
+    pimpl_->loadedHistory_.pendingReactions.clear();
+}
+
 std::string
 Conversation::lastCommitId() const
 {
@@ -1976,14 +2300,15 @@ Conversation::countInteractions(const std::string& toId,
                                 const std::string& fromId,
                                 const std::string& authorUri) const
 {
-    // Log but without content to avoid costly convertions.
     LogOptions options;
     options.to = toId;
     options.from = fromId;
     options.authorUri = authorUri;
     options.logIfNotFound = false;
     options.fastLog = true;
-    return pimpl_->repository_->log(options).size();
+    History history;
+    auto res = pimpl_->loadMessages2(options, &history);
+    return res.size();
 }
 
 void
@@ -1998,7 +2323,74 @@ Conversation::search(uint32_t req,
             auto acc = sthis->pimpl_->account_.lock();
             if (!acc)
                 return;
-            auto commits = sthis->pimpl_->repository_->search(filter);
+
+            History history;
+            std::vector<std::map<std::string, std::string>> commits {};
+            // std::regex_constants::ECMAScript is the default flag.
+            auto re = std::regex(filter.regexSearch,
+                                filter.caseSensitive ? std::regex_constants::ECMAScript
+                                                    : std::regex_constants::icase);
+            sthis->pimpl_->repository_->log([&](const auto& id, const auto& author, auto& commit) {
+                    if (!filter.author.empty() && filter.author != sthis->uriFromDevice(author.email)) {
+                        // Filter author
+                        return CallbackResult::Skip;
+                    }
+                    auto commitTime = git_commit_time(commit.get());
+                    if (filter.before && filter.before < commitTime) {
+                        // Only get commits before this date
+                        return CallbackResult::Skip;
+                    }
+                    if (filter.after && filter.after > commitTime) {
+                        // Only get commits before this date
+                        if (git_commit_parentcount(commit.get()) <= 1)
+                            return CallbackResult::Break;
+                        else
+                            return CallbackResult::Skip; // Because we are sorting it with
+                                                        // GIT_SORT_TOPOLOGICAL | GIT_SORT_TIME
+                    }
+
+                    return CallbackResult::Ok; // Continue
+                },
+                [&](auto&& cc) {
+                    sthis->pimpl_->addToHistory({*sthis->pimpl_->repository_->convCommitToMap(cc)}, false, &history);
+                },
+                [&](auto id, auto, auto) {
+                    if (id == filter.lastId)
+                        return true;
+                    return false;
+                },
+                "",
+                false);
+            // Search on generated history
+            for (auto& message : history.messageList) {
+                auto contentType = message->type;
+                auto isSearchable = contentType == "text/plain"
+                                    || contentType == "application/data-transfer+json";
+                if (filter.type.empty() && !isSearchable) {
+                    // Not searchable, at least for now
+                    continue;
+                } else if (contentType == filter.type || filter.type.empty()) {
+                    if (isSearchable) {
+                        // If it's a text match the body, else the display name
+                        auto body = contentType == "text/plain" ? message->body.at("body")
+                                                                : message->body.at("displayName");
+                        std::smatch body_match;
+                        if (std::regex_search(body, body_match, re)) {
+                            auto commit = message->body;
+                            commit["id"] = message->id;
+                            commit["type"] = message->type;
+                            commits.emplace_back(commit);
+                        }
+                    } else {
+                        // Matching type, just add it to the results
+                        commits.emplace_back(message->body);
+                    }
+
+                    if (filter.maxResult != 0 && commits.size() == filter.maxResult)
+                        break;
+                }
+            }
+
             if (commits.size() > 0)
                 emitSignal<libjami::ConversationSignal::MessagesFound>(req,
                                                                        acc->getAccountID(),
diff --git a/src/jamidht/conversation.h b/src/jamidht/conversation.h
index 3d883ad5c8481c9896689c298301babc3568c4a6..55c4b44c75f06deeeb1828da7b310a6cb05f9bfb 100644
--- a/src/jamidht/conversation.h
+++ b/src/jamidht/conversation.h
@@ -22,6 +22,7 @@
 #include "jamidht/conversationrepository.h"
 #include "conversationrepository.h"
 #include "swarm/swarm_protocol.h"
+#include "jami/conversation_interface.h"
 
 #include <json/json.h>
 #include <msgpack.hpp>
@@ -134,6 +135,8 @@ enum class ConversationMode;
 using OnPullCb = std::function<void(bool fetchOk)>;
 using OnLoadMessages
     = std::function<void(std::vector<std::map<std::string, std::string>>&& messages)>;
+using OnLoadMessages2
+    = std::function<void(std::vector<libjami::SwarmMessage>&& messages)>;
 using OnCommitCb = std::function<void(const std::string&)>;
 using OnDoneCb = std::function<void(bool, const std::string&)>;
 using OnMultiDoneCb = std::function<void(const std::vector<std::string>&)>;
@@ -296,7 +299,17 @@ public:
      * @param cb        The callback when loaded
      * @param options   The log options
      */
-    void loadMessages(const OnLoadMessages& cb, const LogOptions& options);
+    void loadMessages(OnLoadMessages cb, const LogOptions& options);
+    /**
+     * Get a range of messages
+     * @param cb        The callback when loaded
+     * @param options   The log options
+     */
+    void loadMessages2(const OnLoadMessages2& cb, const LogOptions& options);
+    /**
+     * Clear all cached messages
+     */
+    void clearCache();
     /**
      * Retrieve one commit
      * @param   commitId
@@ -474,7 +487,6 @@ public:
      * @return displayed
      */
     std::map<std::string, std::string> displayed() const;
-
     /**
      * Retrieve how many interactions there is from HEAD to interactionId
      * @param toId      "" for getting the whole history
@@ -485,7 +497,6 @@ public:
     uint32_t countInteractions(const std::string& toId,
                                const std::string& fromId = "",
                                const std::string& authorUri = "") const;
-
     /**
      * Search in the conversation via a filter
      * @param req       Id of the request
diff --git a/src/jamidht/conversation_module.cpp b/src/jamidht/conversation_module.cpp
index 5372378c191e09721a3d366115e1f4083c3f6dc5..e2712371cff37d0e5fdc25229b8f31883df46c4a 100644
--- a/src/jamidht/conversation_module.cpp
+++ b/src/jamidht/conversation_module.cpp
@@ -2009,6 +2009,44 @@ ConversationModule::loadConversationMessages(const std::string& conversationId,
     return 0;
 }
 
+void
+ConversationModule::clearCache(const std::string& conversationId)
+{
+    if (auto conv = pimpl_->getConversation(conversationId)) {
+        std::lock_guard<std::mutex> lk(conv->mtx);
+        if (conv->conversation) {
+            conv->conversation->clearCache();
+        }
+    }
+}
+
+uint32_t
+ConversationModule::loadConversation(const std::string& conversationId,
+                                     const std::string& fromMessage,
+                                     size_t n)
+{
+    auto acc = pimpl_->account_.lock();
+    if (auto conv = pimpl_->getConversation(conversationId)) {
+        std::lock_guard<std::mutex> lk(conv->mtx);
+        if (conv->conversation) {
+            const uint32_t id = std::uniform_int_distribution<uint32_t> {}(acc->rand);
+            LogOptions options;
+            options.from = fromMessage;
+            options.nbOfCommits = n;
+            conv->conversation->loadMessages2(
+                [accountId = pimpl_->accountId_, conversationId, id](auto&& messages) {
+                    emitSignal<libjami::ConversationSignal::SwarmLoaded>(id,
+                                                                         accountId,
+                                                                         conversationId,
+                                                                         messages);
+                },
+                options);
+            return id;
+        }
+    }
+    return 0;
+}
+
 uint32_t
 ConversationModule::loadConversationUntil(const std::string& conversationId,
                                           const std::string& fromMessage,
diff --git a/src/jamidht/conversation_module.h b/src/jamidht/conversation_module.h
index 0feaad89639e661d0c0367b267844da3082dcf80..f7e3cba9415b3a8b31d731132e0f6c342df38cf1 100644
--- a/src/jamidht/conversation_module.h
+++ b/src/jamidht/conversation_module.h
@@ -226,9 +226,17 @@ public:
     uint32_t loadConversationMessages(const std::string& conversationId,
                                       const std::string& fromMessage = "",
                                       size_t n = 0);
+    uint32_t loadConversation(const std::string& conversationId,
+                                const std::string& fromMessage = "",
+                                size_t n = 0);
     uint32_t loadConversationUntil(const std::string& conversationId,
                                    const std::string& fromMessage,
                                    const std::string& to);
+    /**
+     * Clear loaded interactions
+     * @param conversationId
+     */
+    void clearCache(const std::string& conversationId);
 
     // File transfer
     /**
diff --git a/src/jamidht/conversationrepository.cpp b/src/jamidht/conversationrepository.cpp
index 88129602968ca91912227cd7b5db78468db0c772..f05b7bc2ed745766a2f1747a7790e902384947fb 100644
--- a/src/jamidht/conversationrepository.cpp
+++ b/src/jamidht/conversationrepository.cpp
@@ -54,13 +54,6 @@ as_view(const GitObject& blob)
     return as_view(reinterpret_cast<git_blob*>(blob.get()));
 }
 
-enum class CallbackResult { Skip, Break, Ok };
-
-using PreConditionCb
-    = std::function<CallbackResult(const std::string&, const GitAuthor&, const GitCommit&)>;
-using PostConditionCb
-    = std::function<bool(const std::string&, const GitAuthor&, ConversationCommit&)>;
-
 class ConversationRepository::Impl
 {
 public:
@@ -158,7 +151,6 @@ public:
                        const std::string& from = "",
                        bool logIfNotFound = true) const;
     std::vector<ConversationCommit> log(const LogOptions& options) const;
-    std::vector<std::map<std::string, std::string>> search(const Filter& filter) const;
 
     GitObject fileAtTree(const std::string& path, const GitTree& tree) const;
     GitObject memberCertificate(std::string_view memberUri, const GitTree& tree) const;
@@ -2184,74 +2176,6 @@ ConversationRepository::Impl::log(const LogOptions& options) const
     return commits;
 }
 
-std::vector<std::map<std::string, std::string>>
-ConversationRepository::Impl::search(const Filter& filter) const
-{
-    std::vector<std::map<std::string, std::string>> commits {};
-    // std::regex_constants::ECMAScript is the default flag.
-    auto re = std::regex(filter.regexSearch,
-                         filter.caseSensitive ? std::regex_constants::ECMAScript
-                                              : std::regex_constants::icase);
-    forEachCommit(
-        [&](const auto& id, const auto& author, auto& commit) {
-            if (!commits.empty()) {
-                // Set linearized parent
-                commits.rbegin()->at("linearizedParent") = id;
-            }
-
-            if (!filter.author.empty() && filter.author != uriFromDevice(author.email)) {
-                // Filter author
-                return CallbackResult::Skip;
-            }
-            auto commitTime = git_commit_time(commit.get());
-            if (filter.before && filter.before < commitTime) {
-                // Only get commits before this date
-                return CallbackResult::Skip;
-            }
-            if (filter.after && filter.after > commitTime) {
-                // Only get commits before this date
-                if (git_commit_parentcount(commit.get()) <= 1)
-                    return CallbackResult::Break;
-                else
-                    return CallbackResult::Skip; // Because we are sorting it with
-                                                 // GIT_SORT_TOPOLOGICAL | GIT_SORT_TIME
-            }
-
-            return CallbackResult::Ok; // Continue
-        },
-        [&](auto&& cc) {
-            auto content = convCommitToMap(cc);
-            auto contentType = content ? content->at("type") : "";
-            auto isSearchable = contentType == "text/plain"
-                                || contentType == "application/data-transfer+json";
-            if (filter.type.empty() && !isSearchable) {
-                // Not searchable, at least for now
-                return;
-            } else if (contentType == filter.type || filter.type.empty()) {
-                if (isSearchable) {
-                    // If it's a text match the body, else the display name
-                    auto body = contentType == "text/plain" ? content->at("body")
-                                                            : content->at("displayName");
-                    std::smatch body_match;
-                    if (std::regex_search(body, body_match, re)) {
-                        commits.emplace(commits.end(), std::move(*content));
-                    }
-                } else {
-                    // Matching type, just add it to the results
-                    commits.emplace(commits.end(), std::move(*content));
-                }
-            }
-        },
-        [&](auto id, auto, auto) {
-            if (filter.maxResult != 0 && commits.size() == filter.maxResult)
-                return true;
-            if (id == filter.lastId)
-                return true;
-            return false;
-        });
-    return commits;
-}
-
 GitObject
 ConversationRepository::Impl::fileAtTree(const std::string& path, const GitTree& tree) const
 {
@@ -3123,10 +3047,14 @@ ConversationRepository::log(const LogOptions& options) const
     return pimpl_->log(options);
 }
 
-std::vector<std::map<std::string, std::string>>
-ConversationRepository::search(const Filter& filter) const
+void
+ConversationRepository::log(PreConditionCb&& preCondition,
+                            std::function<void(ConversationCommit&&)>&& emplaceCb,
+                            PostConditionCb&& postCondition,
+                            const std::string& from,
+                            bool logIfNotFound) const
 {
-    return pimpl_->search(filter);
+    pimpl_->forEachCommit(std::move(preCondition), std::move(emplaceCb), std::move(postCondition), from, logIfNotFound);
 }
 
 std::optional<ConversationCommit>
diff --git a/src/jamidht/conversationrepository.h b/src/jamidht/conversationrepository.h
index 60e585286e4922ae1ee6d701fc561c45fb9ed840..de96ebd8dcb3e8fee78893220f55f4cbdb7cec1c 100644
--- a/src/jamidht/conversationrepository.h
+++ b/src/jamidht/conversationrepository.h
@@ -127,6 +127,13 @@ struct ConversationMember
     }
 };
 
+enum class CallbackResult { Skip, Break, Ok };
+
+using PreConditionCb
+    = std::function<CallbackResult(const std::string&, const GitAuthor&, const GitCommit&)>;
+using PostConditionCb
+    = std::function<bool(const std::string&, const GitAuthor&, ConversationCommit&)>;
+
 /**
  * This class gives access to the git repository that represents the conversation
  */
@@ -219,16 +226,14 @@ public:
      * @return a list of commits
      */
     std::vector<ConversationCommit> log(const LogOptions& options = {}) const;
+    void log(PreConditionCb&& preCondition,
+            std::function<void(ConversationCommit&&)>&& emplaceCb,
+            PostConditionCb&& postCondition,
+            const std::string& from = "",
+            bool logIfNotFound = true) const;
     std::optional<ConversationCommit> getCommit(const std::string& commitId,
                                                 bool logIfNotFound = true) const;
 
-    /**
-     * Search in the conversation via a filter
-     * @param filter    Parameters for the search
-     * @return matching commits
-     */
-    std::vector<std::map<std::string, std::string>> search(const Filter& filter) const;
-
     /**
      * Get parent via topological + date sort in branch main of a commit
      * @param commitId      id to choice
diff --git a/test/unitTest/conversation/call.cpp b/test/unitTest/conversation/call.cpp
index c25b2ce299dcfd9b89781494497dacdd0c33a441..dd92c8fe85a6a09d788b1df55580b0021c780896 100644
--- a/test/unitTest/conversation/call.cpp
+++ b/test/unitTest/conversation/call.cpp
@@ -46,7 +46,7 @@ struct ConvData
     bool conferenceRemoved {false};
     std::string hostState {};
     std::string state {};
-    std::vector<std::map<std::string, std::string>> messages {};
+    std::vector<libjami::SwarmMessage> messages {};
 };
 
 class ConversationCallTest : public CppUnit::TestFixture
@@ -157,10 +157,10 @@ ConversationCallTest::connectSignals()
                 carlaData_.id = conversationId;
             cv.notify_one();
         }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::MessageReceived>(
+    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::SwarmMessageReceived>(
         [&](const std::string& accountId,
             const std::string& conversationId,
-            std::map<std::string, std::string> message) {
+            const libjami::SwarmMessage& message) {
             if (accountId == aliceId && aliceData_.id == conversationId)
                 aliceData_.messages.emplace_back(message);
             if (accountId == bobId && bobData_.id == conversationId)
@@ -295,7 +295,7 @@ ConversationCallTest::testActiveCalls()
     auto callId = libjami::placeCallWithMedia(aliceId, "swarm:" + aliceData_.id, {});
     // should get message
     cv.wait_for(lk, 30s, [&]() { return !aliceData_.messages.empty(); });
-    CPPUNIT_ASSERT(aliceData_.messages[0]["type"] == "application/call-history+json");
+    CPPUNIT_ASSERT(aliceData_.messages[0].type == "application/call-history+json");
 
     // get active calls = 1
     CPPUNIT_ASSERT(libjami::getActiveCalls(aliceId, aliceData_.id).size() == 1);
@@ -306,7 +306,7 @@ ConversationCallTest::testActiveCalls()
 
     // should get message
     cv.wait_for(lk, 30s, [&]() { return !aliceData_.messages.empty(); });
-    CPPUNIT_ASSERT(aliceData_.messages[0].find("duration") != aliceData_.messages[0].end());
+    CPPUNIT_ASSERT(aliceData_.messages[0].body.find("duration") != aliceData_.messages[0].body.end());
 
     // get active calls = 0
     CPPUNIT_ASSERT(libjami::getActiveCalls(aliceId, aliceData_.id).size() == 0);
@@ -352,18 +352,18 @@ ConversationCallTest::testActiveCalls3Peers()
     auto callId = libjami::placeCallWithMedia(aliceId, "swarm:" + aliceData_.id, {});
     auto lastCommitIsCall = [&](const auto& data) {
         return !data.messages.empty()
-               && data.messages.rbegin()->at("type") == "application/call-history+json";
+               && data.messages.rbegin()->body.at("type") == "application/call-history+json";
     };
     // should get message
     cv.wait_for(lk, 30s, [&]() {
         return lastCommitIsCall(aliceData_) && lastCommitIsCall(bobData_)
                && lastCommitIsCall(carlaData_);
     });
-    auto confId = bobData_.messages.rbegin()->at("confId");
+    auto confId = bobData_.messages.rbegin()->body.at("confId");
     auto destination = fmt::format("rdv:{}/{}/{}/{}",
                                    bobData_.id,
-                                   bobData_.messages.rbegin()->at("uri"),
-                                   bobData_.messages.rbegin()->at("device"),
+                                   bobData_.messages.rbegin()->body.at("uri"),
+                                   bobData_.messages.rbegin()->body.at("device"),
                                    confId);
 
     aliceData_.conferenceChanged = false;
@@ -392,9 +392,9 @@ ConversationCallTest::testActiveCalls3Peers()
         return !aliceData_.messages.empty() && !bobData_.messages.empty()
                && !carlaData_.messages.empty();
     });
-    CPPUNIT_ASSERT(aliceData_.messages[0].find("duration") != aliceData_.messages[0].end());
-    CPPUNIT_ASSERT(bobData_.messages[0].find("duration") != bobData_.messages[0].end());
-    CPPUNIT_ASSERT(carlaData_.messages[0].find("duration") != carlaData_.messages[0].end());
+    CPPUNIT_ASSERT(aliceData_.messages[0].body.find("duration") != aliceData_.messages[0].body.end());
+    CPPUNIT_ASSERT(bobData_.messages[0].body.find("duration") != bobData_.messages[0].body.end());
+    CPPUNIT_ASSERT(carlaData_.messages[0].body.find("duration") != carlaData_.messages[0].body.end());
 
     // get active calls = 0
     CPPUNIT_ASSERT(libjami::getActiveCalls(aliceId, aliceData_.id).size() == 0);
@@ -440,7 +440,7 @@ ConversationCallTest::testRejoinCall()
     auto callId = libjami::placeCallWithMedia(aliceId, "swarm:" + aliceData_.id, {});
     auto lastCommitIsCall = [&](const auto& data) {
         return !data.messages.empty()
-               && data.messages.rbegin()->at("type") == "application/call-history+json";
+               && data.messages.rbegin()->body.at("type") == "application/call-history+json";
     };
     // should get message
     cv.wait_for(lk, 30s, [&]() {
@@ -448,11 +448,11 @@ ConversationCallTest::testRejoinCall()
                && lastCommitIsCall(carlaData_);
     });
 
-    auto confId = bobData_.messages.rbegin()->at("confId");
+    auto confId = bobData_.messages.rbegin()->body.at("confId");
     auto destination = fmt::format("rdv:{}/{}/{}/{}",
                                    bobData_.id,
-                                   bobData_.messages.rbegin()->at("uri"),
-                                   bobData_.messages.rbegin()->at("device"),
+                                   bobData_.messages.rbegin()->body.at("uri"),
+                                   bobData_.messages.rbegin()->body.at("device"),
                                    confId);
 
     aliceData_.conferenceChanged = false;
@@ -500,9 +500,9 @@ ConversationCallTest::testRejoinCall()
         return !aliceData_.messages.empty() && !bobData_.messages.empty()
                && !carlaData_.messages.empty();
     });
-    CPPUNIT_ASSERT(aliceData_.messages[0].find("duration") != aliceData_.messages[0].end());
-    CPPUNIT_ASSERT(bobData_.messages[0].find("duration") != bobData_.messages[0].end());
-    CPPUNIT_ASSERT(carlaData_.messages[0].find("duration") != carlaData_.messages[0].end());
+    CPPUNIT_ASSERT(aliceData_.messages[0].body.find("duration") != aliceData_.messages[0].body.end());
+    CPPUNIT_ASSERT(bobData_.messages[0].body.find("duration") != bobData_.messages[0].body.end());
+    CPPUNIT_ASSERT(carlaData_.messages[0].body.find("duration") != carlaData_.messages[0].body.end());
 
     // get active calls = 0
     CPPUNIT_ASSERT(libjami::getActiveCalls(aliceId, aliceData_.id).size() == 0);
@@ -536,7 +536,7 @@ ConversationCallTest::testParticipantHangupConfNotRemoved()
     auto callId = libjami::placeCallWithMedia(aliceId, "swarm:" + aliceData_.id, {});
     auto lastCommitIsCall = [&](const auto& data) {
         return !data.messages.empty()
-               && data.messages.rbegin()->at("type") == "application/call-history+json";
+               && data.messages.rbegin()->body.at("type") == "application/call-history+json";
     };
     // should get message
     cv.wait_for(lk, 30s, [&]() {
@@ -545,9 +545,9 @@ ConversationCallTest::testParticipantHangupConfNotRemoved()
 
     auto destination = fmt::format("rdv:{}/{}/{}/{}",
                                    bobData_.id,
-                                   bobData_.messages.rbegin()->at("uri"),
-                                   bobData_.messages.rbegin()->at("device"),
-                                   bobData_.messages.rbegin()->at("confId"));
+                                   bobData_.messages.rbegin()->body.at("uri"),
+                                   bobData_.messages.rbegin()->body.at("device"),
+                                   bobData_.messages.rbegin()->body.at("confId"));
 
     aliceData_.conferenceChanged = false;
     auto bobCallId = libjami::placeCallWithMedia(bobId, destination, {});
@@ -600,19 +600,19 @@ ConversationCallTest::testJoinFinishedCall()
     auto callId = libjami::placeCallWithMedia(aliceId, "swarm:" + aliceData_.id, {});
     auto lastCommitIsCall = [&](const auto& data) {
         return !data.messages.empty()
-               && data.messages.rbegin()->at("type") == "application/call-history+json";
+               && data.messages.rbegin()->body.at("type") == "application/call-history+json";
     };
     // should get message
     cv.wait_for(lk, 30s, [&]() {
         return lastCommitIsCall(aliceData_) && lastCommitIsCall(bobData_)
                && lastCommitIsCall(carlaData_);
     });
-    auto confId = bobData_.messages.rbegin()->at("confId");
+    auto confId = bobData_.messages.rbegin()->body.at("confId");
     auto destination = fmt::format("rdv:{}/{}/{}/{}",
                                    bobData_.id,
-                                   bobData_.messages.rbegin()->at("uri"),
-                                   bobData_.messages.rbegin()->at("device"),
-                                   bobData_.messages.rbegin()->at("confId"));
+                                   bobData_.messages.rbegin()->body.at("uri"),
+                                   bobData_.messages.rbegin()->body.at("device"),
+                                   bobData_.messages.rbegin()->body.at("confId"));
     // hangup
     aliceData_.messages.clear();
     bobData_.messages.clear();
@@ -635,7 +635,7 @@ ConversationCallTest::testJoinFinishedCall()
         return lastCommitIsCall(aliceData_) && lastCommitIsCall(bobData_)
                && lastCommitIsCall(carlaData_) && bobData_.hostState == "CURRENT";
     });
-    confId = bobData_.messages.rbegin()->at("confId");
+    confId = bobData_.messages.rbegin()->body.at("confId");
     CPPUNIT_ASSERT(libjami::getActiveCalls(aliceId, aliceData_.id).size() == 1);
     // hangup
     aliceData_.messages.clear();
@@ -647,9 +647,9 @@ ConversationCallTest::testJoinFinishedCall()
         return !aliceData_.messages.empty() && !bobData_.messages.empty()
                && !carlaData_.messages.empty();
     });
-    CPPUNIT_ASSERT(aliceData_.messages[0].find("duration") != aliceData_.messages[0].end());
-    CPPUNIT_ASSERT(bobData_.messages[0].find("duration") != bobData_.messages[0].end());
-    CPPUNIT_ASSERT(carlaData_.messages[0].find("duration") != carlaData_.messages[0].end());
+    CPPUNIT_ASSERT(aliceData_.messages[0].body.find("duration") != aliceData_.messages[0].body.end());
+    CPPUNIT_ASSERT(bobData_.messages[0].body.find("duration") != bobData_.messages[0].body.end());
+    CPPUNIT_ASSERT(carlaData_.messages[0].body.find("duration") != carlaData_.messages[0].body.end());
     // get active calls = 0
     CPPUNIT_ASSERT(libjami::getActiveCalls(aliceId, aliceData_.id).size() == 0);
 }
@@ -697,7 +697,7 @@ ConversationCallTest::testJoinFinishedCallForbidden()
     auto callId = libjami::placeCallWithMedia(aliceId, "swarm:" + aliceData_.id, {});
     auto lastCommitIsCall = [&](const auto& data) {
         return !data.messages.empty()
-               && data.messages.rbegin()->at("type") == "application/call-history+json";
+               && data.messages.rbegin()->body.at("type") == "application/call-history+json";
     };
     // should get message
     cv.wait_for(lk, 30s, [&]() {
@@ -705,12 +705,12 @@ ConversationCallTest::testJoinFinishedCallForbidden()
                && lastCommitIsCall(carlaData_);
     });
 
-    auto confId = bobData_.messages.rbegin()->at("confId");
+    auto confId = bobData_.messages.rbegin()->body.at("confId");
     auto destination = fmt::format("rdv:{}/{}/{}/{}",
                                    bobData_.id,
-                                   bobData_.messages.rbegin()->at("uri"),
-                                   bobData_.messages.rbegin()->at("device"),
-                                   bobData_.messages.rbegin()->at("confId"));
+                                   bobData_.messages.rbegin()->body.at("uri"),
+                                   bobData_.messages.rbegin()->body.at("device"),
+                                   bobData_.messages.rbegin()->body.at("confId"));
 
     // hangup
     aliceData_.messages.clear();
@@ -738,7 +738,7 @@ ConversationCallTest::testJoinFinishedCallForbidden()
                && lastCommitIsCall(carlaData_) && bobData_.hostState == "CURRENT";
     });
 
-    confId = bobData_.messages.rbegin()->at("confId");
+    confId = bobData_.messages.rbegin()->body.at("confId");
 
     CPPUNIT_ASSERT(libjami::getActiveCalls(aliceId, aliceData_.id).size() == 1);
 
@@ -753,9 +753,9 @@ ConversationCallTest::testJoinFinishedCallForbidden()
         return !aliceData_.messages.empty() && !bobData_.messages.empty()
                && !carlaData_.messages.empty();
     });
-    CPPUNIT_ASSERT(aliceData_.messages[0].find("duration") != aliceData_.messages[0].end());
-    CPPUNIT_ASSERT(bobData_.messages[0].find("duration") != bobData_.messages[0].end());
-    CPPUNIT_ASSERT(carlaData_.messages[0].find("duration") != carlaData_.messages[0].end());
+    CPPUNIT_ASSERT(aliceData_.messages[0].body.find("duration") != aliceData_.messages[0].body.end());
+    CPPUNIT_ASSERT(bobData_.messages[0].body.find("duration") != bobData_.messages[0].body.end());
+    CPPUNIT_ASSERT(carlaData_.messages[0].body.find("duration") != carlaData_.messages[0].body.end());
 
     // get active calls = 0
     CPPUNIT_ASSERT(libjami::getActiveCalls(aliceId, aliceData_.id).size() == 0);
@@ -787,7 +787,7 @@ ConversationCallTest::testUsePreference()
     bobData_.messages.clear();
     auto lastCommitIsProfile = [&](const auto& data) {
         return !data.messages.empty()
-               && data.messages.rbegin()->at("type") == "application/update-profile";
+               && data.messages.rbegin()->body.at("type") == "application/update-profile";
     };
     libjami::updateConversationInfos(aliceId,
                                      aliceData_.id,
@@ -806,13 +806,13 @@ ConversationCallTest::testUsePreference()
     auto callId = libjami::placeCallWithMedia(bobId, "swarm:" + aliceData_.id, {});
     auto lastCommitIsCall = [&](const auto& data) {
         return !data.messages.empty()
-               && data.messages.rbegin()->at("type") == "application/call-history+json";
+               && data.messages.rbegin()->body.at("type") == "application/call-history+json";
     };
     // should get message
     cv.wait_for(lk, 30s, [&]() {
         return lastCommitIsCall(aliceData_) && lastCommitIsCall(bobData_);
     });
-    auto confId = bobData_.messages.rbegin()->at("confId");
+    auto confId = bobData_.messages.rbegin()->body.at("confId");
 
     // Alice should be the host
     CPPUNIT_ASSERT(aliceAccount->getConference(confId));
@@ -837,12 +837,12 @@ ConversationCallTest::testJoinWhileActiveCall()
     auto callId = libjami::placeCallWithMedia(aliceId, "swarm:" + aliceData_.id, {});
     auto lastCommitIsCall = [&](const auto& data) {
         return !data.messages.empty()
-               && data.messages.rbegin()->at("type") == "application/call-history+json";
+               && data.messages.rbegin()->body.at("type") == "application/call-history+json";
     };
     // should get message
     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return lastCommitIsCall(aliceData_); }));
 
-    auto confId = aliceData_.messages.rbegin()->at("confId");
+    auto confId = aliceData_.messages.rbegin()->body.at("confId");
     libjami::addConversationMember(aliceId, aliceData_.id, bobUri);
     CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&]() { return bobData_.requestReceived; }));
 
@@ -887,7 +887,7 @@ ConversationCallTest::testCallSelfIfDefaultHost()
     bobData_.messages.clear();
     auto lastCommitIsProfile = [&](const auto& data) {
         return !data.messages.empty()
-               && data.messages.rbegin()->at("type") == "application/update-profile";
+               && data.messages.rbegin()->body.at("type") == "application/update-profile";
     };
     libjami::updateConversationInfos(aliceId,
                                      aliceData_.id,
@@ -906,13 +906,13 @@ ConversationCallTest::testCallSelfIfDefaultHost()
     auto callId = libjami::placeCallWithMedia(aliceId, "swarm:" + aliceData_.id, {});
     auto lastCommitIsCall = [&](const auto& data) {
         return !data.messages.empty()
-               && data.messages.rbegin()->at("type") == "application/call-history+json";
+               && data.messages.rbegin()->body.at("type") == "application/call-history+json";
     };
     // should get message
     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
         return lastCommitIsCall(aliceData_) && lastCommitIsCall(bobData_);
     }));
-    auto confId = aliceData_.messages.rbegin()->at("confId");
+    auto confId = aliceData_.messages.rbegin()->body.at("confId");
     // Alice should be the host
     CPPUNIT_ASSERT(aliceAccount->getConference(confId));
     Manager::instance().hangupConference(aliceId, confId);
@@ -945,7 +945,7 @@ ConversationCallTest::testNeedsHost()
     bobData_.messages.clear();
     auto lastCommitIsProfile = [&](const auto& data) {
         return !data.messages.empty()
-               && data.messages.rbegin()->at("type") == "application/update-profile";
+               && data.messages.rbegin()->body.at("type") == "application/update-profile";
     };
     libjami::updateConversationInfos(aliceId,
                                      aliceData_.id,
@@ -996,7 +996,7 @@ ConversationCallTest::testAudioOnly()
     auto callId = libjami::placeCallWithMedia(aliceId, "swarm:" + aliceData_.id, mediaList);
     // should get message
     cv.wait_for(lk, 30s, [&]() { return !aliceData_.messages.empty() && !pInfos_.empty(); });
-    CPPUNIT_ASSERT(aliceData_.messages[0]["type"] == "application/call-history+json");
+    CPPUNIT_ASSERT(aliceData_.messages[0].type == "application/call-history+json");
     CPPUNIT_ASSERT(pInfos_.size() == 1);
     CPPUNIT_ASSERT(pInfos_[0]["videoMuted"] == "true");
 
@@ -1006,7 +1006,7 @@ ConversationCallTest::testAudioOnly()
 
     // should get message
     cv.wait_for(lk, 30s, [&]() { return !aliceData_.messages.empty(); });
-    CPPUNIT_ASSERT(aliceData_.messages[0].find("duration") != aliceData_.messages[0].end());
+    CPPUNIT_ASSERT(aliceData_.messages[0].body.find("duration") != aliceData_.messages[0].body.end());
 
     // get active calls = 0
     CPPUNIT_ASSERT(libjami::getActiveCalls(aliceId, aliceData_.id).size() == 0);
@@ -1052,13 +1052,13 @@ ConversationCallTest::testJoinAfterMuteHost()
     auto callId = libjami::placeCallWithMedia(aliceId, "swarm:" + aliceData_.id, mediaList);
     auto lastCommitIsCall = [&](const auto& data) {
         return !data.messages.empty()
-               && data.messages.rbegin()->at("type") == "application/call-history+json";
+               && data.messages.rbegin()->type == "application/call-history+json";
     };
     // should get message
     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
         return lastCommitIsCall(aliceData_) && lastCommitIsCall(bobData_) && !pInfos_.empty();
     }));
-    auto confId = bobData_.messages.rbegin()->at("confId");
+    auto confId = bobData_.messages.rbegin()->body.at("confId");
 
     // Mute host
     auto call = std::dynamic_pointer_cast<SIPCall>(aliceAccount->getCall(callId));
@@ -1071,8 +1071,8 @@ ConversationCallTest::testJoinAfterMuteHost()
     // Bob join, alice must stay muted
     auto destination = fmt::format("rdv:{}/{}/{}/{}",
                                    bobData_.id,
-                                   bobData_.messages.rbegin()->at("uri"),
-                                   bobData_.messages.rbegin()->at("device"),
+                                   bobData_.messages.rbegin()->body.at("uri"),
+                                   bobData_.messages.rbegin()->body.at("device"),
                                    confId);
 
     aliceData_.conferenceChanged = false;
diff --git a/test/unitTest/conversation/conversation.cpp b/test/unitTest/conversation/conversation.cpp
index a092886c7c04dc1277bb1cd0362bbd810b38dd5f..4fbf617afd7c047338199269922d3fef28ee6428 100644
--- a/test/unitTest/conversation/conversation.cpp
+++ b/test/unitTest/conversation/conversation.cpp
@@ -56,6 +56,29 @@ struct ConvInfoTest
 namespace jami {
 namespace test {
 
+struct UserData {
+    std::string conversationId;
+    bool removed {false};
+    bool requestReceived {false};
+    bool errorDetected {false};
+    bool registered {false};
+    bool stopped {false};
+    bool deviceAnnounced {false};
+    bool composing {false};
+    bool sending {false};
+    bool sent {false};
+    bool searchFinished {false};
+    std::string profilePath;
+    std::string payloadTrustRequest;
+    std::map<std::string, std::string> profile;
+    std::vector<libjami::SwarmMessage> messages;
+    std::vector<libjami::SwarmMessage> messagesUpdated;
+    std::vector<std::map<std::string, std::string>> reactions;
+    std::vector<std::map<std::string, std::string>> messagesFound;
+    std::vector<std::string> reactionRemoved;
+    std::map<std::string, std::string> preferences;
+};
+
 class ConversationTest : public CppUnit::TestFixture
 {
 public:
@@ -67,15 +90,22 @@ public:
                                        const std::string& fakeCert = "");
 
     std::string aliceId;
+    UserData aliceData;
     std::string alice2Id;
+    UserData alice2Data;
     std::string bobId;
+    UserData bobData;
     std::string bob2Id;
+    UserData bob2Data;
     std::string carlaId;
+    UserData carlaData;
 
     std::mutex mtx;
     std::unique_lock<std::mutex> lk {mtx};
     std::condition_variable cv;
 
+    void connectSignals();
+
 private:
     void testCreateConversation();
     void testCreateConversationInvalidDisplayName();
@@ -200,10 +230,266 @@ ConversationTest::setUp()
     bobId = actors["bob"];
     carlaId = actors["carla"];
 
+    aliceData = {};
+    alice2Data = {};
+    bobData = {};
+    bob2Data = {};
+    carlaData = {};
+
     Manager::instance().sendRegister(carlaId, false);
     wait_for_announcement_of({aliceId, bobId});
 }
 
+void
+ConversationTest::connectSignals()
+{
+    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
+    confHandlers.insert(
+        libjami::exportable_callback<libjami::ConfigurationSignal::VolatileDetailsChanged>(
+            [&](const std::string& accountId, const std::map<std::string, std::string>&) {
+                if (accountId == aliceId) {
+                    auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
+                    auto details = aliceAccount->getVolatileAccountDetails();
+                    auto daemonStatus = details[libjami::Account::ConfProperties::Registration::STATUS];
+                    if (daemonStatus == "REGISTERED") {
+                        aliceData.registered = true;
+                    } else if (daemonStatus == "UNREGISTERED") {
+                        aliceData.stopped = true;
+                    }
+                    auto deviceAnnounced = details[libjami::Account::VolatileProperties::DEVICE_ANNOUNCED];
+                    aliceData.deviceAnnounced = deviceAnnounced == "true";
+                } else if (accountId == bobId) {
+                    auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
+                    auto details = bobAccount->getVolatileAccountDetails();
+                    auto daemonStatus = details[libjami::Account::ConfProperties::Registration::STATUS];
+                    if (daemonStatus == "REGISTERED") {
+                        bobData.registered = true;
+                    } else if (daemonStatus == "UNREGISTERED") {
+                        bobData.stopped = true;
+                    }
+                    auto deviceAnnounced = details[libjami::Account::VolatileProperties::DEVICE_ANNOUNCED];
+                    bobData.deviceAnnounced = deviceAnnounced == "true";
+                } else if (accountId == bob2Id) {
+                    auto bob2Account = Manager::instance().getAccount<JamiAccount>(bob2Id);
+                    auto details = bob2Account->getVolatileAccountDetails();
+                    auto daemonStatus = details[libjami::Account::ConfProperties::Registration::STATUS];
+                    if (daemonStatus == "REGISTERED") {
+                        bob2Data.registered = true;
+                    } else if (daemonStatus == "UNREGISTERED") {
+                        bob2Data.stopped = true;
+                    }
+                    auto deviceAnnounced = details[libjami::Account::VolatileProperties::DEVICE_ANNOUNCED];
+                    bob2Data.deviceAnnounced = deviceAnnounced == "true";
+                } else if (accountId == carlaId) {
+                    auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
+                    auto details = carlaAccount->getVolatileAccountDetails();
+                    auto daemonStatus = details[libjami::Account::ConfProperties::Registration::STATUS];
+                    if (daemonStatus == "REGISTERED") {
+                        carlaData.registered = true;
+                    } else if (daemonStatus == "UNREGISTERED") {
+                        carlaData.stopped = true;
+                    }
+                    auto deviceAnnounced = details[libjami::Account::VolatileProperties::DEVICE_ANNOUNCED];
+                    carlaData.deviceAnnounced = deviceAnnounced == "true";
+                }
+                cv.notify_one();
+            }));
+    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
+        [&](const std::string& accountId, const std::string& conversationId) {
+            if (accountId == aliceId) {
+                aliceData.conversationId = conversationId;
+            } else if (accountId == alice2Id) {
+                alice2Data.conversationId = conversationId;
+            } else if (accountId == bobId) {
+                bobData.conversationId = conversationId;
+            } else if (accountId == bob2Id) {
+                bob2Data.conversationId = conversationId;
+            } else if (accountId == carlaId) {
+                carlaData.conversationId = conversationId;
+            }
+            cv.notify_one();
+        }));
+    confHandlers.insert(
+        libjami::exportable_callback<libjami::ConfigurationSignal::IncomingTrustRequest>(
+            [&](const std::string& account_id,
+                const std::string& /*from*/,
+                const std::string& /*conversationId*/,
+                const std::vector<uint8_t>& payload,
+                time_t /*received*/) {
+                auto payloadStr = std::string(payload.data(), payload.data() + payload.size());
+                if (account_id == aliceId)
+                    aliceData.payloadTrustRequest = payloadStr;
+                else if (account_id == bobId)
+                    bobData.payloadTrustRequest = payloadStr;
+                cv.notify_one();
+            }));
+    confHandlers.insert(
+        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
+            [&](const std::string& accountId,
+                const std::string& /* conversationId */,
+                std::map<std::string, std::string> /*metadatas*/) {
+                if (accountId == aliceId) {
+                    aliceData.requestReceived = true;
+                } else if (accountId == bobId) {
+                    bobData.requestReceived = true;
+                } else if (accountId == bob2Id) {
+                    bob2Data.requestReceived = true;
+                } else if (accountId == carlaId) {
+                    carlaData.requestReceived = true;
+                }
+                cv.notify_one();
+            }));
+    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::SwarmMessageReceived>(
+        [&](const std::string& accountId,
+            const std::string& /* conversationId */,
+            libjami::SwarmMessage message) {
+            if (accountId == aliceId) {
+                aliceData.messages.emplace_back(message);
+            } else if (accountId == bobId) {
+                bobData.messages.emplace_back(message);
+            } else if (accountId == carlaId) {
+                carlaData.messages.emplace_back(message);
+            }
+            cv.notify_one();
+        }));
+    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::SwarmMessageUpdated>(
+        [&](const std::string& accountId,
+            const std::string& /* conversationId */,
+            libjami::SwarmMessage message) {
+            if (accountId == aliceId) {
+                aliceData.messagesUpdated.emplace_back(message);
+            } else if (accountId == bobId) {
+                bobData.messagesUpdated.emplace_back(message);
+            } else if (accountId == carlaId) {
+                carlaData.messagesUpdated.emplace_back(message);
+            }
+            cv.notify_one();
+        }));
+    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ReactionAdded>(
+        [&](const std::string& accountId,
+            const std::string& /* conversationId */,
+            const std::string& /* messageId */,
+            std::map<std::string, std::string> reaction) {
+            if (accountId == aliceId) {
+                aliceData.reactions.emplace_back(reaction);
+            } else if (accountId == bobId) {
+                bobData.reactions.emplace_back(reaction);
+            } else if (accountId == carlaId) {
+                carlaData.reactions.emplace_back(reaction);
+            }
+            cv.notify_one();
+        }));
+    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ReactionRemoved>(
+        [&](const std::string& accountId,
+            const std::string& /* conversationId */,
+            const std::string& /* messageId */,
+            const std::string& reactionId) {
+            if (accountId == aliceId) {
+                aliceData.reactionRemoved.emplace_back(reactionId);
+            } else if (accountId == bobId) {
+                bobData.reactionRemoved.emplace_back(reactionId);
+            } else if (accountId == carlaId) {
+                carlaData.reactionRemoved.emplace_back(reactionId);
+            }
+            cv.notify_one();
+        }));
+    confHandlers.insert(
+        libjami::exportable_callback<libjami::ConversationSignal::OnConversationError>(
+            [&](const std::string& accountId,
+                const std::string& /* conversationId */,
+                int /*code*/,
+                const std::string& /* what */) {
+                if (accountId == aliceId)
+                    aliceData.errorDetected = true;
+                else if (accountId == bobId)
+                    bobData.errorDetected = true;
+                else if (accountId == carlaId)
+                    carlaData.errorDetected = true;
+                cv.notify_one();
+            }));
+    confHandlers.insert(
+        libjami::exportable_callback<libjami::ConfigurationSignal::ComposingStatusChanged>(
+            [&](const std::string& accountId,
+                const std::string& /*conversationId*/,
+                const std::string& /*peer*/,
+                bool state) {
+                if (accountId == bobId) {
+                    bobData.composing = state;
+                }
+                cv.notify_one();
+            }));
+    confHandlers.insert(
+        libjami::exportable_callback<libjami::ConfigurationSignal::AccountMessageStatusChanged>(
+            [&](const std::string& accountId,
+                const std::string& /*conversationId*/,
+                const std::string& /*peer*/,
+                const std::string& /*msgId*/,
+                int status) {
+                if (accountId == aliceId) {
+                    if (status == 2)
+                        aliceData.sending = true;
+                    if (status == 3)
+                        aliceData.sent = true;
+                } else if (accountId == bobId) {
+                    if (status == 2)
+                        bobData.sending = true;
+                    if (status == 3)
+                        bobData.sent = true;
+                }
+                cv.notify_one();
+            }));
+    confHandlers.insert(
+        libjami::exportable_callback<libjami::ConversationSignal::ConversationProfileUpdated>(
+            [&](const auto& accountId, const auto& /* conversationId */, const auto& profile) {
+                if (accountId == aliceId) {
+                    aliceData.profile = profile;
+                } else if (accountId == bobId) {
+                    bobData.profile = profile;
+                }
+                cv.notify_one();
+            }));
+    confHandlers.insert(
+        libjami::exportable_callback<libjami::ConversationSignal::ConversationRemoved>(
+            [&](const std::string& accountId, const std::string&) {
+                if (accountId == aliceId)
+                    aliceData.removed = true;
+                else if (accountId == bobId)
+                    bobData.removed = true;
+                else if (accountId == bob2Id)
+                    bob2Data.removed = true;
+                cv.notify_one();
+            }));
+    confHandlers.insert(libjami::exportable_callback<libjami::ConfigurationSignal::ProfileReceived>(
+        [&](const std::string& accountId, const std::string& peerId, const std::string& path) {
+            if (accountId == bobId)
+                bobData.profilePath = path;
+            cv.notify_one();
+        }));
+    confHandlers.insert(
+        libjami::exportable_callback<libjami::ConversationSignal::ConversationPreferencesUpdated>(
+            [&](const std::string& accountId,
+                const std::string& conversationId,
+                std::map<std::string, std::string> preferences) {
+                if (accountId == bobId)
+                    bobData.preferences = preferences;
+                else if (accountId == bob2Id)
+                    bob2Data.preferences = preferences;
+                cv.notify_one();
+            }));
+    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::MessagesFound>(
+        [&](uint32_t,
+            const std::string& accountId,
+            const std::string& conversationId,
+            std::vector<std::map<std::string, std::string>> msg) {
+            if (accountId == aliceId) {
+                aliceData.messagesFound.insert(aliceData.messagesFound.end(), msg.begin(), msg.end());
+                aliceData.searchFinished = conversationId.empty();
+            }
+            cv.notify_one();
+        }));
+    libjami::registerSignalHandlers(confHandlers);
+}
+
 void
 ConversationTest::tearDown()
 {
@@ -226,31 +512,20 @@ void
 ConversationTest::testCreateConversation()
 {
     std::cout << "\nRunning test: " << __func__ << std::endl;
+    connectSignals();
 
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto aliceDeviceId = aliceAccount->currentDeviceId();
     auto uri = aliceAccount->getUsername();
 
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    bool conversationReady = false;
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& /* conversationId */) {
-            if (accountId == aliceId) {
-                conversationReady = true;
-                cv.notify_one();
-            }
-        }));
-    libjami::registerSignalHandlers(confHandlers);
-
     // Start conversation
     auto convId = libjami::startConversation(aliceId);
-    cv.wait_for(lk, 30s, [&]() { return conversationReady; });
-    CPPUNIT_ASSERT(conversationReady);
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !aliceData.conversationId.empty(); }));
     ConversationRepository repo(aliceAccount, convId);
     CPPUNIT_ASSERT(repo.mode() == ConversationMode::INVITES_ONLY);
 
     // Assert that repository exists
-    auto repoPath = fileutils::get_data_dir() / aliceAccount->getAccountID()
+    auto repoPath = fileutils::get_data_dir() / aliceId
                     / "conversations" / convId;
     CPPUNIT_ASSERT(std::filesystem::is_directory(repoPath));
     // Check created files
@@ -341,24 +616,14 @@ void
 ConversationTest::testGetConversationsAfterRm()
 {
     std::cout << "\nRunning test: " << __func__ << std::endl;
+    connectSignals();
 
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto uri = aliceAccount->getUsername();
 
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    bool conversationReady = false;
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& /* conversationId */) {
-            if (accountId == aliceId) {
-                conversationReady = true;
-                cv.notify_one();
-            }
-        }));
-    libjami::registerSignalHandlers(confHandlers);
-
     // Start conversation
     auto convId = libjami::startConversation(aliceId);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return conversationReady; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !aliceData.conversationId.empty(); }));
 
     auto conversations = libjami::getConversations(aliceId);
     CPPUNIT_ASSERT(conversations.size() == 1);
@@ -371,24 +636,11 @@ void
 ConversationTest::testRemoveInvalidConversation()
 {
     std::cout << "\nRunning test: " << __func__ << std::endl;
-
-    auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
-    auto uri = aliceAccount->getUsername();
-
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    bool conversationReady = false;
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& /* conversationId */) {
-            if (accountId == aliceId) {
-                conversationReady = true;
-                cv.notify_one();
-            }
-        }));
-    libjami::registerSignalHandlers(confHandlers);
+    connectSignals();
 
     // Start conversation
     auto convId = libjami::startConversation(aliceId);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return conversationReady; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !aliceData.conversationId.empty(); }));
 
     auto conversations = libjami::getConversations(aliceId);
     CPPUNIT_ASSERT(conversations.size() == 1);
@@ -401,203 +653,86 @@ void
 ConversationTest::testSendMessage()
 {
     std::cout << "\nRunning test: " << __func__ << std::endl;
+    connectSignals();
 
-    auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto bobUri = bobAccount->getUsername();
 
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    auto messageBobReceived = 0, messageAliceReceived = 0;
-    bool requestReceived = false;
-    bool conversationReady = false;
-    confHandlers.insert(libjami::exportable_callback<libjami::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(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
-            [&](const std::string& /*accountId*/,
-                const std::string& /* conversationId */,
-                std::map<std::string, std::string> /*metadatas*/) {
-                requestReceived = true;
-                cv.notify_one();
-            }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& /* conversationId */) {
-            if (accountId == bobId) {
-                conversationReady = true;
-                cv.notify_one();
-            }
-        }));
-    libjami::registerSignalHandlers(confHandlers);
-
     auto convId = libjami::startConversation(aliceId);
-
     libjami::addConversationMember(aliceId, convId, bobUri);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
 
     libjami::acceptConversationRequest(bobId, convId);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return conversationReady; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !bobData.conversationId.empty(); }));
 
     // Assert that repository exists
-    auto repoPath = fileutils::get_data_dir() / bobAccount->getAccountID()
+    auto repoPath = fileutils::get_data_dir() / bobId
                     / "conversations" / convId;
     CPPUNIT_ASSERT(std::filesystem::is_directory(repoPath));
     // Wait that alice sees Bob
-    cv.wait_for(lk, 30s, [&]() { return messageAliceReceived == 2; });
+    cv.wait_for(lk, 30s, [&]() { return aliceData.messages.size() == 2; });
 
+    auto bobMsgSize = bobData.messages.size();
     libjami::sendMessage(aliceId, convId, "hi"s, "");
-    cv.wait_for(lk, 30s, [&]() { return messageBobReceived == 1; });
+    cv.wait_for(lk, 30s, [&]() { return bobData.messages.size() == bobMsgSize + 1; });
 }
 
 void
 ConversationTest::testSendMessageWithBadDisplayName()
 {
     std::cout << "\nRunning test: " << __func__ << std::endl;
+    connectSignals();
 
-    auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto bobUri = bobAccount->getUsername();
 
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    auto messageBobReceived = 0, messageAliceReceived = 0;
-    bool requestReceived = false;
-    bool conversationReady = false;
-    confHandlers.insert(libjami::exportable_callback<libjami::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(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
-            [&](const std::string& /*accountId*/,
-                const std::string& /* conversationId */,
-                std::map<std::string, std::string> /*metadatas*/) {
-                requestReceived = true;
-                cv.notify_one();
-            }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& /* conversationId */) {
-            if (accountId == bobId) {
-                conversationReady = true;
-                cv.notify_one();
-            }
-        }));
-    bool aliceRegistered = false;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConfigurationSignal::VolatileDetailsChanged>(
-            [&](const std::string&, const std::map<std::string, std::string>&) {
-                auto details = aliceAccount->getVolatileAccountDetails();
-                auto daemonStatus = details[libjami::Account::ConfProperties::Registration::STATUS];
-                if (daemonStatus == "REGISTERED") {
-                    aliceRegistered = true;
-                    cv.notify_one();
-                }
-            }));
-    libjami::registerSignalHandlers(confHandlers);
-
     std::map<std::string, std::string> details;
     details[ConfProperties::DISPLAYNAME] = "<o>";
     libjami::setAccountDetails(aliceId, details);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceRegistered; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceData.registered; }));
 
     auto convId = libjami::startConversation(aliceId);
-
     libjami::addConversationMember(aliceId, convId, bobUri);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
 
     libjami::acceptConversationRequest(bobId, convId);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return conversationReady; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !bobData.conversationId.empty(); }));
 
     // Assert that repository exists
-    auto repoPath = fileutils::get_data_dir() / bobAccount->getAccountID()
+    auto repoPath = fileutils::get_data_dir() / bobId
                     / "conversations" / convId;
     CPPUNIT_ASSERT(std::filesystem::is_directory(repoPath));
     // Wait that alice sees Bob
-    cv.wait_for(lk, 30s, [&]() { return messageAliceReceived == 2; });
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceData.messages.size() == 2; }));
 
+    auto bobMsgSize = bobData.messages.size();
     libjami::sendMessage(aliceId, convId, "hi"s, "");
-    cv.wait_for(lk, 30s, [&]() { return messageBobReceived == 1; });
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.messages.size() == bobMsgSize + 1; }));
 }
 
 void
 ConversationTest::testReplaceWithBadCertificate()
 {
     std::cout << "\nRunning test: " << __func__ << std::endl;
+    connectSignals();
 
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto bobUri = bobAccount->getUsername();
 
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    auto messageBobReceived = 0, messageAliceReceived = 0;
-    confHandlers.insert(libjami::exportable_callback<libjami::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();
-        }));
-    auto requestReceived = false;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
-            [&](const std::string& /*accountId*/,
-                const std::string& /* conversationId */,
-                std::map<std::string, std::string> /*metadatas*/) {
-                requestReceived = true;
-                cv.notify_one();
-            }));
-    auto conversationReady = false;
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& /* conversationId */) {
-            if (accountId == bobId) {
-                conversationReady = true;
-                cv.notify_one();
-            }
-        }));
-    auto errorDetected = false;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::OnConversationError>(
-            [&](const std::string& /* accountId */,
-                const std::string& /* conversationId */,
-                int code,
-                const std::string& /* what */) {
-                if (code == 3)
-                    errorDetected = true;
-                cv.notify_one();
-            }));
-    libjami::registerSignalHandlers(confHandlers);
-
     auto convId = libjami::startConversation(aliceId);
 
     libjami::addConversationMember(aliceId, convId, bobUri);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
 
     libjami::acceptConversationRequest(bobId, convId);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return conversationReady; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !bobData.conversationId.empty(); }));
 
     // Wait that alice sees Bob
-    cv.wait_for(lk, 30s, [&]() { return messageAliceReceived == 2; });
+    cv.wait_for(lk, 30s, [&]() { return aliceData.messages.size() == 2; });
 
     // Replace alice's certificate with a bad one.
-    auto repoPath = fileutils::get_data_dir() / aliceAccount->getAccountID() / "conversations" / convId;
+    auto repoPath = fileutils::get_data_dir() / aliceId / "conversations" / convId;
     auto aliceDevicePath = repoPath / "devices" / fmt::format("{}.crt", aliceAccount->currentDeviceId());
     auto bobDevicePath = repoPath / "devices" / fmt::format("{}.crt", bobAccount->currentDeviceId());
     std::filesystem::copy(bobDevicePath,
@@ -613,50 +748,33 @@ ConversationTest::testReplaceWithBadCertificate()
     wbuilder["commentStyle"] = "None";
     wbuilder["indentation"] = "";
     auto message = Json::writeString(wbuilder, root);
-    messageBobReceived = 0;
     commitInRepo(repoPath, aliceAccount, message);
     // now we need to sync!
+    bobData.errorDetected = false;
     libjami::sendMessage(aliceId, convId, "trigger sync!"s, "");
     // We should detect the incorrect commit!
-    cv.wait_for(lk, 30s, [&]() { return errorDetected; });
+    cv.wait_for(lk, 30s, [&]() { return bobData.errorDetected; });
 }
 
 void
 ConversationTest::testSendMessageTriggerMessageReceived()
 {
     std::cout << "\nRunning test: " << __func__ << std::endl;
-
-    auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    auto messageReceived = 0;
-    bool conversationReady = false;
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::MessageReceived>(
-        [&](const std::string& /* accountId */,
-            const std::string& /* conversationId */,
-            std::map<std::string, std::string> /*message*/) {
-            messageReceived += 1;
-            cv.notify_one();
-        }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& /* accountId */, const std::string& /* conversationId */) {
-            conversationReady = true;
-            cv.notify_one();
-        }));
-    libjami::registerSignalHandlers(confHandlers);
+    connectSignals();
 
     auto convId = libjami::startConversation(aliceId);
-    cv.wait_for(lk, 30s, [&] { return conversationReady; });
+    cv.wait_for(lk, 30s, [&] { return !aliceData.conversationId.empty(); });
 
+    auto msgSize = aliceData.messages.size();
     libjami::sendMessage(aliceId, convId, "hi"s, "");
-    cv.wait_for(lk, 30s, [&] { return messageReceived == 1; });
-    CPPUNIT_ASSERT(messageReceived == 1);
-    libjami::unregisterSignalHandlers();
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return aliceData.messages.size() == msgSize + 1; }));
 }
 
 void
 ConversationTest::testMergeTwoDifferentHeads()
 {
     std::cout << "\nRunning test: " << __func__ << std::endl;
+    connectSignals();
 
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
@@ -666,33 +784,15 @@ ConversationTest::testMergeTwoDifferentHeads()
     carlaAccount->trackBuddyPresence(aliceUri, true);
     auto convId = libjami::startConversation(aliceId);
 
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    bool conversationReady = false, carlaGotMessage = false;
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& /* conversationId */) {
-            if (accountId == carlaId) {
-                conversationReady = true;
-                cv.notify_one();
-            }
-        }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::MessageReceived>(
-        [&](const std::string& accountId,
-            const std::string& conversationId,
-            std::map<std::string, std::string> /* message */) {
-            if (accountId == carlaId && conversationId == convId) {
-                carlaGotMessage = true;
-            }
-            cv.notify_one();
-        }));
-    libjami::registerSignalHandlers(confHandlers);
-
+    auto msgSize = aliceData.messages.size();
     aliceAccount->convModule()->addConversationMember(convId, carlaUri, false);
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceData.messages.size() == msgSize + 1; }));
 
     // Cp conversations & convInfo
-    auto repoPathAlice = fileutils::get_data_dir() / aliceAccount->getAccountID() / "conversations";
+    auto repoPathAlice = fileutils::get_data_dir() / aliceId / "conversations";
     auto repoPathCarla = fileutils::get_data_dir() / carlaAccount->getAccountID() / "conversations";
     std::filesystem::copy(repoPathAlice, repoPathCarla, std::filesystem::copy_options::recursive);
-    auto ciPathAlice = fileutils::get_data_dir() / aliceAccount->getAccountID()
+    auto ciPathAlice = fileutils::get_data_dir() / aliceId
                        / "convInfo";
     auto ciPathCarla = fileutils::get_data_dir() / carlaAccount->getAccountID()
                        / "convInfo";
@@ -710,9 +810,7 @@ ConversationTest::testMergeTwoDifferentHeads()
 
     // Start Carla, should merge and all messages should be there
     Manager::instance().sendRegister(carlaId, true);
-    carlaGotMessage = false;
-    CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&] { return carlaGotMessage; }));
-    libjami::unregisterSignalHandlers();
+    CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&] { return !carlaData.messages.empty(); }));
 }
 
 void
@@ -797,7 +895,7 @@ ConversationTest::testSendMessageToMultipleParticipants()
     }));
 
     // Assert that repository exists
-    auto repoPath = fileutils::get_data_dir() / bobAccount->getAccountID()
+    auto repoPath = fileutils::get_data_dir() / bobId
                     / "conversations" / convId;
     CPPUNIT_ASSERT(std::filesystem::is_directory(repoPath));
     repoPath = fileutils::get_data_dir() / carlaAccount->getAccountID()
@@ -815,288 +913,124 @@ void
 ConversationTest::testPingPongMessages()
 {
     std::cout << "\nRunning test: " << __func__ << std::endl;
+    connectSignals();
 
-    auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto bobUri = bobAccount->getUsername();
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    auto messageBobReceived = 0, messageAliceReceived = 0;
-    bool requestReceived = false;
-    bool conversationReady = false;
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::MessageReceived>(
-        [&](const std::string& accountId,
-            const std::string& /* conversationId */,
-            std::map<std::string, std::string> /*message*/) {
-            if (accountId == bobId)
-                messageBobReceived += 1;
-            if (accountId == aliceId)
-                messageAliceReceived += 1;
-            cv.notify_one();
-        }));
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
-            [&](const std::string& /*accountId*/,
-                const std::string& /* conversationId */,
-                std::map<std::string, std::string> /*metadatas*/) {
-                requestReceived = true;
-                cv.notify_one();
-            }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& /* conversationId */) {
-            if (accountId == bobId) {
-                conversationReady = true;
-                cv.notify_one();
-            }
-        }));
-    libjami::registerSignalHandlers(confHandlers);
     auto convId = libjami::startConversation(aliceId);
     libjami::addConversationMember(aliceId, convId, bobUri);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; }));
-    messageAliceReceived = 0;
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
+    auto aliceMsgSize = aliceData.messages.size();
     libjami::acceptConversationRequest(bobId, convId);
     CPPUNIT_ASSERT(
-        cv.wait_for(lk, 60s, [&]() { return conversationReady && messageAliceReceived == 1; }));
+        cv.wait_for(lk, 60s, [&]() { return !bobData.conversationId.empty() && aliceMsgSize + 1 == aliceData.messages.size(); }));
     // Assert that repository exists
-    auto repoPath = fileutils::get_data_dir() / bobAccount->getAccountID()
+    auto repoPath = fileutils::get_data_dir() / bobId
                     / "conversations" / convId;
     CPPUNIT_ASSERT(std::filesystem::is_directory(repoPath));
-    messageBobReceived = 0;
-    messageAliceReceived = 0;
+    aliceMsgSize = aliceData.messages.size();
+    auto bobMsgSize = bobData.messages.size();
     libjami::sendMessage(aliceId, convId, "ping"s, "");
     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
-        return messageBobReceived == 1 && messageAliceReceived == 1;
+        return bobMsgSize + 1 == bobData.messages.size() && aliceMsgSize + 1 == aliceData.messages.size();
     }));
     libjami::sendMessage(bobId, convId, "pong"s, "");
     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
-        return messageBobReceived == 2 && messageAliceReceived == 2;
+        return bobMsgSize + 2 == bobData.messages.size() && aliceMsgSize + 2 == aliceData.messages.size();
     }));
     libjami::sendMessage(bobId, convId, "ping"s, "");
     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
-        return messageBobReceived == 3 && messageAliceReceived == 3;
+        return bobMsgSize + 3 == bobData.messages.size() && aliceMsgSize + 3 == aliceData.messages.size();
     }));
     libjami::sendMessage(aliceId, convId, "pong"s, "");
     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
-        return messageBobReceived == 4 && messageAliceReceived == 4;
+        return bobMsgSize + 4 == bobData.messages.size() && aliceMsgSize + 4 == aliceData.messages.size();
     }));
-    libjami::unregisterSignalHandlers();
 }
 
 void
 ConversationTest::testIsComposing()
 {
     std::cout << "\nRunning test: " << __func__ << std::endl;
+    connectSignals();
 
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
-    auto aliceUri = aliceAccount->getUsername();
     auto bobUri = bobAccount->getUsername();
     auto convId = libjami::startConversation(aliceId);
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    bool conversationReady = false, requestReceived = false, memberMessageGenerated = false,
-         aliceComposing = false;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
-            [&](const std::string& /*accountId*/,
-                const std::string& /* conversationId */,
-                std::map<std::string, std::string> /*metadatas*/) {
-                requestReceived = true;
-                cv.notify_one();
-            }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& conversationId) {
-            if (accountId == bobId && conversationId == convId) {
-                conversationReady = true;
-                cv.notify_one();
-            }
-        }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::MessageReceived>(
-        [&](const std::string& accountId,
-            const std::string& conversationId,
-            std::map<std::string, std::string> message) {
-            if (accountId == aliceId && conversationId == convId && message["type"] == "member") {
-                memberMessageGenerated = true;
-                cv.notify_one();
-            }
-        }));
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConfigurationSignal::ComposingStatusChanged>(
-            [&](const std::string& accountId,
-                const std::string& conversationId,
-                const std::string& peer,
-                bool state) {
-                if (accountId == bobId && conversationId == convId && peer == aliceUri) {
-                    aliceComposing = state;
-                    cv.notify_one();
-                }
-            }));
-    libjami::registerSignalHandlers(confHandlers);
+    auto aliceMsgSize = aliceData.messages.size();
     libjami::addConversationMember(aliceId, convId, bobUri);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return memberMessageGenerated; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 1 == aliceData.messages.size(); }));
     // Assert that repository exists
-    auto repoPath = fileutils::get_data_dir() / aliceAccount->getAccountID()
+    auto repoPath = fileutils::get_data_dir() / aliceId
                     / "conversations" / convId;
     CPPUNIT_ASSERT(std::filesystem::is_directory(repoPath));
     // Check created files
     auto bobInvited = repoPath / "invited" / bobUri;
     CPPUNIT_ASSERT(std::filesystem::is_regular_file(bobInvited));
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; }));
-    memberMessageGenerated = false;
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
     libjami::acceptConversationRequest(bobId, convId);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return memberMessageGenerated; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 2 == aliceData.messages.size(); }));
 
     aliceAccount->setIsComposing("swarm:" + convId, true);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceComposing; }));
-
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.composing; }));
     aliceAccount->setIsComposing("swarm:" + convId, false);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !aliceComposing; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !bobData.composing; }));
 }
 
 void
 ConversationTest::testMessageStatus()
 {
     std::cout << "\nRunning test: " << __func__ << std::endl;
+    connectSignals();
 
-    auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
-    auto aliceUri = aliceAccount->getUsername();
     auto bobUri = bobAccount->getUsername();
     auto convId = libjami::startConversation(aliceId);
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    bool conversationReady = false, requestReceived = false, memberMessageGenerated = false;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
-            [&](const std::string& /*accountId*/,
-                const std::string& /* conversationId */,
-                std::map<std::string, std::string> /*metadatas*/) {
-                requestReceived = true;
-                cv.notify_one();
-            }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& conversationId) {
-            if (accountId == bobId && conversationId == convId) {
-                conversationReady = true;
-                cv.notify_one();
-            }
-        }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::MessageReceived>(
-        [&](const std::string& accountId,
-            const std::string& conversationId,
-            std::map<std::string, std::string> message) {
-            if (accountId == aliceId && conversationId == convId) {
-                if (message["type"] == "member")
-                    memberMessageGenerated = true;
-                cv.notify_one();
-            }
-        }));
-    bool sending = false, sent = false;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConfigurationSignal::AccountMessageStatusChanged>(
-            [&](const std::string& accountId,
-                const std::string& conversationId,
-                const std::string& peer,
-                const std::string& msgId,
-                int status) {
-                if (accountId == aliceId && convId == conversationId) {
-                    if (status == 2)
-                        sending = true;
-                    if (status == 3)
-                        sent = true;
-                    cv.notify_one();
-                }
-            }));
-    libjami::registerSignalHandlers(confHandlers);
+    auto aliceMsgSize = aliceData.messages.size();
     libjami::addConversationMember(aliceId, convId, bobUri);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return memberMessageGenerated; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 1 == aliceData.messages.size(); }));
     // Assert that repository exists
-    auto repoPath = fileutils::get_data_dir() / aliceAccount->getAccountID()
+    auto repoPath = fileutils::get_data_dir() / aliceId
                     / "conversations" / convId;
     CPPUNIT_ASSERT(std::filesystem::is_directory(repoPath));
     // Check created files
     auto bobInvited = repoPath / "invited" / bobUri;
     CPPUNIT_ASSERT(std::filesystem::is_regular_file(bobInvited));
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; }));
-    memberMessageGenerated = false;
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
     libjami::acceptConversationRequest(bobId, convId);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return memberMessageGenerated; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 2 == aliceData.messages.size(); }));
 
-    sending = false;
-    sent = false;
+    aliceData.sending = false;
+    aliceData.sent = false;
     libjami::sendMessage(aliceId, convId, "hi"s, "");
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return sending && sent; }));
-
-    libjami::unregisterSignalHandlers();
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceData.sending && aliceData.sent; }));
 }
 
 void
 ConversationTest::testSetMessageDisplayed()
 {
     std::cout << "\nRunning test: " << __func__ << std::endl;
+    connectSignals();
 
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto aliceUri = aliceAccount->getUsername();
     auto bobUri = bobAccount->getUsername();
     auto convId = libjami::startConversation(aliceId);
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    bool conversationReady = false, requestReceived = false, memberMessageGenerated = false,
-         msgDisplayed = false;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
-            [&](const std::string& /*accountId*/,
-                const std::string& /* conversationId */,
-                std::map<std::string, std::string> /*metadatas*/) {
-                requestReceived = true;
-                cv.notify_one();
-            }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& conversationId) {
-            if (accountId == bobId && conversationId == convId) {
-                conversationReady = true;
-                cv.notify_one();
-            }
-        }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::MessageReceived>(
-        [&](const std::string& accountId,
-            const std::string& conversationId,
-            std::map<std::string, std::string> message) {
-            if (accountId == aliceId && conversationId == convId) {
-                if (message["type"] == "member")
-                    memberMessageGenerated = true;
-                cv.notify_one();
-            }
-        }));
-    std::string aliceLastMsg;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConfigurationSignal::AccountMessageStatusChanged>(
-            [&](const std::string& accountId,
-                const std::string& conversationId,
-                const std::string& peer,
-                const std::string& msgId,
-                int status) {
-                if (conversationId == convId && peer == aliceUri && status == 3) {
-                    if (accountId == bobId && msgId == conversationId)
-                        msgDisplayed = true;
-                    else if (accountId == aliceId)
-                        aliceLastMsg = msgId;
-                    cv.notify_one();
-                }
-            }));
-    libjami::registerSignalHandlers(confHandlers);
-    aliceLastMsg = "";
+    auto aliceMsgSize = aliceData.messages.size();
     libjami::addConversationMember(aliceId, convId, bobUri);
-    CPPUNIT_ASSERT(
-        cv.wait_for(lk, 30s, [&]() { return memberMessageGenerated && !aliceLastMsg.empty(); }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 1 == aliceData.messages.size(); }));
     // Assert that repository exists
-    auto repoPath = fileutils::get_data_dir() / aliceAccount->getAccountID()
+    auto repoPath = fileutils::get_data_dir() / aliceId
                     / "conversations" / convId;
     CPPUNIT_ASSERT(std::filesystem::is_directory(repoPath));
     // Check created files
     auto bobInvited = repoPath / "invited" / bobUri;
     CPPUNIT_ASSERT(std::filesystem::is_regular_file(bobInvited));
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; }));
-    memberMessageGenerated = false;
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
     libjami::acceptConversationRequest(bobId, convId);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return memberMessageGenerated; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 2 == aliceData.messages.size(); }));
 
     // Last displayed messages should not be set yet
     auto membersInfos = libjami::getConversationMembers(bobId, convId);
@@ -1112,12 +1046,12 @@ ConversationTest::testSetMessageDisplayed()
                                 [&](auto infos) {
                                     // Last read for alice is when bob is added to the members
                                     return infos["uri"] == aliceUri
-                                           && infos["lastDisplayed"] == aliceLastMsg;
+                                           && infos["lastDisplayed"] == aliceData.messages[0].id;
                                 })
                    != membersInfos.end());
-
+    bobData.sent = false;
     aliceAccount->setMessageDisplayed("swarm:" + convId, convId, 3);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return msgDisplayed; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.sent; }));
 
     // Now, the last displayed message should be updated in member's infos (both sides)
     membersInfos = libjami::getConversationMembers(bobId, convId);
@@ -1136,170 +1070,65 @@ ConversationTest::testSetMessageDisplayed()
                                            && infos["lastDisplayed"] == convId;
                                 })
                    != membersInfos.end());
-
-    libjami::unregisterSignalHandlers();
 }
 
 void
 ConversationTest::testSetMessageDisplayedTwice()
 {
     std::cout << "\nRunning test: " << __func__ << std::endl;
+    connectSignals();
 
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
-    auto aliceUri = aliceAccount->getUsername();
     auto bobUri = bobAccount->getUsername();
     auto convId = libjami::startConversation(aliceId);
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    bool conversationReady = false, requestReceived = false, memberMessageGenerated = false,
-         msgDisplayed = false;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
-            [&](const std::string& /*accountId*/,
-                const std::string& /* conversationId */,
-                std::map<std::string, std::string> /*metadatas*/) {
-                requestReceived = true;
-                cv.notify_one();
-            }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& conversationId) {
-            if (accountId == bobId && conversationId == convId) {
-                conversationReady = true;
-                cv.notify_one();
-            }
-        }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::MessageReceived>(
-        [&](const std::string& accountId,
-            const std::string& conversationId,
-            std::map<std::string, std::string> message) {
-            if (accountId == aliceId && conversationId == convId) {
-                if (message["type"] == "member")
-                    memberMessageGenerated = true;
-                cv.notify_one();
-            }
-        }));
-    std::string aliceLastMsg;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConfigurationSignal::AccountMessageStatusChanged>(
-            [&](const std::string& accountId,
-                const std::string& conversationId,
-                const std::string& peer,
-                const std::string& msgId,
-                int status) {
-                if (conversationId == convId && peer == aliceUri && status == 3) {
-                    if (accountId == bobId && msgId == conversationId)
-                        msgDisplayed = true;
-                    else if (accountId == aliceId)
-                        aliceLastMsg = msgId;
-                    cv.notify_one();
-                }
-            }));
-    libjami::registerSignalHandlers(confHandlers);
-    aliceLastMsg = "";
+    auto aliceMsgSize = aliceData.messages.size();
     libjami::addConversationMember(aliceId, convId, bobUri);
-    CPPUNIT_ASSERT(
-        cv.wait_for(lk, 30s, [&]() { return memberMessageGenerated && !aliceLastMsg.empty(); }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 1 == aliceData.messages.size(); }));
     // Assert that repository exists
-    auto repoPath = fileutils::get_data_dir() / aliceAccount->getAccountID()
+    auto repoPath = fileutils::get_data_dir() / aliceId
                     / "conversations" / convId;
     CPPUNIT_ASSERT(std::filesystem::is_directory(repoPath));
     // Check created files
     auto bobInvited = repoPath / "invited" / bobUri;
     CPPUNIT_ASSERT(std::filesystem::is_regular_file(bobInvited));
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; }));
-    memberMessageGenerated = false;
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
     libjami::acceptConversationRequest(bobId, convId);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return memberMessageGenerated; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 2 == aliceData.messages.size(); }));
 
+    bobData.sent = false;
     aliceAccount->setMessageDisplayed("swarm:" + convId, convId, 3);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return msgDisplayed; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.sent; }));
 
-    msgDisplayed = false;
+    bobData.sent = false;
     aliceAccount->setMessageDisplayed("swarm:" + convId, convId, 3);
-    CPPUNIT_ASSERT(!cv.wait_for(lk, 10s, [&]() { return msgDisplayed; }));
-
-    libjami::unregisterSignalHandlers();
+    CPPUNIT_ASSERT(!cv.wait_for(lk, 10s, [&]() { return bobData.sent; }));
 }
 
 void
 ConversationTest::testSetMessageDisplayedPreference()
 {
     std::cout << "\nRunning test: " << __func__ << std::endl;
+    connectSignals();
 
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto aliceUri = aliceAccount->getUsername();
     auto bobUri = bobAccount->getUsername();
     auto convId = libjami::startConversation(aliceId);
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    bool conversationReady = false, requestReceived = false, memberMessageGenerated = false,
-         msgDisplayed = false, aliceRegistered = false;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
-            [&](const std::string& /*accountId*/,
-                const std::string& /* conversationId */,
-                std::map<std::string, std::string> /*metadatas*/) {
-                requestReceived = true;
-                cv.notify_one();
-            }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& conversationId) {
-            if (accountId == bobId && conversationId == convId) {
-                conversationReady = true;
-                cv.notify_one();
-            }
-        }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::MessageReceived>(
-        [&](const std::string& accountId,
-            const std::string& conversationId,
-            std::map<std::string, std::string> message) {
-            if (accountId == aliceId && conversationId == convId && message["type"] == "member") {
-                memberMessageGenerated = true;
-                cv.notify_one();
-            }
-        }));
-    std::string aliceLastMsg;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConfigurationSignal::AccountMessageStatusChanged>(
-            [&](const std::string& accountId,
-                const std::string& conversationId,
-                const std::string& peer,
-                const std::string& msgId,
-                int status) {
-                if (conversationId == convId && peer == aliceUri && status == 3) {
-                    if (accountId == bobId && msgId == conversationId)
-                        msgDisplayed = true;
-                    else if (accountId == aliceId)
-                        aliceLastMsg = msgId;
-                    cv.notify_one();
-                }
-            }));
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConfigurationSignal::VolatileDetailsChanged>(
-            [&](const std::string&, const std::map<std::string, std::string>&) {
-                auto details = aliceAccount->getVolatileAccountDetails();
-                auto daemonStatus = details[libjami::Account::ConfProperties::Registration::STATUS];
-                if (daemonStatus == "REGISTERED") {
-                    aliceRegistered = true;
-                    cv.notify_one();
-                }
-            }));
-    libjami::registerSignalHandlers(confHandlers);
 
     auto details = aliceAccount->getAccountDetails();
     CPPUNIT_ASSERT(details[ConfProperties::SENDREADRECEIPT] == "true");
     details[ConfProperties::SENDREADRECEIPT] = "false";
     libjami::setAccountDetails(aliceId, details);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceRegistered; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceData.registered; }));
 
-    aliceLastMsg = "";
+    auto aliceMsgSize = aliceData.messages.size();
     libjami::addConversationMember(aliceId, convId, bobUri);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
-        return requestReceived && memberMessageGenerated && !aliceLastMsg.empty();
-    }));
-    memberMessageGenerated = false;
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived && aliceMsgSize + 1 == aliceData.messages.size(); }));
+
     libjami::acceptConversationRequest(bobId, convId);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return memberMessageGenerated; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 2 == aliceData.messages.size(); }));
 
     // Last displayed messages should not be set yet
     auto membersInfos = libjami::getConversationMembers(aliceId, convId);
@@ -1308,13 +1137,13 @@ ConversationTest::testSetMessageDisplayedPreference()
                                 [&](auto infos) {
                                     // Last read for alice is when bob is added to the members
                                     return infos["uri"] == aliceUri
-                                           && infos["lastDisplayed"] == aliceLastMsg;
+                                           && infos["lastDisplayed"] == aliceData.messages[0].id;
                                 })
                    != membersInfos.end());
 
     aliceAccount->setMessageDisplayed("swarm:" + convId, convId, 3);
     // Bob should not receive anything here, as sendMessageDisplayed is disabled for Alice
-    CPPUNIT_ASSERT(!cv.wait_for(lk, 10s, [&]() { return msgDisplayed; }));
+    CPPUNIT_ASSERT(!cv.wait_for(lk, 10s, [&]() { return bobData.sent; }));
 
     // Assert that message is set as displayed for self (for the read status)
     membersInfos = libjami::getConversationMembers(aliceId, convId);
@@ -1325,85 +1154,25 @@ ConversationTest::testSetMessageDisplayedPreference()
                                            && infos["lastDisplayed"] == convId;
                                 })
                    != membersInfos.end());
-    libjami::unregisterSignalHandlers();
 }
 
 void
 ConversationTest::testSetMessageDisplayedAfterClone()
 {
     std::cout << "\nRunning test: " << __func__ << std::endl;
+    connectSignals();
 
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto aliceUri = aliceAccount->getUsername();
     auto bobUri = bobAccount->getUsername();
     auto convId = libjami::startConversation(aliceId);
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    bool requestReceived = false, memberMessageGenerated = false, msgDisplayed = false,
-         aliceRegistered = false;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
-            [&](const std::string& /*accountId*/,
-                const std::string& /* conversationId */,
-                std::map<std::string, std::string> /*metadatas*/) {
-                requestReceived = true;
-                cv.notify_one();
-            }));
-    bool conversationReady = false, conversationAlice2Ready = false;
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& conversationId) {
-            if (accountId == bobId && conversationId == convId) {
-                conversationReady = true;
-            } else if (accountId == alice2Id) {
-                conversationAlice2Ready = true;
-            }
-            cv.notify_one();
-        }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::MessageReceived>(
-        [&](const std::string& accountId,
-            const std::string& conversationId,
-            std::map<std::string, std::string> message) {
-            if (accountId == aliceId && conversationId == convId && message["type"] == "member") {
-                memberMessageGenerated = true;
-                cv.notify_one();
-            }
-        }));
-    std::string aliceLastMsg;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConfigurationSignal::AccountMessageStatusChanged>(
-            [&](const std::string& accountId,
-                const std::string& conversationId,
-                const std::string& peer,
-                const std::string& msgId,
-                int status) {
-                if (conversationId == convId && peer == aliceUri && status == 3) {
-                    if (accountId == bobId && msgId == conversationId)
-                        msgDisplayed = true;
-                    else if (accountId == aliceId)
-                        aliceLastMsg = msgId;
-                    cv.notify_one();
-                }
-            }));
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConfigurationSignal::VolatileDetailsChanged>(
-            [&](const std::string&, const std::map<std::string, std::string>&) {
-                auto details = aliceAccount->getVolatileAccountDetails();
-                auto daemonStatus = details[libjami::Account::ConfProperties::Registration::STATUS];
-                if (daemonStatus == "REGISTERED") {
-                    aliceRegistered = true;
-                    cv.notify_one();
-                }
-            }));
-    libjami::registerSignalHandlers(confHandlers);
 
-    aliceLastMsg = "";
+    auto aliceMsgSize = aliceData.messages.size();
     libjami::addConversationMember(aliceId, convId, bobUri);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
-        return requestReceived && memberMessageGenerated && !aliceLastMsg.empty();
-    }));
-    memberMessageGenerated = false;
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived && aliceMsgSize + 1 == aliceData.messages.size(); }));
     libjami::acceptConversationRequest(bobId, convId);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return memberMessageGenerated; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 2 == aliceData.messages.size(); }));
 
     aliceAccount->setMessageDisplayed("swarm:" + convId, convId, 3);
 
@@ -1422,7 +1191,7 @@ ConversationTest::testSetMessageDisplayedAfterClone()
     alice2Id = Manager::instance().addAccount(details);
 
     // Disconnect alice2, to create a valid conv betwen Alice and alice1
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return conversationAlice2Ready; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !alice2Data.conversationId.empty(); }));
 
     // Assert that message is set as displayed for self (for the read status)
     auto membersInfos = libjami::getConversationMembers(aliceId, convId);
@@ -1433,8 +1202,6 @@ ConversationTest::testSetMessageDisplayedAfterClone()
                                            && infos["lastDisplayed"] == convId;
                                 })
                    != membersInfos.end());
-
-    libjami::unregisterSignalHandlers();
 }
 
 void
@@ -1510,8 +1277,6 @@ std::string
 ConversationTest::createFakeConversation(std::shared_ptr<JamiAccount> account,
                                          const std::string& fakeCert)
 {
-    std::cout << "\nRunning test: " << __func__ << std::endl;
-
     auto repoPath = fileutils::get_data_dir() / account->getAccountID()
                     / "conversations" / "tmp";
 
@@ -1678,6 +1443,7 @@ void
 ConversationTest::testVoteNonEmpty()
 {
     std::cout << "\nRunning test: " << __func__ << std::endl;
+    connectSignals();
 
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
@@ -1688,158 +1454,53 @@ ConversationTest::testVoteNonEmpty()
     auto carlaUri = carlaAccount->getUsername();
     aliceAccount->trackBuddyPresence(carlaUri, true);
 
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    bool conversationReady = false, requestReceived = false, memberMessageGenerated = false,
-         voteMessageGenerated = false, messageBobReceived = false, errorDetected = false,
-         carlaConnected = false;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
-            [&](const std::string& /*accountId*/,
-                const std::string& /* conversationId */,
-                std::map<std::string, std::string> /*metadatas*/) {
-                requestReceived = true;
-                cv.notify_one();
-            }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& conversationId) {
-            if (accountId == bobId && conversationId == convId) {
-                conversationReady = true;
-                cv.notify_one();
-            }
-        }));
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConfigurationSignal::VolatileDetailsChanged>(
-            [&](const std::string&, const std::map<std::string, std::string>&) {
-                auto details = carlaAccount->getVolatileAccountDetails();
-                auto deviceAnnounced
-                    = details[libjami::Account::VolatileProperties::DEVICE_ANNOUNCED];
-                if (deviceAnnounced == "true") {
-                    carlaConnected = true;
-                    cv.notify_one();
-                }
-            }));
-    confHandlers.insert(libjami::exportable_callback<libjami::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(
-        libjami::exportable_callback<libjami::ConversationSignal::OnConversationError>(
-            [&](const std::string& /* accountId */,
-                const std::string& /* conversationId */,
-                int code,
-                const std::string& /* what */) {
-                if (code == 3)
-                    errorDetected = true;
-                cv.notify_one();
-            }));
-    libjami::registerSignalHandlers(confHandlers);
     Manager::instance().sendRegister(carlaId, true);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&] { return carlaConnected; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&] { return carlaData.deviceAnnounced; }));
+
+    auto aliceMsgSize = aliceData.messages.size();
     libjami::addConversationMember(aliceId, convId, bobUri);
-    CPPUNIT_ASSERT(
-        cv.wait_for(lk, 30s, [&]() { return requestReceived && memberMessageGenerated; }));
-    memberMessageGenerated = false;
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived && aliceMsgSize + 1 == aliceData.messages.size(); }));
     libjami::acceptConversationRequest(bobId, convId);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return memberMessageGenerated; }));
-    requestReceived = false;
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 2 == aliceData.messages.size(); }));
+
+    aliceMsgSize = aliceData.messages.size();
+    auto bobMsgSize = bobData.messages.size();
     libjami::addConversationMember(aliceId, convId, carlaUri);
-    CPPUNIT_ASSERT(
-        cv.wait_for(lk, 30s, [&]() { return requestReceived && memberMessageGenerated; }));
-    memberMessageGenerated = false;
-    messageBobReceived = false;
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return carlaData.requestReceived && aliceMsgSize + 1 == aliceData.messages.size() && bobMsgSize + 1 == bobData.messages.size(); }));
     libjami::acceptConversationRequest(carlaId, convId);
-    CPPUNIT_ASSERT(
-        cv.wait_for(lk, 30s, [&]() { return memberMessageGenerated && messageBobReceived; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 2 == aliceData.messages.size() && bobMsgSize + 2 == bobData.messages.size(); }));
 
     // 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, 30s, [&]() { return errorDetected; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return carlaData.errorDetected; }));
 }
 
 void
 ConversationTest::testNoBadFileInInitialCommit()
 {
     std::cout << "\nRunning test: " << __func__ << std::endl;
+    connectSignals();
 
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
-    auto carlaUri = carlaAccount->getUsername();
     auto aliceUri = aliceAccount->getUsername();
 
     auto convId = createFakeConversation(carlaAccount);
-
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    auto messageBobReceived = 0, messageAliceReceived = 0;
-    bool requestReceived = false;
-    bool carlaConnected = false;
-    bool errorDetected = false;
-    confHandlers.insert(libjami::exportable_callback<libjami::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(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
-            [&](const std::string& /*accountId*/,
-                const std::string& /* conversationId */,
-                std::map<std::string, std::string> /*metadatas*/) {
-                requestReceived = true;
-                cv.notify_one();
-            }));
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConfigurationSignal::VolatileDetailsChanged>(
-            [&](const std::string&, const std::map<std::string, std::string>&) {
-                auto details = carlaAccount->getVolatileAccountDetails();
-                auto deviceAnnounced
-                    = details[libjami::Account::VolatileProperties::DEVICE_ANNOUNCED];
-                if (deviceAnnounced == "true") {
-                    carlaConnected = true;
-                    cv.notify_one();
-                }
-            }));
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::OnConversationError>(
-            [&](const std::string& /* accountId */,
-                const std::string& /* conversationId */,
-                int code,
-                const std::string& /* what */) {
-                if (code == 3)
-                    errorDetected = true;
-                cv.notify_one();
-            }));
-    libjami::registerSignalHandlers(confHandlers);
-
     Manager::instance().sendRegister(carlaId, true);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return carlaConnected; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return carlaData.deviceAnnounced; }));
     libjami::addConversationMember(carlaId, convId, aliceUri);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceData.requestReceived; }));
 
     libjami::acceptConversationRequest(aliceId, convId);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return errorDetected; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceData.errorDetected; }));
 }
 
 void
 ConversationTest::testNoBadCertInInitialCommit()
 {
     std::cout << "\nRunning test: " << __func__ << std::endl;
+    connectSignals();
 
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
@@ -1854,141 +1515,47 @@ ConversationTest::testNoBadCertInInitialCommit()
     // Create a conversation from Carla with Alice's device
     auto convId = createFakeConversation(carlaAccount, fakeCert->toString(false));
 
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    auto messageBobReceived = 0, messageAliceReceived = 0;
-    bool requestReceived = false;
-    bool carlaConnected = false;
-    bool errorDetected = false;
-    confHandlers.insert(libjami::exportable_callback<libjami::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(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
-            [&](const std::string& /*accountId*/,
-                const std::string& /* conversationId */,
-                std::map<std::string, std::string> /*metadatas*/) {
-                requestReceived = true;
-                cv.notify_one();
-            }));
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConfigurationSignal::VolatileDetailsChanged>(
-            [&](const std::string&, const std::map<std::string, std::string>&) {
-                auto details = carlaAccount->getVolatileAccountDetails();
-                auto deviceAnnounced
-                    = details[libjami::Account::VolatileProperties::DEVICE_ANNOUNCED];
-                if (deviceAnnounced == "true") {
-                    carlaConnected = true;
-                    cv.notify_one();
-                }
-            }));
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::OnConversationError>(
-            [&](const std::string& /* accountId */,
-                const std::string& /* conversationId */,
-                int code,
-                const std::string& /* what */) {
-                if (code == 3)
-                    errorDetected = true;
-                cv.notify_one();
-            }));
-    libjami::registerSignalHandlers(confHandlers);
-
     Manager::instance().sendRegister(carlaId, true);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return carlaConnected; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return carlaData.deviceAnnounced; }));
     libjami::addConversationMember(carlaId, convId, aliceUri);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceData.requestReceived; }));
 
     libjami::acceptConversationRequest(aliceId, convId);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return errorDetected; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceData.errorDetected; }));
 }
 
 void
 ConversationTest::testPlainTextNoBadFile()
 {
     std::cout << "\nRunning test: " << __func__ << std::endl;
+    connectSignals();
 
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto bobUri = bobAccount->getUsername();
 
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    auto messageBobReceived = 0;
-    bool memberMessageGenerated = false;
-    bool requestReceived = false;
-    bool conversationReady = false;
-    bool errorDetected = false;
     std::string convId = libjami::startConversation(aliceId);
-    confHandlers.insert(libjami::exportable_callback<libjami::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(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
-            [&](const std::string& /*accountId*/,
-                const std::string& /* conversationId */,
-                std::map<std::string, std::string> /*metadatas*/) {
-                requestReceived = true;
-                cv.notify_one();
-            }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& /* conversationId */) {
-            if (accountId == bobId) {
-                conversationReady = true;
-                cv.notify_one();
-            }
-        }));
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::OnConversationError>(
-            [&](const std::string& /* accountId */,
-                const std::string& /* conversationId */,
-                int code,
-                const std::string& /* what */) {
-                if (code == 3)
-                    errorDetected = true;
-                cv.notify_one();
-            }));
-    libjami::registerSignalHandlers(confHandlers);
-
+    auto aliceMsgSize = aliceData.messages.size();
     libjami::addConversationMember(aliceId, convId, bobUri);
-    CPPUNIT_ASSERT(
-        cv.wait_for(lk, 30s, [&]() { return requestReceived && memberMessageGenerated; }));
-    memberMessageGenerated = false;
-
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived && aliceMsgSize + 1 == aliceData.messages.size(); }));
     libjami::acceptConversationRequest(bobId, convId);
-    cv.wait_for(lk, 30s, [&] { return conversationReady && memberMessageGenerated; });
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 2 == aliceData.messages.size(); }));
 
     addFile(aliceAccount, convId, "BADFILE");
     Json::Value root;
     root["type"] = "text/plain";
     root["body"] = "hi";
     commit(aliceAccount, convId, root);
-    errorDetected = false;
     libjami::sendMessage(aliceId, convId, "hi"s, "");
     // Check not received due to the unwanted file
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return errorDetected; }));
-    libjami::unregisterSignalHandlers();
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.errorDetected; }));
 }
 
 void
 ConversationTest::testVoteNoBadFile()
 {
     std::cout << "\nRunning test: " << __func__ << std::endl;
+    connectSignals();
 
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
@@ -1999,146 +1566,47 @@ ConversationTest::testVoteNoBadFile()
     auto carlaUri = carlaAccount->getUsername();
     aliceAccount->trackBuddyPresence(carlaUri, true);
 
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    bool conversationReady = false, requestReceived = false, memberMessageGenerated = false,
-         voteMessageGenerated = false, messageBobReceived = false, messageCarlaReceived = false,
-         carlaConnected = false;
-    ;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
-            [&](const std::string& /*accountId*/,
-                const std::string& /* conversationId */,
-                std::map<std::string, std::string> /*metadatas*/) {
-                requestReceived = true;
-                cv.notify_one();
-            }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& conversationId) {
-            if (accountId == bobId && conversationId == convId) {
-                conversationReady = true;
-                cv.notify_one();
-            }
-        }));
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConfigurationSignal::VolatileDetailsChanged>(
-            [&](const std::string&, const std::map<std::string, std::string>&) {
-                auto details = carlaAccount->getVolatileAccountDetails();
-                auto deviceAnnounced
-                    = details[libjami::Account::VolatileProperties::DEVICE_ANNOUNCED];
-                if (deviceAnnounced == "true") {
-                    carlaConnected = true;
-                    cv.notify_one();
-                }
-            }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::MessageReceived>(
-        [&](const std::string& accountId,
-            const std::string& conversationId,
-            std::map<std::string, std::string> message) {
-            std::string body = "";
-            auto it = message.find("body");
-            if (it != message.end()) {
-                body = it->second;
-            }
-            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;
-            } else if (accountId == carlaId && conversationId == convId && body == "final") {
-                messageCarlaReceived = true;
-            }
-            cv.notify_one();
-        }));
-    libjami::registerSignalHandlers(confHandlers);
     Manager::instance().sendRegister(carlaId, true);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return carlaConnected; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return carlaData.deviceAnnounced; }));
 
+    auto aliceMsgSize = aliceData.messages.size();
     libjami::addConversationMember(aliceId, convId, bobUri);
-    CPPUNIT_ASSERT(
-        cv.wait_for(lk, 30s, [&]() { return requestReceived && memberMessageGenerated; }));
-    memberMessageGenerated = false;
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived && aliceMsgSize + 1 == aliceData.messages.size(); }));
     libjami::acceptConversationRequest(bobId, convId);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return memberMessageGenerated; }));
-    requestReceived = false;
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 2 == aliceData.messages.size(); }));
+
+    aliceMsgSize = aliceData.messages.size();
+    auto bobMsgSize = bobData.messages.size();
     libjami::addConversationMember(aliceId, convId, carlaUri);
-    CPPUNIT_ASSERT(
-        cv.wait_for(lk, 30s, [&]() { return requestReceived && memberMessageGenerated; }));
-    memberMessageGenerated = false;
-    messageBobReceived = false;
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return carlaData.requestReceived && aliceMsgSize + 1 == aliceData.messages.size() && bobMsgSize + 1 == bobData.messages.size(); }));
     libjami::acceptConversationRequest(carlaId, convId);
-    CPPUNIT_ASSERT(
-        cv.wait_for(lk, 30s, [&]() { return memberMessageGenerated && messageBobReceived; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 2 == aliceData.messages.size() && bobMsgSize + 2 == bobData.messages.size(); }));
 
     // Now Alice remove Carla without a vote. Bob will not receive the message
-    messageBobReceived = false;
     addFile(aliceAccount, convId, "BADFILE");
+    aliceMsgSize = aliceData.messages.size();
     libjami::removeConversationMember(aliceId, convId, bobUri);
-    CPPUNIT_ASSERT(
-        cv.wait_for(lk, 30s, [&]() { return memberMessageGenerated && voteMessageGenerated; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 2 == aliceData.messages.size(); }));
 
-    messageCarlaReceived = false;
+    auto carlaMsgSize = carlaData.messages.size();
     libjami::sendMessage(bobId, convId, "final"s, "");
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return messageCarlaReceived; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return carlaMsgSize + 1 == carlaData.messages.size(); }));
 }
 
 void
 ConversationTest::testETooBigClone()
 {
     std::cout << "\nRunning test: " << __func__ << std::endl;
+    connectSignals();
 
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto bobUri = bobAccount->getUsername();
 
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    auto messageBobReceived = 0, messageAliceReceived = 0;
-    bool requestReceived = false;
-    bool conversationReady = false;
-    bool errorDetected = false;
-    confHandlers.insert(libjami::exportable_callback<libjami::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(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
-            [&](const std::string& /*accountId*/,
-                const std::string& /* conversationId */,
-                std::map<std::string, std::string> /*metadatas*/) {
-                requestReceived = true;
-                cv.notify_one();
-            }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& /* conversationId */) {
-            if (accountId == bobId) {
-                conversationReady = true;
-                cv.notify_one();
-            }
-        }));
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::OnConversationError>(
-            [&](const std::string& /* accountId */,
-                const std::string& /* conversationId */,
-                int code,
-                const std::string& /* what */) {
-                if (code == 3)
-                    errorDetected = true;
-                cv.notify_one();
-            }));
-    libjami::registerSignalHandlers(confHandlers);
-
     auto convId = libjami::startConversation(aliceId);
 
     // Assert that repository exists
-    auto repoPath = fileutils::get_data_dir() / aliceAccount->getAccountID()
+    auto repoPath = fileutils::get_data_dir() / aliceId
                     / "conversations" / convId;
     std::ofstream bad(repoPath / "BADFILE");
     CPPUNIT_ASSERT(bad.is_open());
@@ -2147,84 +1615,38 @@ ConversationTest::testETooBigClone()
     bad.close();
 
     addAll(aliceAccount, convId);
-
     libjami::addConversationMember(aliceId, convId, bobUri);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; }));
-
-    errorDetected = false;
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
     libjami::acceptConversationRequest(bobId, convId);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return errorDetected; }));
-    libjami::unregisterSignalHandlers();
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.errorDetected; }));
 }
 
 void
 ConversationTest::testETooBigFetch()
 {
     std::cout << "\nRunning test: " << __func__ << std::endl;
+    connectSignals();
 
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto bobUri = bobAccount->getUsername();
 
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    auto messageBobReceived = 0, messageAliceReceived = 0;
-    bool requestReceived = false;
-    bool conversationReady = false;
-    bool errorDetected = false;
-    confHandlers.insert(libjami::exportable_callback<libjami::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(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
-            [&](const std::string& /*accountId*/,
-                const std::string& /* conversationId */,
-                std::map<std::string, std::string> /*metadatas*/) {
-                requestReceived = true;
-                cv.notify_one();
-            }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& /* conversationId */) {
-            if (accountId == bobId) {
-                conversationReady = true;
-                cv.notify_one();
-            }
-        }));
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::OnConversationError>(
-            [&](const std::string& /* accountId */,
-                const std::string& /* conversationId */,
-                int code,
-                const std::string& /* what */) {
-                if (code == 3)
-                    errorDetected = true;
-                cv.notify_one();
-            }));
-    libjami::registerSignalHandlers(confHandlers);
-
     auto convId = libjami::startConversation(aliceId);
 
+    auto aliceMsgSize = aliceData.messages.size();
     libjami::addConversationMember(aliceId, convId, bobUri);
-    cv.wait_for(lk, 30s, [&]() { return requestReceived; });
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
 
     libjami::acceptConversationRequest(bobId, convId);
-    cv.wait_for(lk, 30s, [&]() { return conversationReady; });
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !bobData.conversationId.empty(); }));
 
     // Wait that alice sees Bob
-    cv.wait_for(lk, 30s, [&]() { return messageAliceReceived == 2; });
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 2 == aliceData.messages.size(); }));
 
-    auto repoPath = fileutils::get_data_dir() / aliceAccount->getAccountID()
+    auto repoPath = fileutils::get_data_dir() / aliceId
                     / "conversations" / convId;
     std::ofstream bad(repoPath / "BADFILE");
     CPPUNIT_ASSERT(bad.is_open());
-    errorDetected = false;
     for (int i = 0; i < 300 * 1024 * 1024; ++i)
         bad << "A";
     bad.close();
@@ -2236,14 +1658,14 @@ ConversationTest::testETooBigFetch()
     commit(aliceAccount, convId, json);
 
     libjami::sendMessage(aliceId, convId, "hi"s, "");
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return errorDetected; }));
-    libjami::unregisterSignalHandlers();
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.errorDetected; }));
 }
 
 void
 ConversationTest::testUnknownModeDetected()
 {
     std::cout << "\nRunning test: " << __func__ << std::endl;
+    connectSignals();
 
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
@@ -2257,405 +1679,144 @@ ConversationTest::testUnknownModeDetected()
     wbuilder["commentStyle"] = "None";
     wbuilder["indentation"] = "";
     repo.amend(convId, Json::writeString(wbuilder, json));
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    bool conversationReady = false, requestReceived = false, memberMessageGenerated = false,
-         errorDetected = false;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
-            [&](const std::string& /*accountId*/,
-                const std::string& /* conversationId */,
-                std::map<std::string, std::string> /*metadatas*/) {
-                requestReceived = true;
-                cv.notify_one();
-            }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& /* conversationId */) {
-            if (accountId == bobId) {
-                conversationReady = true;
-                cv.notify_one();
-            }
-        }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::MessageReceived>(
-        [&](const std::string& accountId,
-            const std::string& conversationId,
-            std::map<std::string, std::string> message) {
-            if (accountId == aliceId && conversationId == convId && message["type"] == "member") {
-                memberMessageGenerated = true;
-                cv.notify_one();
-            }
-        }));
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::OnConversationError>(
-            [&](const std::string& /* accountId */,
-                const std::string& /* conversationId */,
-                int code,
-                const std::string& /* what */) {
-                if (code == 2)
-                    errorDetected = true;
-                cv.notify_one();
-            }));
-    libjami::registerSignalHandlers(confHandlers);
     libjami::addConversationMember(aliceId, convId, bobUri);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; }));
-    errorDetected = false;
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
     libjami::acceptConversationRequest(bobId, convId);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return errorDetected; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.errorDetected; }));
 }
 
 void
 ConversationTest::testUpdateProfile()
 {
     std::cout << "\nRunning test: " << __func__ << std::endl;
+    connectSignals();
 
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto bobUri = bobAccount->getUsername();
 
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    auto messageBobReceived = 0, messageAliceReceived = 0;
-    confHandlers.insert(libjami::exportable_callback<libjami::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();
-        }));
-    bool requestReceived = false;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
-            [&](const std::string& /*accountId*/,
-                const std::string& /* conversationId */,
-                std::map<std::string, std::string> /*metadatas*/) {
-                requestReceived = true;
-                cv.notify_one();
-            }));
-    bool conversationReady = false;
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& /* conversationId */) {
-            if (accountId == bobId) {
-                conversationReady = true;
-                cv.notify_one();
-            }
-        }));
-    std::map<std::string, std::string> profileAlice, profileBob;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationProfileUpdated>(
-            [&](const auto& accountId, const auto& /* conversationId */, const auto& profile) {
-                if (accountId == aliceId) {
-                    profileAlice = profile;
-                } else if (accountId == bobId) {
-                    profileBob = profile;
-                }
-                cv.notify_one();
-            }));
-    libjami::registerSignalHandlers(confHandlers);
-
     auto convId = libjami::startConversation(aliceId);
-
+    auto aliceMsgSize = aliceData.messages.size();
     libjami::addConversationMember(aliceId, convId, bobUri);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; }));
-
-    messageAliceReceived = 0;
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
     libjami::acceptConversationRequest(bobId, convId);
-    CPPUNIT_ASSERT(
-        cv.wait_for(lk, 30s, [&]() { return conversationReady && messageAliceReceived == 1; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 2 == aliceData.messages.size() && !bobData.conversationId.empty(); }));
 
-    messageBobReceived = 0;
+    auto bobMsgSize = bobData.messages.size();
     aliceAccount->convModule()->updateConversationInfos(convId, {{"title", "My awesome swarm"}});
     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
-        return messageBobReceived == 1 && !profileAlice.empty() && !profileBob.empty();
+        return bobMsgSize + 1 == bobData.messages.size() && !aliceData.profile.empty() && !bobData.profile.empty();
     }));
 
     auto infos = libjami::conversationInfos(bobId, convId);
     // Verify that we have the same profile everywhere
     CPPUNIT_ASSERT(infos["title"] == "My awesome swarm");
-    CPPUNIT_ASSERT(profileAlice["title"] == "My awesome swarm");
-    CPPUNIT_ASSERT(profileBob["title"] == "My awesome swarm");
+    CPPUNIT_ASSERT(aliceData.profile["title"] == "My awesome swarm");
+    CPPUNIT_ASSERT(bobData.profile["title"] == "My awesome swarm");
     CPPUNIT_ASSERT(infos["description"].empty());
-    CPPUNIT_ASSERT(profileAlice["description"].empty());
-    CPPUNIT_ASSERT(profileBob["description"].empty());
-
-    libjami::unregisterSignalHandlers();
+    CPPUNIT_ASSERT(aliceData.profile["description"].empty());
+    CPPUNIT_ASSERT(bobData.profile["description"].empty());
 }
 
 void
 ConversationTest::testGetProfileRequest()
 {
     std::cout << "\nRunning test: " << __func__ << std::endl;
+    connectSignals();
 
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto bobUri = bobAccount->getUsername();
 
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    auto messageAliceReceived = 0;
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::MessageReceived>(
-        [&](const std::string& accountId,
-            const std::string& /* conversationId */,
-            std::map<std::string, std::string> /*message*/) {
-            if (accountId == aliceId)
-                messageAliceReceived += 1;
-            cv.notify_one();
-        }));
-    bool requestReceived = false;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
-            [&](const std::string& /*accountId*/,
-                const std::string& /* conversationId */,
-                std::map<std::string, std::string> /*metadatas*/) {
-                requestReceived = true;
-                cv.notify_one();
-            }));
-    libjami::registerSignalHandlers(confHandlers);
-
     auto convId = libjami::startConversation(aliceId);
-
-    messageAliceReceived = 0;
+    auto aliceMsgSize = aliceData.messages.size();
     aliceAccount->convModule()->updateConversationInfos(convId, {{"title", "My awesome swarm"}});
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return messageAliceReceived == 1; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 1 == aliceData.messages.size(); }));
 
     libjami::addConversationMember(aliceId, convId, bobUri);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
 
     auto infos = libjami::conversationInfos(bobId, convId);
     CPPUNIT_ASSERT(infos["title"] == "My awesome swarm");
     CPPUNIT_ASSERT(infos["description"].empty());
-
-    libjami::unregisterSignalHandlers();
 }
 
 void
 ConversationTest::testCheckProfileInConversationRequest()
 {
     std::cout << "\nRunning test: " << __func__ << std::endl;
+    connectSignals();
 
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto bobUri = bobAccount->getUsername();
 
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    auto messageBobReceived = 0, messageAliceReceived = 0;
-    bool requestReceived = false;
-    bool conversationReady = false;
-    confHandlers.insert(libjami::exportable_callback<libjami::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(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
-            [&](const std::string& /*accountId*/,
-                const std::string& /* conversationId */,
-                std::map<std::string, std::string> /*metadatas*/) {
-                requestReceived = true;
-                cv.notify_one();
-            }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& /* conversationId */) {
-            if (accountId == bobId) {
-                conversationReady = true;
-                cv.notify_one();
-            }
-        }));
-    libjami::registerSignalHandlers(confHandlers);
-
     auto convId = libjami::startConversation(aliceId);
     aliceAccount->convModule()->updateConversationInfos(convId, {{"title", "My awesome swarm"}});
 
     libjami::addConversationMember(aliceId, convId, bobUri);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
     auto requests = libjami::getConversationRequests(bobId);
     CPPUNIT_ASSERT(requests.size() == 1);
     CPPUNIT_ASSERT(requests.front()["title"] == "My awesome swarm");
-
-    libjami::unregisterSignalHandlers();
 }
 
 void
 ConversationTest::testCheckProfileInTrustRequest()
 {
     std::cout << "\nRunning test: " << __func__ << std::endl;
+    connectSignals();
 
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto bobUri = bobAccount->getUsername();
-    auto aliceUri = aliceAccount->getUsername();
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    bool conversationReady = false, requestReceived = false;
-    std::string convId = "";
     std::string vcard = "BEGIN:VCARD\n\
 VERSION:2.1\n\
 FN:TITLE\n\
 DESCRIPTION:DESC\n\
 END:VCARD";
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConfigurationSignal::IncomingTrustRequest>(
-            [&](const std::string& account_id,
-                const std::string& /*from*/,
-                const std::string& /*conversationId*/,
-                const std::vector<uint8_t>& payload,
-                time_t /*received*/) {
-                auto pstr = std::string(payload.begin(), payload.begin() + payload.size());
-                if (account_id == bobId
-                    && std::string(payload.data(), payload.data() + payload.size()) == vcard)
-                    requestReceived = true;
-                cv.notify_one();
-            }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& conversationId) {
-            if (accountId == aliceId) {
-                convId = conversationId;
-            } else if (accountId == bobId) {
-                conversationReady = true;
-            }
-            cv.notify_one();
-        }));
-    libjami::registerSignalHandlers(confHandlers);
     aliceAccount->addContact(bobUri);
     std::vector<uint8_t> payload(vcard.begin(), vcard.end());
     aliceAccount->sendTrustRequest(bobUri, payload);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&]() { return !convId.empty() && requestReceived; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&]() { return bobData.payloadTrustRequest == vcard; }));
 }
 
 void
 ConversationTest::testMemberCannotUpdateProfile()
 {
     std::cout << "\nRunning test: " << __func__ << std::endl;
+    connectSignals();
 
-    auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto bobUri = bobAccount->getUsername();
 
     auto convId = libjami::startConversation(aliceId);
-
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    auto messageBobReceived = 0, messageAliceReceived = 0;
-    bool requestReceived = false;
-    bool conversationReady = false;
-    bool errorDetected = false;
-    confHandlers.insert(libjami::exportable_callback<libjami::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(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
-            [&](const std::string& /*accountId*/,
-                const std::string& /* conversationId */,
-                std::map<std::string, std::string> /*metadatas*/) {
-                requestReceived = true;
-                cv.notify_one();
-            }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& /* conversationId */) {
-            if (accountId == bobId) {
-                conversationReady = true;
-                cv.notify_one();
-            }
-        }));
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::OnConversationError>(
-            [&](const std::string& accountId,
-                const std::string& conversationId,
-                int code,
-                const std::string& /* what */) {
-                if (accountId == bobId && conversationId == convId && code == 4)
-                    errorDetected = true;
-                cv.notify_one();
-            }));
-    libjami::registerSignalHandlers(confHandlers);
-
+    auto aliceMsgSize = aliceData.messages.size();
     libjami::addConversationMember(aliceId, convId, bobUri);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; }));
-
-    messageAliceReceived = 0;
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
     libjami::acceptConversationRequest(bobId, convId);
-    CPPUNIT_ASSERT(
-        cv.wait_for(lk, 30s, [&]() { return conversationReady && messageAliceReceived == 1; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 2 == aliceData.messages.size() && !bobData.conversationId.empty(); }));
 
-    messageBobReceived = 0;
     bobAccount->convModule()->updateConversationInfos(convId, {{"title", "My awesome swarm"}});
-    CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&]() { return errorDetected; }));
-
-    libjami::unregisterSignalHandlers();
+    CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&]() { return bobData.errorDetected; }));
 }
 
 void
 ConversationTest::testUpdateProfileWithBadFile()
 {
     std::cout << "\nRunning test: " << __func__ << std::endl;
+    connectSignals();
 
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto bobUri = bobAccount->getUsername();
-    auto convId = libjami::startConversation(aliceId);
-
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    auto messageBobReceived = 0, messageAliceReceived = 0;
-    bool requestReceived = false;
-    bool conversationReady = false;
-    bool errorDetected = false;
-    confHandlers.insert(libjami::exportable_callback<libjami::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(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
-            [&](const std::string& /*accountId*/,
-                const std::string& /* conversationId */,
-                std::map<std::string, std::string> /*metadatas*/) {
-                requestReceived = true;
-                cv.notify_one();
-            }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& /* conversationId */) {
-            if (accountId == bobId) {
-                conversationReady = true;
-                cv.notify_one();
-            }
-        }));
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::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();
-            }));
-    libjami::registerSignalHandlers(confHandlers);
 
+    auto convId = libjami::startConversation(aliceId);
+    auto aliceMsgSize = aliceData.messages.size();
     libjami::addConversationMember(aliceId, convId, bobUri);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; }));
-
-    messageAliceReceived = 0;
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
     libjami::acceptConversationRequest(bobId, convId);
-    CPPUNIT_ASSERT(
-        cv.wait_for(lk, 30s, [&]() { return conversationReady && messageAliceReceived == 1; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 2 == aliceData.messages.size() && !bobData.conversationId.empty(); }));
 
     // Update profile but with bad file
     addFile(aliceAccount, convId, "BADFILE");
@@ -2668,73 +1829,25 @@ END:VCARD";
     Json::Value root;
     root["type"] = "application/update-profile";
     commit(aliceAccount, convId, root);
-    errorDetected = false;
     libjami::sendMessage(aliceId, convId, "hi"s, "");
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return errorDetected; }));
-
-    libjami::unregisterSignalHandlers();
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.errorDetected; }));
 }
 
 void
 ConversationTest::testFetchProfileUnauthorized()
 {
     std::cout << "\nRunning test: " << __func__ << std::endl;
+    connectSignals();
 
-    auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto bobUri = bobAccount->getUsername();
-    auto convId = libjami::startConversation(aliceId);
-
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    auto messageBobReceived = 0, messageAliceReceived = 0;
-    bool requestReceived = false;
-    bool conversationReady = false;
-    bool errorDetected = false;
-    confHandlers.insert(libjami::exportable_callback<libjami::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(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
-            [&](const std::string& /*accountId*/,
-                const std::string& /* conversationId */,
-                std::map<std::string, std::string> /*metadatas*/) {
-                requestReceived = true;
-                cv.notify_one();
-            }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& /* conversationId */) {
-            if (accountId == bobId) {
-                conversationReady = true;
-                cv.notify_one();
-            }
-        }));
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::OnConversationError>(
-            [&](const std::string& accountId,
-                const std::string& conversationId,
-                int code,
-                const std::string& /* what */) {
-                if (accountId == aliceId && conversationId == convId && code == 3)
-                    errorDetected = true;
-                cv.notify_one();
-            }));
-    libjami::registerSignalHandlers(confHandlers);
 
+    auto convId = libjami::startConversation(aliceId);
+    auto aliceMsgSize = aliceData.messages.size();
     libjami::addConversationMember(aliceId, convId, bobUri);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; }));
-
-    messageAliceReceived = 0;
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
     libjami::acceptConversationRequest(bobId, convId);
-    CPPUNIT_ASSERT(
-        cv.wait_for(lk, 30s, [&]() { return conversationReady && messageAliceReceived == 1; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 2 == aliceData.messages.size() && !bobData.conversationId.empty(); }));
 
     // Fake realist profile update
     std::string vcard = "BEGIN:VCARD\n\
@@ -2746,61 +1859,36 @@ END:VCARD";
     Json::Value root;
     root["type"] = "application/update-profile";
     commit(bobAccount, convId, root);
-    errorDetected = false;
     libjami::sendMessage(bobId, convId, "hi"s, "");
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return errorDetected; }));
-
-    libjami::unregisterSignalHandlers();
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceData.errorDetected; }));
 }
 
 void
 ConversationTest::testSyncingWhileAccepting()
 {
     std::cout << "\nRunning test: " << __func__ << std::endl;
+    connectSignals();
 
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto bobUri = bobAccount->getUsername();
     auto aliceUri = aliceAccount->getUsername();
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    bool conversationReady = false, requestReceived = false;
-    std::string convId = "";
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConfigurationSignal::IncomingTrustRequest>(
-            [&](const std::string& account_id,
-                const std::string& /*from*/,
-                const std::string& /*conversationId*/,
-                const std::vector<uint8_t>& /*payload*/,
-                time_t /*received*/) {
-                if (account_id == bobId)
-                    requestReceived = true;
-                cv.notify_one();
-            }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& conversationId) {
-            if (accountId == aliceId) {
-                convId = conversationId;
-            } else if (accountId == bobId) {
-                conversationReady = true;
-            }
-            cv.notify_one();
-        }));
-    libjami::registerSignalHandlers(confHandlers);
+
     aliceAccount->addContact(bobUri);
     aliceAccount->sendTrustRequest(bobUri, {});
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
 
     Manager::instance().sendRegister(aliceId, false); // This avoid to sync immediately
     CPPUNIT_ASSERT(bobAccount->acceptTrustRequest(aliceUri));
 
-    auto convInfos = libjami::conversationInfos(bobId, convId);
+    auto convInfos = libjami::conversationInfos(bobId, aliceData.conversationId);
     CPPUNIT_ASSERT(convInfos["syncing"] == "true");
     CPPUNIT_ASSERT(convInfos.find("created") != convInfos.end());
 
     Manager::instance().sendRegister(aliceId, true); // This avoid to sync immediately
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return conversationReady; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !bobData.conversationId.empty(); }));
 
-    convInfos = libjami::conversationInfos(bobId, convId);
+    convInfos = libjami::conversationInfos(bobId, bobData.conversationId);
     CPPUNIT_ASSERT(convInfos.find("syncing") == convInfos.end());
 }
 
@@ -2843,85 +1931,44 @@ void
 ConversationTest::testReplayConversation()
 {
     std::cout << "\nRunning test: " << __func__ << std::endl;
+    connectSignals();
 
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto bobUri = bobAccount->getUsername();
     auto aliceUri = aliceAccount->getUsername();
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    bool conversationReady = false, requestReceived = false, memberMessageGenerated = false,
-         conversationRemoved = false, messageReceived = false;
-    std::vector<std::string> bobMessages;
-    std::string convId = "";
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConfigurationSignal::IncomingTrustRequest>(
-            [&](const std::string& account_id,
-                const std::string& /*from*/,
-                const std::string& /*conversationId*/,
-                const std::vector<uint8_t>& /*payload*/,
-                time_t /*received*/) {
-                if (account_id == bobId)
-                    requestReceived = true;
-                cv.notify_one();
-            }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& conversationId) {
-            if (accountId == aliceId) {
-                convId = conversationId;
-            } else if (accountId == bobId) {
-                conversationReady = true;
-            }
-            cv.notify_one();
-        }));
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRemoved>(
-            [&](const std::string& accountId, const std::string&) {
-                if (accountId == aliceId)
-                    conversationRemoved = true;
-                cv.notify_one();
-            }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::MessageReceived>(
-        [&](const std::string& accountId,
-            const std::string& conversationId,
-            std::map<std::string, std::string> message) {
-            if (accountId == aliceId && conversationId == convId) {
-                messageReceived = true;
-                if (message["type"] == "member")
-                    memberMessageGenerated = true;
-            } else if (accountId == bobId && message["type"] == "text/plain") {
-                bobMessages.emplace_back(message["body"]);
-            }
-            cv.notify_one();
-        }));
-    libjami::registerSignalHandlers(confHandlers);
-    requestReceived = false;
+
     aliceAccount->addContact(bobUri);
     aliceAccount->sendTrustRequest(bobUri, {});
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
+    auto aliceMsgSize = aliceData.messages.size();
     CPPUNIT_ASSERT(bobAccount->acceptTrustRequest(aliceUri));
     CPPUNIT_ASSERT(
-        cv.wait_for(lk, 30s, [&]() { return conversationReady && memberMessageGenerated; }));
+        cv.wait_for(lk, 30s, [&]() {
+            return !bobData.conversationId.empty() && aliceMsgSize + 1 == aliceData.messages.size(); }));
     // removeContact
     aliceAccount->removeContact(bobUri, false);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return conversationRemoved; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceData.removed; }));
+    std::this_thread::sleep_for(5s);
     // re-add
-    CPPUNIT_ASSERT(convId != "");
-    auto oldConvId = convId;
-    convId = "";
+    CPPUNIT_ASSERT(bobData.conversationId != "");
+    auto oldConvId = bobData.conversationId;
+    aliceData.conversationId = "";
     aliceAccount->addContact(bobUri);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !convId.empty(); }));
-    messageReceived = false;
-    libjami::sendMessage(aliceId, convId, "foo"s, "");
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return messageReceived; }));
-    messageReceived = false;
-    libjami::sendMessage(aliceId, convId, "bar"s, "");
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return messageReceived; }));
-    convId = "";
-    bobMessages.clear();
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !aliceData.conversationId.empty(); }));
+    aliceMsgSize = aliceData.messages.size();
+    libjami::sendMessage(aliceId, aliceData.conversationId, "foo"s, "");
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 1 == aliceData.messages.size(); }));
+    libjami::sendMessage(aliceId, aliceData.conversationId, "bar"s, "");
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 2 == aliceData.messages.size(); }));
+    bobData.messages.clear();
     aliceAccount->sendTrustRequest(bobUri, {});
     // Should retrieve previous conversation
     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
-        return bobMessages.size() == 2 && bobMessages[0] == "foo" && bobMessages[1] == "bar";
+        JAMI_ERROR("@@@ {}", bobData.messages.size());
+        if (bobData.messages.size() > 0)
+            JAMI_ERROR("@@@ {}", bobData.messages[0].body["body"]);
+        return bobData.messages.size() == 2 && bobData.messages[0].body["body"] == "foo" && bobData.messages[1].body["body"] == "bar";
     }));
 }
 
@@ -2929,69 +1976,13 @@ void
 ConversationTest::testSyncWithoutPinnedCert()
 {
     std::cout << "\nRunning test: " << __func__ << std::endl;
+    connectSignals();
 
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto bobUri = bobAccount->getUsername();
     auto aliceUri = aliceAccount->getUsername();
 
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    std::string convId = "";
-    auto requestReceived = false, conversationReady = false, memberMessageGenerated = false,
-         aliceMessageReceived = false;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConfigurationSignal::IncomingTrustRequest>(
-            [&](const std::string& account_id,
-                const std::string& /*from*/,
-                const std::string& /*conversationId*/,
-                const std::vector<uint8_t>& /*payload*/,
-                time_t /*received*/) {
-                if (account_id == bobId)
-                    requestReceived = true;
-                cv.notify_one();
-            }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& conversationId) {
-            if (accountId == aliceId) {
-                convId = conversationId;
-            } else {
-                conversationReady = true;
-            }
-            cv.notify_one();
-        }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::MessageReceived>(
-        [&](const std::string& accountId,
-            const std::string& conversationId,
-            std::map<std::string, std::string> message) {
-            if (accountId == aliceId && conversationId == convId) {
-                if (message["type"] == "member")
-                    memberMessageGenerated = true;
-                else if (message["type"] == "text/plain")
-                    aliceMessageReceived = true;
-            }
-            cv.notify_one();
-        }));
-    auto bob2Started = false, aliceStopped = false, bob2Stopped = false;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConfigurationSignal::VolatileDetailsChanged>(
-            [&](const std::string&, const std::map<std::string, std::string>&) {
-                auto bob2Account = Manager::instance().getAccount<JamiAccount>(bob2Id);
-                if (!bob2Account)
-                    return;
-                auto details = bob2Account->getVolatileAccountDetails();
-                auto daemonStatus = details[libjami::Account::ConfProperties::Registration::STATUS];
-                if (daemonStatus == "REGISTERED")
-                    bob2Started = true;
-                if (daemonStatus == "UNREGISTERED")
-                    bob2Stopped = true;
-                details = aliceAccount->getVolatileAccountDetails();
-                daemonStatus = details[libjami::Account::ConfProperties::Registration::STATUS];
-                if (daemonStatus == "UNREGISTERED")
-                    aliceStopped = true;
-                cv.notify_one();
-            }));
-    libjami::registerSignalHandlers(confHandlers);
-
     // Bob creates a second device
     auto bobArchive = std::filesystem::current_path().string() + "/bob.gz";
     std::remove(bobArchive.c_str());
@@ -3007,33 +1998,31 @@ ConversationTest::testSyncWithoutPinnedCert()
     bob2Id = Manager::instance().addAccount(details);
 
     // Disconnect bob2, to create a valid conv betwen Alice and Bob1
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bob2Started; }));
-    bob2Stopped = false;
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bob2Data.registered; }));
     Manager::instance().sendRegister(bob2Id, false);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bob2Stopped; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bob2Data.stopped; }));
 
     // Alice adds bob
-    requestReceived = false;
     aliceAccount->addContact(bobUri);
     aliceAccount->sendTrustRequest(bobUri, {});
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
+    auto aliceMsgSize = aliceData.messages.size();
     CPPUNIT_ASSERT(bobAccount->acceptTrustRequest(aliceUri));
     CPPUNIT_ASSERT(
-        cv.wait_for(lk, 30s, [&]() { return conversationReady && memberMessageGenerated; }));
+        cv.wait_for(lk, 30s, [&]() {
+            return !bobData.conversationId.empty() && aliceMsgSize + 1 == aliceData.messages.size(); }));
 
     // Bob send a message
-    libjami::sendMessage(bobId, convId, "hi"s, "");
-    cv.wait_for(lk, 30s, [&]() { return aliceMessageReceived; });
+    libjami::sendMessage(bobId, bobData.conversationId, "hi"s, "");
+    cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 2 == aliceData.messages.size(); });
 
     // Alice off, bob2 On
-    conversationReady = false;
-    aliceStopped = false;
     Manager::instance().sendRegister(aliceId, false);
-    cv.wait_for(lk, 10s, [&]() { return aliceStopped; });
+    cv.wait_for(lk, 10s, [&]() { return aliceData.stopped; });
     Manager::instance().sendRegister(bob2Id, true);
 
     // Sync + validate
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return conversationReady; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !bob2Data.conversationId.empty(); }));
 }
 
 void
@@ -3065,6 +2054,7 @@ void
 ConversationTest::testRemoveReaddMultipleDevice()
 {
     std::cout << "\nRunning test: " << __func__ << std::endl;
+    connectSignals();
 
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
@@ -3086,67 +2076,6 @@ END:VCARD";
         file.close();
     }
 
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    auto requestReceived = false, requestReceivedBob2 = false;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
-            [&](const std::string& accountId,
-                const std::string& conversationId,
-                std::map<std::string, std::string> /*metadatas*/) {
-                if (accountId == bobId)
-                    requestReceived = true;
-                else if (accountId == bob2Id)
-                    requestReceivedBob2 = true;
-                cv.notify_one();
-            }));
-    std::string convId = "";
-    auto conversationReadyBob = false, conversationReadyBob2 = false;
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& conversationId) {
-            if (accountId == aliceId) {
-                convId = conversationId;
-            } else if (accountId == bobId) {
-                conversationReadyBob = true;
-            } else if (accountId == bob2Id) {
-                conversationReadyBob2 = true;
-            }
-            cv.notify_one();
-        }));
-    auto memberMessageGenerated = false;
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::MessageReceived>(
-        [&](const std::string& accountId,
-            const std::string& conversationId,
-            std::map<std::string, std::string> message) {
-            if (accountId == aliceId && conversationId == convId) {
-                if (message["type"] == "member")
-                    memberMessageGenerated = true;
-            }
-            cv.notify_one();
-        }));
-    auto bob2Started = false;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConfigurationSignal::VolatileDetailsChanged>(
-            [&](const std::string& accountId, const std::map<std::string, std::string>& details) {
-                if (accountId == bob2Id) {
-                    auto daemonStatus = details.at(
-                        libjami::Account::VolatileProperties::DEVICE_ANNOUNCED);
-                    if (daemonStatus == "true")
-                        bob2Started = true;
-                }
-                cv.notify_one();
-            }));
-    auto conversationRmBob = false, conversationRmBob2 = false;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRemoved>(
-            [&](const std::string& accountId, const std::string&) {
-                if (accountId == bobId)
-                    conversationRmBob = true;
-                else if (accountId == bob2Id)
-                    conversationRmBob2 = true;
-                cv.notify_one();
-            }));
-    libjami::registerSignalHandlers(confHandlers);
-
     // Bob creates a second device
     auto bobArchive = std::filesystem::current_path().string() + "/bob.gz";
     std::remove(bobArchive.c_str());
@@ -3161,110 +2090,40 @@ END:VCARD";
     details[ConfProperties::ARCHIVE_PATH] = bobArchive;
     bob2Id = Manager::instance().addAccount(details);
 
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bob2Started; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bob2Data.deviceAnnounced; }));
 
     // Alice adds bob
-    requestReceived = false, requestReceivedBob2 = false;
     aliceAccount->addContact(bobUri);
     aliceAccount->sendTrustRequest(bobUri, {});
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived && requestReceivedBob2; }));
-    libjami::acceptConversationRequest(bobId, convId);
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived && bob2Data.requestReceived; }));
+    auto aliceMsgSize = aliceData.messages.size();
+    libjami::acceptConversationRequest(bobId, aliceData.conversationId);
     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
-        return conversationReadyBob && conversationReadyBob2 && memberMessageGenerated;
+        return !bobData.conversationId.empty() && !bob2Data.conversationId.empty() && aliceMsgSize + 1 == aliceData.messages.size();
     }));
 
     // Remove contact
     bobAccount->removeContact(aliceUri, false);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return conversationRmBob && conversationRmBob2; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.removed && bob2Data.removed; }));
 
     // wait that connections are closed.
-    std::this_thread::sleep_for(10s);
+    std::this_thread::sleep_for(5s);
 
-    // Alice send a message. This will not trigger any request, because contact was removed.
-    requestReceived = false, requestReceivedBob2 = false;
-    libjami::sendMessage(aliceId, convId, "hi"s, "");
-    CPPUNIT_ASSERT(!cv.wait_for(lk, 30s, [&]() { return requestReceived || requestReceivedBob2; }));
+    // Alice send a message
+    bobData.requestReceived = false; bob2Data.requestReceived = false;
+    libjami::sendMessage(aliceId, aliceData.conversationId, "hi"s, "");
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived && bob2Data.requestReceived; }));
 }
 
 void
 ConversationTest::testCloneFromMultipleDevice()
 {
-    std::cout << "\nRunning test: " << __func__ << std::endl;
-
-    auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
-    auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
-    auto bobUri = bobAccount->getUsername();
-    auto aliceUri = aliceAccount->getUsername();
-
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    auto requestReceived = false, requestReceivedBob2 = false;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
-            [&](const std::string& accountId,
-                const std::string& conversationId,
-                std::map<std::string, std::string> /*metadatas*/) {
-                if (accountId == bobId)
-                    requestReceived = true;
-                else if (accountId == bob2Id)
-                    requestReceivedBob2 = true;
-                cv.notify_one();
-            }));
-    std::string convId = "";
-    auto conversationReadyBob = false, conversationReadyBob2 = false;
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& conversationId) {
-            if (accountId == aliceId) {
-                convId = conversationId;
-            } else if (accountId == bobId) {
-                conversationReadyBob = true;
-            } else if (accountId == bob2Id) {
-                conversationReadyBob2 = true;
-            }
-            cv.notify_one();
-        }));
-    auto memberMessageGenerated = false;
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::MessageReceived>(
-        [&](const std::string& accountId,
-            const std::string& conversationId,
-            std::map<std::string, std::string> message) {
-            if (accountId == aliceId && conversationId == convId) {
-                if (message["type"] == "member")
-                    memberMessageGenerated = true;
-            }
-            cv.notify_one();
-        }));
-    auto bob2Started = false;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConfigurationSignal::VolatileDetailsChanged>(
-            [&](const std::string& accountId, const std::map<std::string, std::string>& details) {
-                if (accountId == bob2Id) {
-                    auto daemonStatus = details.at(
-                        libjami::Account::VolatileProperties::DEVICE_ANNOUNCED);
-                    if (daemonStatus == "true")
-                        bob2Started = true;
-                }
-                cv.notify_one();
-            }));
-    auto conversationRmAlice = false;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRemoved>(
-            [&](const std::string& accountId, const std::string&) {
-                if (accountId == aliceId)
-                    conversationRmAlice = true;
-                cv.notify_one();
-            }));
-    auto errorDetected = false;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::OnConversationError>(
-            [&](const std::string& /* accountId */,
-                const std::string& /* conversationId */,
-                int code,
-                const std::string& /* what */) {
-                if (code == 1)
-                    errorDetected = true;
-                cv.notify_one();
-            }));
-    libjami::registerSignalHandlers(confHandlers);
+    std::cout << "\nRunning test: " << __func__ << std::endl;
+    connectSignals();
+
+    auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
+    auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
+    auto bobUri = bobAccount->getUsername();
 
     // Bob creates a second device
     auto bobArchive = std::filesystem::current_path().string() + "/bob.gz";
@@ -3280,227 +2139,116 @@ ConversationTest::testCloneFromMultipleDevice()
     details[ConfProperties::ARCHIVE_PATH] = bobArchive;
     bob2Id = Manager::instance().addAccount(details);
 
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bob2Started; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bob2Data.deviceAnnounced; }));
 
     // Alice adds bob
-    requestReceived = false, requestReceivedBob2 = false;
     aliceAccount->addContact(bobUri);
     aliceAccount->sendTrustRequest(bobUri, {});
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived && requestReceivedBob2; }));
-    libjami::acceptConversationRequest(bobId, convId);
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived && bob2Data.requestReceived; }));
+    auto aliceMsgSize = aliceData.messages.size();
+    libjami::acceptConversationRequest(bobId, aliceData.conversationId);
     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
-        return conversationReadyBob && conversationReadyBob2 && memberMessageGenerated;
+        return !bobData.conversationId.empty() && !bob2Data.conversationId.empty() && aliceMsgSize + 1 == aliceData.messages.size();
     }));
 
     // Remove contact
     aliceAccount->removeContact(bobUri, false);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return conversationRmAlice; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceData.removed; }));
 
     // wait that connections are closed.
     std::this_thread::sleep_for(10s);
 
     // Alice re-adds Bob
-    auto oldConv = convId;
-    conversationRmAlice = false;
+    auto oldConv = bobData.conversationId;
     aliceAccount->addContact(bobUri);
     aliceAccount->sendTrustRequest(bobUri, {});
     // This should retrieve the conversation from Bob and don't show any error
-    CPPUNIT_ASSERT(!cv.wait_for(lk, 10s, [&]() { return errorDetected; }));
-    CPPUNIT_ASSERT(conversationRmAlice);
-    CPPUNIT_ASSERT(oldConv == convId); // Check that convId didn't change and conversation is ready.
+    CPPUNIT_ASSERT(!cv.wait_for(lk, 10s, [&]() { return aliceData.errorDetected; }));
+    CPPUNIT_ASSERT(oldConv == aliceData.conversationId); // Check that convId didn't change and conversation is ready.
 }
 
 void
 ConversationTest::testSendReply()
 {
     std::cout << "\nRunning test: " << __func__ << std::endl;
+    connectSignals();
 
-    auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto bobUri = bobAccount->getUsername();
-
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    std::vector<std::map<std::string, std::string>> messageBobReceived = {},
-                                                    messageAliceReceived = {};
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::MessageReceived>(
-        [&](const std::string& accountId,
-            const std::string& /* conversationId */,
-            std::map<std::string, std::string> message) {
-            if (accountId == bobId) {
-                messageBobReceived.emplace_back(message);
-            } else {
-                messageAliceReceived.emplace_back(message);
-            }
-            cv.notify_one();
-        }));
-    bool requestReceived = false;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
-            [&](const std::string& /*accountId*/,
-                const std::string& /* conversationId */,
-                std::map<std::string, std::string> /*metadatas*/) {
-                requestReceived = true;
-                cv.notify_one();
-            }));
-    bool conversationReady = false;
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& /* conversationId */) {
-            if (accountId == bobId) {
-                conversationReady = true;
-                cv.notify_one();
-            }
-        }));
-    libjami::registerSignalHandlers(confHandlers);
-
     auto convId = libjami::startConversation(aliceId);
 
     libjami::addConversationMember(aliceId, convId, bobUri);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; }));
-
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
+    auto aliceMsgSize = aliceData.messages.size();
     libjami::acceptConversationRequest(bobId, convId);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return conversationReady; }));
-
-    // Assert that repository exists
-    auto repoPath = fileutils::get_data_dir() / bobAccount->getAccountID()
-                    / "conversations" / convId;
-    CPPUNIT_ASSERT(std::filesystem::is_directory(repoPath));
-    // Wait that alice sees Bob
-    cv.wait_for(lk, 30s, [&]() { return messageAliceReceived.size() == 2; });
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !bobData.conversationId.empty() && aliceMsgSize + 1 == aliceData.messages.size(); }));
 
-    messageBobReceived.clear();
+    auto bobMsgSize = bobData.messages.size();
     libjami::sendMessage(aliceId, convId, "hi"s, "");
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return messageBobReceived.size() == 1; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.messages.size() == bobMsgSize + 1; }));
 
-    auto validId = messageBobReceived.at(0).at("id");
+    auto validId = bobData.messages.at(0).id;
     libjami::sendMessage(aliceId, convId, "foo"s, validId);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&]() { return messageBobReceived.size() == 2; }));
-    CPPUNIT_ASSERT(messageBobReceived.rbegin()->at("reply-to") == validId);
+    CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&]() { return bobData.messages.size() == bobMsgSize + 2; }));
+    CPPUNIT_ASSERT(bobData.messages.rbegin()->body.at("reply-to") == validId);
 
     // Check if parent doesn't exists, no message is generated
     libjami::sendMessage(aliceId, convId, "foo"s, "invalid");
-    CPPUNIT_ASSERT(!cv.wait_for(lk, 10s, [&]() { return messageBobReceived.size() == 3; }));
+    CPPUNIT_ASSERT(!cv.wait_for(lk, 10s, [&]() { return bobData.messages.size() == bobMsgSize + 3; }));
 }
 
 void
 ConversationTest::testSearchInConv()
 {
     std::cout << "\nRunning test: " << __func__ << std::endl;
+    connectSignals();
 
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto bobUri = bobAccount->getUsername();
     auto aliceUri = aliceAccount->getUsername();
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    bool conversationReady = false, requestReceived = false, memberMessageGenerated = false,
-         messageReceived = false;
-    std::vector<std::string> bobMessages;
-    std::string convId = "";
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConfigurationSignal::IncomingTrustRequest>(
-            [&](const std::string& account_id,
-                const std::string& /*from*/,
-                const std::string& /*conversationId*/,
-                const std::vector<uint8_t>& /*payload*/,
-                time_t /*received*/) {
-                if (account_id == bobId)
-                    requestReceived = true;
-                cv.notify_one();
-            }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& conversationId) {
-            if (accountId == aliceId) {
-                convId = conversationId;
-            } else if (accountId == bobId) {
-                conversationReady = true;
-            }
-            cv.notify_one();
-        }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::MessageReceived>(
-        [&](const std::string& accountId,
-            const std::string& conversationId,
-            std::map<std::string, std::string> message) {
-            if (accountId == aliceId && conversationId == convId) {
-                if (message["type"] == "member")
-                    memberMessageGenerated = true;
-            } else if (accountId == bobId) {
-                messageReceived = true;
-            }
-            cv.notify_one();
-        }));
-    std::vector<std::map<std::string, std::string>> messages;
-    bool finished = false;
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::MessagesFound>(
-        [&](uint32_t,
-            const std::string&,
-            const std::string& conversationId,
-            std::vector<std::map<std::string, std::string>> msg) {
-            if (conversationId == convId)
-                messages = msg;
-            finished = conversationId.empty();
-            cv.notify_one();
-        }));
-    libjami::registerSignalHandlers(confHandlers);
-    requestReceived = false;
+
+    auto aliceMsgSize = aliceData.messages.size();
     aliceAccount->addContact(bobUri);
     aliceAccount->sendTrustRequest(bobUri, {});
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
     CPPUNIT_ASSERT(bobAccount->acceptTrustRequest(aliceUri));
     CPPUNIT_ASSERT(
-        cv.wait_for(lk, 30s, [&]() { return conversationReady && memberMessageGenerated; }));
+        cv.wait_for(lk, 30s, [&]() { return !bobData.conversationId.empty() && aliceMsgSize + 1 == aliceData.messages.size(); }));
     // Add some messages
-    messageReceived = false;
-    libjami::sendMessage(aliceId, convId, "message 1"s, "");
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return messageReceived; }));
-    messageReceived = false;
-    libjami::sendMessage(aliceId, convId, "message 2"s, "");
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return messageReceived; }));
-    messageReceived = false;
-    libjami::sendMessage(aliceId, convId, "Message 3"s, "");
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return messageReceived; }));
-
-    libjami::searchConversation(aliceId, convId, "", "", "message", "", 0, 0, 0, 0);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return messages.size() == 3 && finished; }));
-    messages.clear();
-    finished = false;
-    libjami::searchConversation(aliceId, convId, "", "", "Message", "", 0, 0, 0, 1);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return messages.size() == 1 && finished; }));
-    messages.clear();
-    finished = false;
-    libjami::searchConversation(aliceId, convId, "", "", "message 2", "", 0, 0, 0, 0);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return messages.size() == 1 && finished; }));
-    messages.clear();
-    finished = false;
-    libjami::searchConversation(aliceId, convId, "", "", "foo", "", 0, 0, 0, 0);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return messages.size() == 0 && finished; }));
+    auto bobMsgSize = bobData.messages.size();
+    libjami::sendMessage(aliceId, aliceData.conversationId, "message 1"s, "");
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobMsgSize + 1 == bobData.messages.size(); }));
+    libjami::sendMessage(aliceId, aliceData.conversationId, "message 2"s, "");
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobMsgSize + 2 == bobData.messages.size(); }));
+    libjami::sendMessage(aliceId, aliceData.conversationId, "Message 3"s, "");
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobMsgSize + 3 == bobData.messages.size(); }));
+
+    libjami::searchConversation(aliceId, aliceData.conversationId, "", "", "message", "", 0, 0, 0, 0);
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceData.messagesFound.size() == 3 && aliceData.searchFinished; }));
+    aliceData.messagesFound.clear();
+    aliceData.searchFinished = false;
+    libjami::searchConversation(aliceId, aliceData.conversationId, "", "", "Message", "", 0, 0, 0, 1);
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceData.messagesFound.size() == 1 && aliceData.searchFinished; }));
+    aliceData.messagesFound.clear();
+    aliceData.searchFinished = false;
+    libjami::searchConversation(aliceId, aliceData.conversationId, "", "", "message 2", "", 0, 0, 0, 0);
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceData.messagesFound.size() == 1 && aliceData.searchFinished; }));
+    aliceData.messagesFound.clear();
+    aliceData.searchFinished = false;
+    libjami::searchConversation(aliceId, aliceData.conversationId, "", "", "foo", "", 0, 0, 0, 0);
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceData.messagesFound.size() == 0 && aliceData.searchFinished; }));
 }
 
 void
 ConversationTest::testConversationPreferences()
 {
     std::cout << "\nRunning test: " << __func__ << std::endl;
+    connectSignals();
 
-    auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
-    auto uri = aliceAccount->getUsername();
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    bool conversationReady = false, conversationRemoved = false;
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& /* conversationId */) {
-            if (accountId == aliceId) {
-                conversationReady = true;
-                cv.notify_one();
-            }
-        }));
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRemoved>(
-            [&](const std::string& accountId, const std::string&) {
-                if (accountId == aliceId)
-                    conversationRemoved = true;
-                cv.notify_one();
-            }));
-    libjami::registerSignalHandlers(confHandlers);
     // Start conversation and set preferences
     auto convId = libjami::startConversation(aliceId);
-    cv.wait_for(lk, 30s, [&]() { return conversationReady; });
+    cv.wait_for(lk, 30s, [&]() { return !aliceData.conversationId.empty(); });
     CPPUNIT_ASSERT(libjami::getConversationPreferences(aliceId, convId).size() == 0);
     libjami::setConversationPreferences(aliceId, convId, {{"foo", "bar"}});
     auto preferences = libjami::getConversationPreferences(aliceId, convId);
@@ -3514,7 +2262,7 @@ ConversationTest::testConversationPreferences()
     CPPUNIT_ASSERT(preferences["bar"] == "foo");
     // Remove conversations removes its preferences.
     CPPUNIT_ASSERT(libjami::removeConversation(aliceId, convId));
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return conversationRemoved; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceData.removed; }));
     CPPUNIT_ASSERT(libjami::getConversationPreferences(aliceId, convId).size() == 0);
 }
 
@@ -3522,79 +2270,27 @@ void
 ConversationTest::testConversationPreferencesBeforeClone()
 {
     std::cout << "\nRunning test: " << __func__ << std::endl;
+    connectSignals();
 
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto bobUri = bobAccount->getUsername();
-    auto aliceUri = aliceAccount->getUsername();
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    auto requestReceived = false, requestReceivedBob2 = false;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
-            [&](const std::string& accountId,
-                const std::string& conversationId,
-                std::map<std::string, std::string> /*metadatas*/) {
-                if (accountId == bobId)
-                    requestReceived = true;
-                else if (accountId == bob2Id)
-                    requestReceivedBob2 = true;
-                cv.notify_one();
-            }));
-    std::string convId = "";
-    auto conversationReadyBob = false, conversationReadyBob2 = false;
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& conversationId) {
-            if (accountId == aliceId) {
-                convId = conversationId;
-            } else if (accountId == bobId) {
-                conversationReadyBob = true;
-            } else if (accountId == bob2Id) {
-                conversationReadyBob2 = true;
-            }
-            cv.notify_one();
-        }));
-    auto bob2Started = false;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConfigurationSignal::VolatileDetailsChanged>(
-            [&](const std::string& accountId, const std::map<std::string, std::string>& details) {
-                if (accountId == bob2Id) {
-                    auto daemonStatus = details.at(
-                        libjami::Account::VolatileProperties::DEVICE_ANNOUNCED);
-                    if (daemonStatus == "true")
-                        bob2Started = true;
-                }
-                cv.notify_one();
-            }));
-    std::map<std::string, std::string> preferencesBob, preferencesBob2;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationPreferencesUpdated>(
-            [&](const std::string& accountId,
-                const std::string& conversationId,
-                std::map<std::string, std::string> preferences) {
-                if (accountId == bobId)
-                    preferencesBob = preferences;
-                else if (accountId == bob2Id)
-                    preferencesBob2 = preferences;
-                cv.notify_one();
-            }));
-    libjami::registerSignalHandlers(confHandlers);
     // Bob creates a second device
     auto bobArchive = std::filesystem::current_path().string() + "/bob.gz";
     std::remove(bobArchive.c_str());
     bobAccount->exportArchive(bobArchive);
     // Alice adds bob
-    requestReceived = false;
     aliceAccount->addContact(bobUri);
     aliceAccount->sendTrustRequest(bobUri, {});
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; }));
-    libjami::acceptConversationRequest(bobId, convId);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return conversationReadyBob; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
+    libjami::acceptConversationRequest(bobId, aliceData.conversationId);
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !bobData.conversationId.empty(); }));
 
     // Set preferences
     Manager::instance().sendRegister(aliceId, false);
-    libjami::setConversationPreferences(bobId, convId, {{"foo", "bar"}, {"bar", "foo"}});
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return preferencesBob.size() == 2; }));
-    CPPUNIT_ASSERT(preferencesBob["foo"] == "bar" && preferencesBob["bar"] == "foo");
+    libjami::setConversationPreferences(bobId, bobData.conversationId, {{"foo", "bar"}, {"bar", "foo"}});
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.preferences.size() == 2; }));
+    CPPUNIT_ASSERT(bobData.preferences["foo"] == "bar" && bobData.preferences["bar"] == "foo");
 
     // Bob2 should sync preferences
     std::map<std::string, std::string> details = libjami::getAccountTemplate("RING");
@@ -3607,71 +2303,20 @@ ConversationTest::testConversationPreferencesBeforeClone()
     details[ConfProperties::ARCHIVE_PATH] = bobArchive;
     bob2Id = Manager::instance().addAccount(details);
     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
-        return bob2Started && conversationReadyBob2 && !preferencesBob2.empty();
+        return bob2Data.deviceAnnounced && !bob2Data.conversationId.empty() && !bob2Data.preferences.empty();
     }));
-    CPPUNIT_ASSERT(preferencesBob2["foo"] == "bar" && preferencesBob2["bar"] == "foo");
+    CPPUNIT_ASSERT(bob2Data.preferences["foo"] == "bar" && bob2Data.preferences["bar"] == "foo");
 }
 
 void
 ConversationTest::testConversationPreferencesMultiDevices()
 {
     std::cout << "\nRunning test: " << __func__ << std::endl;
+    connectSignals();
 
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto bobUri = bobAccount->getUsername();
-    auto aliceUri = aliceAccount->getUsername();
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    auto requestReceived = false, requestReceivedBob2 = false;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
-            [&](const std::string& accountId,
-                const std::string& conversationId,
-                std::map<std::string, std::string> /*metadatas*/) {
-                if (accountId == bobId)
-                    requestReceived = true;
-                else if (accountId == bob2Id)
-                    requestReceivedBob2 = true;
-                cv.notify_one();
-            }));
-    std::string convId = "";
-    auto conversationReadyBob = false, conversationReadyBob2 = false;
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& conversationId) {
-            if (accountId == aliceId) {
-                convId = conversationId;
-            } else if (accountId == bobId) {
-                conversationReadyBob = true;
-            } else if (accountId == bob2Id) {
-                conversationReadyBob2 = true;
-            }
-            cv.notify_one();
-        }));
-    auto bob2Started = false;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConfigurationSignal::VolatileDetailsChanged>(
-            [&](const std::string& accountId, const std::map<std::string, std::string>& details) {
-                if (accountId == bob2Id) {
-                    auto daemonStatus = details.at(
-                        libjami::Account::VolatileProperties::DEVICE_ANNOUNCED);
-                    if (daemonStatus == "true")
-                        bob2Started = true;
-                }
-                cv.notify_one();
-            }));
-    std::map<std::string, std::string> preferencesBob, preferencesBob2;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationPreferencesUpdated>(
-            [&](const std::string& accountId,
-                const std::string& conversationId,
-                std::map<std::string, std::string> preferences) {
-                if (accountId == bobId)
-                    preferencesBob = preferences;
-                else if (accountId == bob2Id)
-                    preferencesBob2 = preferences;
-                cv.notify_one();
-            }));
-    libjami::registerSignalHandlers(confHandlers);
     // Bob creates a second device
     auto bobArchive = std::filesystem::current_path().string() + "/bob.gz";
     std::remove(bobArchive.c_str());
@@ -3685,50 +2330,39 @@ ConversationTest::testConversationPreferencesMultiDevices()
     details[ConfProperties::ARCHIVE_PIN] = "";
     details[ConfProperties::ARCHIVE_PATH] = bobArchive;
     bob2Id = Manager::instance().addAccount(details);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bob2Started; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bob2Data.deviceAnnounced; }));
     // Alice adds bob
-    requestReceived = false;
     aliceAccount->addContact(bobUri);
     aliceAccount->sendTrustRequest(bobUri, {});
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived && requestReceivedBob2; }));
-    libjami::acceptConversationRequest(bobId, convId);
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived && bob2Data.requestReceived; }));
+    libjami::acceptConversationRequest(bobId, aliceData.conversationId);
     CPPUNIT_ASSERT(
-        cv.wait_for(lk, 30s, [&]() { return conversationReadyBob && conversationReadyBob2; }));
-    libjami::setConversationPreferences(bobId, convId, {{"foo", "bar"}, {"bar", "foo"}});
+        cv.wait_for(lk, 30s, [&]() { return !bobData.conversationId.empty() && !bob2Data.conversationId.empty(); }));
+    libjami::setConversationPreferences(bobId, bobData.conversationId, {{"foo", "bar"}, {"bar", "foo"}});
     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
-        return preferencesBob.size() == 2 && preferencesBob2.size() == 2;
+        return bobData.preferences.size() == 2 && bob2Data.preferences.size() == 2;
     }));
-    CPPUNIT_ASSERT(preferencesBob["foo"] == "bar" && preferencesBob["bar"] == "foo");
-    CPPUNIT_ASSERT(preferencesBob2["foo"] == "bar" && preferencesBob2["bar"] == "foo");
+    CPPUNIT_ASSERT(bobData.preferences["foo"] == "bar" && bobData.preferences["bar"] == "foo");
+    CPPUNIT_ASSERT(bob2Data.preferences["foo"] == "bar" && bob2Data.preferences["bar"] == "foo");
 }
 
 void
 ConversationTest::testFixContactDetails()
 {
     std::cout << "\nRunning test: " << __func__ << std::endl;
+    connectSignals();
 
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto bobUri = bobAccount->getUsername();
 
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    std::string convId = "";
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& conversationId) {
-            if (accountId == aliceId) {
-                convId = conversationId;
-            }
-            cv.notify_one();
-        }));
-    libjami::registerSignalHandlers(confHandlers);
-
     aliceAccount->addContact(bobUri);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&]() { return !convId.empty(); }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&]() { return !aliceData.conversationId.empty(); }));
 
     auto details = aliceAccount->getContactDetails(bobUri);
-    CPPUNIT_ASSERT(details["conversationId"] == convId);
+    CPPUNIT_ASSERT(details["conversationId"] == aliceData.conversationId);
     // Erase convId from contact details, this should be fixed by next reload.
-    CPPUNIT_ASSERT(aliceAccount->updateConvForContact(bobUri, convId, ""));
+    CPPUNIT_ASSERT(aliceAccount->updateConvForContact(bobUri, aliceData.conversationId, ""));
     details = aliceAccount->getContactDetails(bobUri);
     CPPUNIT_ASSERT(details["conversationId"].empty());
 
@@ -3737,136 +2371,77 @@ ConversationTest::testFixContactDetails()
     std::this_thread::sleep_for(5s); // Let the daemon fix the structures
 
     details = aliceAccount->getContactDetails(bobUri);
-    CPPUNIT_ASSERT(details["conversationId"] == convId);
+    CPPUNIT_ASSERT(details["conversationId"] == aliceData.conversationId);
 }
 
 void
 ConversationTest::testRemoveOneToOneNotInDetails()
 {
     std::cout << "\nRunning test: " << __func__ << std::endl;
+    connectSignals();
 
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto bobUri = bobAccount->getUsername();
 
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    std::string convId = "", secondConv;
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& conversationId) {
-            if (accountId == aliceId) {
-                if (convId.empty())
-                    convId = conversationId;
-                else
-                    secondConv = conversationId;
-            }
-            cv.notify_one();
-        }));
-    bool conversationRemoved = false;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRemoved>(
-            [&](const std::string& accountId, const std::string& cid) {
-                if (accountId == aliceId && cid == secondConv)
-                    conversationRemoved = true;
-                cv.notify_one();
-            }));
-    libjami::registerSignalHandlers(confHandlers);
-
     aliceAccount->addContact(bobUri);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&]() { return !convId.empty(); }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&]() { return !aliceData.conversationId.empty(); }));
 
     auto details = aliceAccount->getContactDetails(bobUri);
-    CPPUNIT_ASSERT(details["conversationId"] == convId);
+    CPPUNIT_ASSERT(details["conversationId"] == aliceData.conversationId);
+    auto firstConv = aliceData.conversationId;
     // Create a duplicate
     std::this_thread::sleep_for(2s); // Avoid to get same id
     aliceAccount->convModule()->startConversation(ConversationMode::ONE_TO_ONE, bobUri);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&]() { return !secondConv.empty(); }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&]() { return firstConv != aliceData.conversationId; }));
 
     // Assert that repository exists
-    auto repoPath = fileutils::get_data_dir() / aliceAccount->getAccountID()
-                    / "conversations" / secondConv;
+    auto repoPath = fileutils::get_data_dir() / aliceId
+                    / "conversations" / aliceData.conversationId;
     CPPUNIT_ASSERT(std::filesystem::is_directory(repoPath));
 
     aliceAccount->convModule()->loadConversations();
 
     // Check that conv is removed
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return conversationRemoved; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceData.removed; }));
 }
 
 void
 ConversationTest::testMessageEdition()
 {
     std::cout << "\nRunning test: " << __func__ << std::endl;
+    connectSignals();
 
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto bobUri = bobAccount->getUsername();
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    std::vector<std::map<std::string, std::string>> messageBobReceived;
-    bool conversationReady = false, memberMessageGenerated = false;
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::MessageReceived>(
-        [&](const std::string& accountId,
-            const std::string& /* conversationId */,
-            std::map<std::string, std::string> message) {
-            if (accountId == bobId) {
-                messageBobReceived.emplace_back(message);
-            } else if (accountId == aliceId && message["type"] == "member") {
-                memberMessageGenerated = true;
-            }
-            cv.notify_one();
-        }));
-    bool requestReceived = false;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
-            [&](const std::string& /*accountId*/,
-                const std::string& /* conversationId */,
-                std::map<std::string, std::string> /*metadatas*/) {
-                requestReceived = true;
-                cv.notify_one();
-            }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& /* conversationId */) {
-            if (accountId == bobId) {
-                conversationReady = true;
-                cv.notify_one();
-            }
-        }));
-    auto errorDetected = false;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::OnConversationError>(
-            [&](const std::string& /* accountId */,
-                const std::string& /* conversationId */,
-                int code,
-                const std::string& /* what */) {
-                if (code == 3)
-                    errorDetected = true;
-                cv.notify_one();
-            }));
-    libjami::registerSignalHandlers(confHandlers);
     auto convId = libjami::startConversation(aliceId);
     libjami::addConversationMember(aliceId, convId, bobUri);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; }));
-    memberMessageGenerated = false;
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
+
+    auto aliceMsgSize = aliceData.messages.size();
     libjami::acceptConversationRequest(bobId, convId);
     CPPUNIT_ASSERT(
-        cv.wait_for(lk, 30s, [&]() { return conversationReady && memberMessageGenerated; }));
-    auto msgSize = messageBobReceived.size();
+        cv.wait_for(lk, 30s, [&]() { return !bobData.conversationId.empty() && aliceData.messages.size() == aliceMsgSize + 1; }));
+
+    auto bobMsgSize = bobData.messages.size();
     libjami::sendMessage(aliceId, convId, "hi"s, "");
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return messageBobReceived.size() == msgSize + 1; }));
-    msgSize = messageBobReceived.size();
-    auto editedId = messageBobReceived.rbegin()->at("id");
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.messages.size() == bobMsgSize + 1; }));
+    auto editedId = bobData.messages.rbegin()->id;
+    // Should trigger MessageUpdated
+    bobMsgSize = bobData.messagesUpdated.size();
     libjami::sendMessage(aliceId, convId, "New body"s, editedId, 1);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&]() { return messageBobReceived.size() == msgSize + 1; }));
-    CPPUNIT_ASSERT(messageBobReceived.rbegin()->at("edit") == editedId);
-    CPPUNIT_ASSERT(messageBobReceived.rbegin()->at("body") == "New body");
+    CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&]() { return bobData.messagesUpdated.size() == bobMsgSize + 1; }));
+    CPPUNIT_ASSERT(bobData.messagesUpdated.rbegin()->body.at("body") == "New body");
     // Not an existing message
-    msgSize = messageBobReceived.size();
+    bobMsgSize = bobData.messagesUpdated.size();
     libjami::sendMessage(aliceId, convId, "New body"s, "invalidId", 1);
     CPPUNIT_ASSERT(
-        !cv.wait_for(lk, 10s, [&]() { return messageBobReceived.size() == msgSize + 1; }));
+        !cv.wait_for(lk, 10s, [&]() { return bobData.messagesUpdated.size() == bobMsgSize + 1; }));
     // Invalid author
     libjami::sendMessage(aliceId, convId, "New body"s, convId, 1);
     CPPUNIT_ASSERT(
-        !cv.wait_for(lk, 10s, [&]() { return messageBobReceived.size() == msgSize + 1; }));
+        !cv.wait_for(lk, 10s, [&]() { return bobData.messagesUpdated.size() == bobMsgSize + 1; }));
     // Add invalid edition
     Json::Value root;
     root["type"] = "application/edited-message";
@@ -3875,113 +2450,71 @@ ConversationTest::testMessageEdition()
     Json::StreamWriterBuilder wbuilder;
     wbuilder["commentStyle"] = "None";
     wbuilder["indentation"] = "";
-    auto repoPath = fileutils::get_data_dir() / aliceAccount->getAccountID()
+    auto repoPath = fileutils::get_data_dir() / aliceId
                     / "conversations" / convId;
     auto message = Json::writeString(wbuilder, root);
     commitInRepo(repoPath, aliceAccount, message);
-    errorDetected = false;
+    bobData.errorDetected = false;
     libjami::sendMessage(aliceId, convId, "trigger"s, "");
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return errorDetected; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.errorDetected; }));
+
 }
 
 void
 ConversationTest::testMessageReaction()
 {
     std::cout << "\nRunning test: " << __func__ << std::endl;
-
-    auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
-    auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
-    auto bobUri = bobAccount->getUsername();
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    std::vector<std::map<std::string, std::string>> messageAliceReceived;
-    bool conversationReady = false;
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::MessageReceived>(
-        [&](const std::string& accountId,
-            const std::string& /* conversationId */,
-            std::map<std::string, std::string> message) {
-            if (accountId == aliceId)
-                messageAliceReceived.emplace_back(message);
-            cv.notify_one();
-        }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& /* conversationId */) {
-            if (accountId == aliceId)
-                conversationReady = true;
-            cv.notify_one();
-        }));
-    libjami::registerSignalHandlers(confHandlers);
+    connectSignals();
     auto convId = libjami::startConversation(aliceId);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return conversationReady; }));
-    auto msgSize = messageAliceReceived.size();
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !aliceData.conversationId.empty(); }));
+    auto msgSize = aliceData.messages.size();
     libjami::sendMessage(aliceId, convId, "hi"s, "");
     CPPUNIT_ASSERT(
-        cv.wait_for(lk, 30s, [&]() { return messageAliceReceived.size() == msgSize + 1; }));
-    msgSize = messageAliceReceived.size();
-
-    auto reactId = messageAliceReceived.rbegin()->at("id");
+        cv.wait_for(lk, 30s, [&]() { return aliceData.messages.size() == msgSize + 1; }));
+    msgSize = aliceData.messages.size();
 
+    // Add reaction
+    auto reactId = aliceData.messages.rbegin()->id;
     libjami::sendMessage(aliceId, convId, "👋"s, reactId, 2);
     CPPUNIT_ASSERT(
-        cv.wait_for(lk, 10s, [&]() { return messageAliceReceived.size() == msgSize + 1; }));
-    CPPUNIT_ASSERT(messageAliceReceived.rbegin()->at("react-to") == reactId);
-    CPPUNIT_ASSERT(messageAliceReceived.rbegin()->at("body") == "👋");
+        cv.wait_for(lk, 10s, [&]() { return aliceData.reactions.size() == 1; }));
+    CPPUNIT_ASSERT(aliceData.reactions.rbegin()->at("react-to") == reactId);
+    CPPUNIT_ASSERT(aliceData.reactions.rbegin()->at("body") == "👋");
+    auto emojiId = aliceData.reactions.rbegin()->at("id");
+
+    // Remove reaction
+    libjami::sendMessage(aliceId, convId, ""s, emojiId, 1);
+    CPPUNIT_ASSERT(
+        cv.wait_for(lk, 10s, [&]() { return aliceData.reactionRemoved.size() == 1; }));
+    CPPUNIT_ASSERT(emojiId == aliceData.reactionRemoved[0]);
 }
 
 void
 ConversationTest::testLoadPartiallyRemovedConversation()
 {
     std::cout << "\nRunning test: " << __func__ << std::endl;
+    connectSignals();
 
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto bobUri = bobAccount->getUsername();
-    auto aliceUri = aliceAccount->getUsername();
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    bool requestReceived = false;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConfigurationSignal::IncomingTrustRequest>(
-            [&](const std::string& account_id,
-                const std::string& /*from*/,
-                const std::string& /*conversationId*/,
-                const std::vector<uint8_t>& /*payload*/,
-                time_t /*received*/) {
-                if (account_id == bobId)
-                    requestReceived = true;
-                cv.notify_one();
-            }));
-    std::string convId = "";
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& conversationId) {
-            if (accountId == aliceId) {
-                convId = conversationId;
-            }
-            cv.notify_one();
-        }));
-    bool conversationRemoved = false;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRemoved>(
-            [&](const std::string& accountId, const std::string&) {
-                if (accountId == aliceId)
-                    conversationRemoved = true;
-                cv.notify_one();
-            }));
-    libjami::registerSignalHandlers(confHandlers);
+
     aliceAccount->addContact(bobUri);
     aliceAccount->sendTrustRequest(bobUri, {});
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
 
     // Copy alice's conversation temporary
-    auto repoPathAlice = fileutils::get_data_dir() / aliceAccount->getAccountID() / "conversations" / convId;
-    std::filesystem::copy(repoPathAlice, fmt::format("./{}", convId), std::filesystem::copy_options::recursive);
+    auto repoPathAlice = fileutils::get_data_dir() / aliceId / "conversations" / aliceData.conversationId;
+    std::filesystem::copy(repoPathAlice, fmt::format("./{}", aliceData.conversationId), std::filesystem::copy_options::recursive);
 
     // removeContact
     aliceAccount->removeContact(bobUri, false);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return conversationRemoved; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceData.removed; }));
     std::this_thread::sleep_for(10s); // Wait for connection to close and async tasks to finish
 
     // Copy back alice's conversation
-    std::filesystem::copy(fmt::format("./{}", convId), repoPathAlice, std::filesystem::copy_options::recursive);
-    std::filesystem::remove_all(fmt::format("./{}", convId));
+    std::filesystem::copy(fmt::format("./{}", aliceData.conversationId), repoPathAlice, std::filesystem::copy_options::recursive);
+    std::filesystem::remove_all(fmt::format("./{}", aliceData.conversationId));
 
     // Reloading conversation should remove directory
     CPPUNIT_ASSERT(std::filesystem::is_directory(repoPathAlice));
diff --git a/test/unitTest/conversation/conversationMembersEvent.cpp b/test/unitTest/conversation/conversationMembersEvent.cpp
index e3730f103c44cc0be68a554ae62f54748bbcba57..8efab0997eaeda825af00499c8e63895fad10bb5 100644
--- a/test/unitTest/conversation/conversationMembersEvent.cpp
+++ b/test/unitTest/conversation/conversationMembersEvent.cpp
@@ -44,6 +44,21 @@ using namespace libjami::Account;
 namespace jami {
 namespace test {
 
+struct UserData {
+    std::string conversationId;
+    bool removed {false};
+    bool requestReceived {false};
+    bool requestRemoved {false};
+    bool errorDetected {false};
+    bool registered {false};
+    bool stopped {false};
+    bool deviceAnnounced {false};
+    bool contactRemoved {false};
+    std::string payloadTrustRequest;
+    std::vector<libjami::SwarmMessage> messages;
+    std::vector<libjami::SwarmMessage> messagesUpdated;
+};
+
 class ConversationMembersEventTest : public CppUnit::TestFixture
 {
 public:
@@ -90,15 +105,22 @@ public:
     void testBanHostWhileHosting();
     void testRemoveContactTwice();
     void testAddContactTwice();
+    void testBanFromNewDevice();
 
     std::string aliceId;
+    UserData aliceData;
     std::string bobId;
+    UserData bobData;
     std::string bob2Id;
+    UserData bob2Data;
     std::string carlaId;
+    UserData carlaData;
     std::mutex mtx;
     std::unique_lock<std::mutex> lk {mtx};
     std::condition_variable cv;
 
+    void connectSignals();
+
 private:
     CPPUNIT_TEST_SUITE(ConversationMembersEventTest);
     CPPUNIT_TEST(testRemoveConversationNoMember);
@@ -136,6 +158,7 @@ private:
     CPPUNIT_TEST(testBanHostWhileHosting);
     CPPUNIT_TEST(testRemoveContactTwice);
     CPPUNIT_TEST(testAddContactTwice);
+    CPPUNIT_TEST(testBanFromNewDevice);
     CPPUNIT_TEST_SUITE_END();
 };
 
@@ -145,6 +168,8 @@ CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(ConversationMembersEventTest,
 void
 ConversationMembersEventTest::setUp()
 {
+    connectSignals();
+
     // Init daemon
     libjami::init(
         libjami::InitFlag(libjami::LIBJAMI_FLAG_DEBUG | libjami::LIBJAMI_FLAG_CONSOLE_LOG));
@@ -156,6 +181,11 @@ ConversationMembersEventTest::setUp()
     bobId = actors["bob"];
     carlaId = actors["carla"];
 
+    aliceData = {};
+    bobData = {};
+    bob2Data = {};
+    carlaData = {};
+
     Manager::instance().sendRegister(carlaId, false);
     wait_for_announcement_of({aliceId, bobId});
 }
@@ -163,6 +193,8 @@ ConversationMembersEventTest::setUp()
 void
 ConversationMembersEventTest::tearDown()
 {
+    connectSignals();
+
     auto bobArchive = std::filesystem::current_path().string() + "/bob.gz";
     std::remove(bobArchive.c_str());
     if (bob2Id.empty()) {
@@ -172,6 +204,178 @@ ConversationMembersEventTest::tearDown()
     }
 }
 
+void
+ConversationMembersEventTest::connectSignals()
+{
+    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
+    confHandlers.insert(
+        libjami::exportable_callback<libjami::ConfigurationSignal::VolatileDetailsChanged>(
+            [&](const std::string& accountId, const std::map<std::string, std::string>&) {
+                if (accountId == aliceId) {
+                    auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
+                    auto details = aliceAccount->getVolatileAccountDetails();
+                    auto daemonStatus = details[libjami::Account::ConfProperties::Registration::STATUS];
+                    if (daemonStatus == "REGISTERED") {
+                        aliceData.registered = true;
+                    } else if (daemonStatus == "UNREGISTERED") {
+                        aliceData.stopped = true;
+                    }
+                    auto deviceAnnounced = details[libjami::Account::VolatileProperties::DEVICE_ANNOUNCED];
+                    aliceData.deviceAnnounced = deviceAnnounced == "true";
+                } else if (accountId == bobId) {
+                    auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
+                    auto details = bobAccount->getVolatileAccountDetails();
+                    auto daemonStatus = details[libjami::Account::ConfProperties::Registration::STATUS];
+                    if (daemonStatus == "REGISTERED") {
+                        bobData.registered = true;
+                    } else if (daemonStatus == "UNREGISTERED") {
+                        bobData.stopped = true;
+                    }
+                    auto deviceAnnounced = details[libjami::Account::VolatileProperties::DEVICE_ANNOUNCED];
+                    bobData.deviceAnnounced = deviceAnnounced == "true";
+                } else if (accountId == bob2Id) {
+                    auto bob2Account = Manager::instance().getAccount<JamiAccount>(bob2Id);
+                    auto details = bob2Account->getVolatileAccountDetails();
+                    auto daemonStatus = details[libjami::Account::ConfProperties::Registration::STATUS];
+                    if (daemonStatus == "REGISTERED") {
+                        bob2Data.registered = true;
+                    } else if (daemonStatus == "UNREGISTERED") {
+                        bob2Data.stopped = true;
+                    }
+                    auto deviceAnnounced = details[libjami::Account::VolatileProperties::DEVICE_ANNOUNCED];
+                    bob2Data.deviceAnnounced = deviceAnnounced == "true";
+                } else if (accountId == carlaId) {
+                    auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
+                    auto details = carlaAccount->getVolatileAccountDetails();
+                    auto daemonStatus = details[libjami::Account::ConfProperties::Registration::STATUS];
+                    if (daemonStatus == "REGISTERED") {
+                        carlaData.registered = true;
+                    } else if (daemonStatus == "UNREGISTERED") {
+                        carlaData.stopped = true;
+                    }
+                    auto deviceAnnounced = details[libjami::Account::VolatileProperties::DEVICE_ANNOUNCED];
+                    carlaData.deviceAnnounced = deviceAnnounced == "true";
+                }
+                cv.notify_one();
+            }));
+    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
+        [&](const std::string& accountId, const std::string& conversationId) {
+            if (accountId == aliceId) {
+                aliceData.conversationId = conversationId;
+            } else if (accountId == bobId) {
+                bobData.conversationId = conversationId;
+            } else if (accountId == bob2Id) {
+                bob2Data.conversationId = conversationId;
+            } else if (accountId == carlaId) {
+                carlaData.conversationId = conversationId;
+            }
+            cv.notify_one();
+        }));
+    confHandlers.insert(
+        libjami::exportable_callback<libjami::ConfigurationSignal::IncomingTrustRequest>(
+            [&](const std::string& account_id,
+                const std::string& /*from*/,
+                const std::string& /*conversationId*/,
+                const std::vector<uint8_t>& payload,
+                time_t /*received*/) {
+                auto payloadStr = std::string(payload.data(), payload.data() + payload.size());
+                if (account_id == aliceId)
+                    aliceData.payloadTrustRequest = payloadStr;
+                else if (account_id == bobId)
+                    bobData.payloadTrustRequest = payloadStr;
+                cv.notify_one();
+            }));
+    confHandlers.insert(
+        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
+            [&](const std::string& accountId,
+                const std::string& /* conversationId */,
+                std::map<std::string, std::string> /*metadatas*/) {
+                if (accountId == aliceId) {
+                    aliceData.requestReceived = true;
+                } else if (accountId == bobId) {
+                    bobData.requestReceived = true;
+                } else if (accountId == bob2Id) {
+                    bob2Data.requestReceived = true;
+                } else if (accountId == carlaId) {
+                    carlaData.requestReceived = true;
+                }
+                cv.notify_one();
+            }));
+    confHandlers.insert(
+        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestDeclined>(
+            [&](const std::string& accountId, const std::string&) {
+                if (accountId == bobId) {
+                    bobData.requestRemoved = true;
+                } else if (accountId == bob2Id) {
+                    bob2Data.requestRemoved = true;
+                }
+                cv.notify_one();
+            }));
+    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::SwarmMessageReceived>(
+        [&](const std::string& accountId,
+            const std::string& /* conversationId */,
+            libjami::SwarmMessage message) {
+            if (accountId == aliceId) {
+                aliceData.messages.emplace_back(message);
+            } else if (accountId == bobId) {
+                bobData.messages.emplace_back(message);
+            } else if (accountId == bob2Id) {
+                bob2Data.messages.emplace_back(message);
+            } else if (accountId == carlaId) {
+                carlaData.messages.emplace_back(message);
+            }
+            cv.notify_one();
+        }));
+    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::SwarmMessageUpdated>(
+        [&](const std::string& accountId,
+            const std::string& /* conversationId */,
+            libjami::SwarmMessage message) {
+            if (accountId == aliceId) {
+                aliceData.messagesUpdated.emplace_back(message);
+            } else if (accountId == bobId) {
+                bobData.messagesUpdated.emplace_back(message);
+            } else if (accountId == carlaId) {
+                carlaData.messagesUpdated.emplace_back(message);
+            }
+            cv.notify_one();
+        }));
+    confHandlers.insert(
+        libjami::exportable_callback<libjami::ConversationSignal::OnConversationError>(
+            [&](const std::string& accountId,
+                const std::string& /* conversationId */,
+                int /*code*/,
+                const std::string& /* what */) {
+                if (accountId == aliceId)
+                    aliceData.errorDetected = true;
+                else if (accountId == bobId)
+                    bobData.errorDetected = true;
+                else if (accountId == carlaId)
+                    carlaData.errorDetected = true;
+                cv.notify_one();
+            }));
+    confHandlers.insert(
+        libjami::exportable_callback<libjami::ConversationSignal::ConversationRemoved>(
+            [&](const std::string& accountId, const std::string&) {
+                if (accountId == aliceId)
+                    aliceData.removed = true;
+                else if (accountId == bobId)
+                    bobData.removed = true;
+                else if (accountId == bob2Id)
+                    bob2Data.removed = true;
+                cv.notify_one();
+            }));
+    confHandlers.insert(libjami::exportable_callback<libjami::ConfigurationSignal::ContactRemoved>(
+        [&](const std::string& accountId, const std::string&, bool) {
+            if (accountId == bobId) {
+                bobData.contactRemoved = true;
+            } else if (accountId == bob2Id) {
+                bob2Data.contactRemoved = true;
+            }
+            cv.notify_one();
+        }));
+    libjami::registerSignalHandlers(confHandlers);
+}
+
 void
 ConversationMembersEventTest::generateFakeInvite(std::shared_ptr<JamiAccount> account,
                                                  const std::string& convId,
@@ -221,28 +425,16 @@ ConversationMembersEventTest::generateFakeInvite(std::shared_ptr<JamiAccount> ac
 void
 ConversationMembersEventTest::testRemoveConversationNoMember()
 {
-    auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
-    auto uri = aliceAccount->getUsername();
-
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    bool conversationReady = false;
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& /* conversationId */) {
-            if (accountId == aliceId) {
-                conversationReady = true;
-                cv.notify_one();
-            }
-        }));
-    libjami::registerSignalHandlers(confHandlers);
+    connectSignals();
 
     // Start conversation
     auto convId = libjami::startConversation(aliceId);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return conversationReady; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !aliceData.conversationId.empty(); }));
 
     // Assert that repository exists
-    auto repoPath = fileutils::get_data_dir() / aliceAccount->getAccountID()
+    auto repoPath = fileutils::get_data_dir() / aliceId
                     / "conversations" / convId;
-    auto dataPath = fileutils::get_data_dir() / aliceAccount->getAccountID()
+    auto dataPath = fileutils::get_data_dir() / aliceId
                     / "conversation_data" / convId;
     CPPUNIT_ASSERT(std::filesystem::is_directory(repoPath));
     CPPUNIT_ASSERT(std::filesystem::is_directory(dataPath));
@@ -260,73 +452,35 @@ ConversationMembersEventTest::testRemoveConversationNoMember()
 void
 ConversationMembersEventTest::testRemoveConversationWithMember()
 {
-    auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
+    connectSignals();
+
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto bobUri = bobAccount->getUsername();
     auto convId = libjami::startConversation(aliceId);
 
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    bool conversationReady = false, requestReceived = false, memberMessageGenerated = false,
-         bobSeeAliceRemoved = false;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
-            [&](const std::string& /*accountId*/,
-                const std::string& /* conversationId */,
-                std::map<std::string, std::string> /*metadatas*/) {
-                requestReceived = true;
-                cv.notify_one();
-            }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& /* conversationId */) {
-            if (accountId == bobId) {
-                conversationReady = true;
-                cv.notify_one();
-            }
-        }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::MessageReceived>(
-        [&](const std::string& accountId,
-            const std::string& conversationId,
-            std::map<std::string, std::string> message) {
-            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
-                       && itFind->second == "member") {
-                bobSeeAliceRemoved = true;
-                cv.notify_one();
-            }
-        }));
-    libjami::registerSignalHandlers(confHandlers);
-
     libjami::addConversationMember(aliceId, convId, bobUri);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return memberMessageGenerated; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
 
     // Assert that repository exists
-    auto repoPath = fileutils::get_data_dir() / aliceAccount->getAccountID()
+    auto repoPath = fileutils::get_data_dir() / aliceId
                     / "conversations" / convId;
     CPPUNIT_ASSERT(std::filesystem::is_directory(repoPath));
     // Check created files
     auto bobInvitedFile = repoPath / "invited" / bobUri;
     CPPUNIT_ASSERT(std::filesystem::is_regular_file(bobInvitedFile));
 
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; }));
-    memberMessageGenerated = false;
+    auto aliceMsgSize = aliceData.messages.size();
     libjami::acceptConversationRequest(bobId, convId);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return conversationReady; }));
-    auto clonedPath = fileutils::get_data_dir() / bobAccount->getAccountID()
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 1 == aliceData.messages.size(); }));
+    auto clonedPath = fileutils::get_data_dir() / bobId
                       / "conversations" / convId;
-    CPPUNIT_ASSERT(std::filesystem::is_directory(clonedPath));
     bobInvitedFile = clonedPath / "invited" / bobUri;
     CPPUNIT_ASSERT(!std::filesystem::is_regular_file(bobInvitedFile));
     // Remove conversation from alice once member confirmed
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return memberMessageGenerated; }));
 
-    bobSeeAliceRemoved = false;
+    auto bobMsgSize = bobData.messages.size();
     libjami::removeConversation(aliceId, convId);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobSeeAliceRemoved; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobMsgSize + 1 == bobData.messages.size() && bobData.messages.rbegin()->type == "member"; }));
     std::this_thread::sleep_for(3s);
     CPPUNIT_ASSERT(!std::filesystem::is_directory(repoPath));
 }
@@ -334,50 +488,24 @@ ConversationMembersEventTest::testRemoveConversationWithMember()
 void
 ConversationMembersEventTest::testAddMember()
 {
-    auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
+    connectSignals();
+
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto bobUri = bobAccount->getUsername();
     auto convId = libjami::startConversation(aliceId);
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    bool conversationReady = false, requestReceived = false, memberMessageGenerated = false;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
-            [&](const std::string& /*accountId*/,
-                const std::string& /* conversationId */,
-                std::map<std::string, std::string> /*metadatas*/) {
-                requestReceived = true;
-                cv.notify_one();
-            }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& /* conversationId */) {
-            if (accountId == bobId) {
-                conversationReady = true;
-                cv.notify_one();
-            }
-        }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::MessageReceived>(
-        [&](const std::string& accountId,
-            const std::string& conversationId,
-            std::map<std::string, std::string> message) {
-            if (accountId == aliceId && conversationId == convId && message["type"] == "member") {
-                memberMessageGenerated = true;
-                cv.notify_one();
-            }
-        }));
-    libjami::registerSignalHandlers(confHandlers);
+
     libjami::addConversationMember(aliceId, convId, bobUri);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return memberMessageGenerated; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
     // Assert that repository exists
-    auto repoPath = fileutils::get_data_dir() / aliceAccount->getAccountID()
+    auto repoPath = fileutils::get_data_dir() / aliceId
                     / "conversations" / convId;
     CPPUNIT_ASSERT(std::filesystem::is_directory(repoPath));
     // Check created files
     auto bobInvited = repoPath / "invited" / bobUri;
     CPPUNIT_ASSERT(std::filesystem::is_regular_file(bobInvited));
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; }));
     libjami::acceptConversationRequest(bobId, convId);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return conversationReady; }));
-    auto clonedPath = fileutils::get_data_dir() / bobAccount->getAccountID()
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !bobData.conversationId.empty(); }));
+    auto clonedPath = fileutils::get_data_dir() / bobId
                       / "conversations" / convId;
     CPPUNIT_ASSERT(std::filesystem::is_directory(clonedPath));
     bobInvited = clonedPath / "invited" / bobUri;
@@ -389,38 +517,13 @@ ConversationMembersEventTest::testAddMember()
 void
 ConversationMembersEventTest::testMemberAddedNoBadFile()
 {
+    connectSignals();
+
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto bobUri = bobAccount->getUsername();
     auto convId = libjami::startConversation(aliceId);
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    bool conversationReady = false, requestReceived = false, errorDetected = false;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
-            [&](const std::string& /*accountId*/,
-                const std::string& /* conversationId */,
-                std::map<std::string, std::string> /*metadatas*/) {
-                requestReceived = true;
-                cv.notify_one();
-            }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& /* conversationId */) {
-            if (accountId == bobId) {
-                conversationReady = true;
-                cv.notify_one();
-            }
-        }));
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::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();
-            }));
-    libjami::registerSignalHandlers(confHandlers);
+
     addFile(aliceAccount, convId, "BADFILE");
     // NOTE: Add certificate because no DHT lookup
     aliceAccount->certStore().pinCertificate(bobAccount->identity().second);
@@ -430,51 +533,31 @@ ConversationMembersEventTest::testMemberAddedNoBadFile()
                                   std::string(bobAccount->currentDeviceId()),
                                   {{"application/invite+json",
                                     "{\"conversationId\":\"" + convId + "\"}"}});
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; }));
-    errorDetected = false;
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
     libjami::acceptConversationRequest(bobId, convId);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return errorDetected; }));
-    libjami::unregisterSignalHandlers();
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.errorDetected; }));
 }
 
 void
 ConversationMembersEventTest::testAddOfflineMemberThenConnects()
 {
+    connectSignals();
+
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
     auto carlaUri = carlaAccount->getUsername();
     aliceAccount->trackBuddyPresence(carlaUri, true);
     auto convId = libjami::startConversation(aliceId);
 
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    bool conversationReady = false, requestReceived = false;
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& /* conversationId */) {
-            if (accountId == carlaId) {
-                conversationReady = true;
-                cv.notify_one();
-            }
-        }));
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
-            [&](const std::string& /*accountId*/,
-                const std::string& /* conversationId */,
-                std::map<std::string, std::string> /*metadatas*/) {
-                requestReceived = true;
-                cv.notify_one();
-            }));
-    libjami::registerSignalHandlers(confHandlers);
-
     libjami::addConversationMember(aliceId, convId, carlaUri);
     Manager::instance().sendRegister(carlaId, true);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&] { return requestReceived; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&] { return carlaData.requestReceived; }));
 
     libjami::acceptConversationRequest(carlaId, convId);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return conversationReady; }));
-    auto clonedPath = fileutils::get_data_dir() / carlaAccount->getAccountID()
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !carlaData.conversationId.empty(); }));
+    auto clonedPath = fileutils::get_data_dir() / carlaId
                       / "conversations" / convId;
     CPPUNIT_ASSERT(std::filesystem::is_directory(clonedPath));
-    libjami::unregisterSignalHandlers();
 }
 
 void
@@ -521,48 +604,19 @@ ConversationMembersEventTest::testAddAcceptOfflineThenConnects()
 void
 ConversationMembersEventTest::testGetMembers()
 {
+    connectSignals();
+
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto bobUri = bobAccount->getUsername();
-    auto aliceUri = aliceAccount->getUsername();
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    auto messageReceived = false;
-    bool requestReceived = false;
-    bool conversationReady = false;
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::MessageReceived>(
-        [&](const std::string& accountId,
-            const std::string& /* conversationId */,
-            std::map<std::string, std::string> /*message*/) {
-            if (accountId == aliceId) {
-                messageReceived = true;
-                cv.notify_one();
-            }
-        }));
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
-            [&](const std::string& /*accountId*/,
-                const std::string& /* conversationId */,
-                std::map<std::string, std::string> /*metadatas*/) {
-                requestReceived = true;
-                cv.notify_one();
-            }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& /* conversationId */) {
-            if (accountId == bobId) {
-                conversationReady = true;
-                cv.notify_one();
-            }
-        }));
-    libjami::registerSignalHandlers(confHandlers);
     // Start a conversation and add member
     auto convId = libjami::startConversation(aliceId);
 
-    messageReceived = false;
     libjami::addConversationMember(aliceId, convId, bobUri);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&]() { return messageReceived; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&]() { return bobData.requestReceived; }));
 
     // Assert that repository exists
-    auto repoPath = fileutils::get_data_dir() / aliceAccount->getAccountID()
+    auto repoPath = fileutils::get_data_dir() / aliceId
                     / "conversations" / convId;
     CPPUNIT_ASSERT(std::filesystem::is_directory(repoPath));
 
@@ -573,74 +627,39 @@ ConversationMembersEventTest::testGetMembers()
     CPPUNIT_ASSERT(members[1]["uri"] == bobUri);
     CPPUNIT_ASSERT(members[1]["role"] == "invited");
 
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; }));
-    messageReceived = false;
+    auto aliceMsgSize = aliceData.messages.size();
     libjami::acceptConversationRequest(bobId, convId);
-    cv.wait_for(lk, 30s, [&]() { return conversationReady; });
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !bobData.conversationId.empty(); }));
     members = libjami::getConversationMembers(bobId, convId);
     CPPUNIT_ASSERT(members.size() == 2);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&]() { return messageReceived; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&]() { return aliceMsgSize + 1 == aliceData.messages.size(); }));
     members = libjami::getConversationMembers(aliceId, convId);
     CPPUNIT_ASSERT(members.size() == 2);
     CPPUNIT_ASSERT(members[0]["uri"] == aliceAccount->getUsername());
     CPPUNIT_ASSERT(members[0]["role"] == "admin");
     CPPUNIT_ASSERT(members[1]["uri"] == bobUri);
     CPPUNIT_ASSERT(members[1]["role"] == "member");
-    libjami::unregisterSignalHandlers();
 }
 
 void
 ConversationMembersEventTest::testRemoveMember()
 {
-    auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
+    connectSignals();
+
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
-    auto aliceUri = aliceAccount->getUsername();
     auto bobUri = bobAccount->getUsername();
     auto convId = libjami::startConversation(aliceId);
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    bool conversationReady = false, requestReceived = false, memberMessageGenerated = false,
-         voteMessageGenerated = false;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
-            [&](const std::string& /*accountId*/,
-                const std::string& /* conversationId */,
-                std::map<std::string, std::string> /*metadatas*/) {
-                requestReceived = true;
-                cv.notify_one();
-            }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& conversationId) {
-            if (accountId == bobId && conversationId == convId) {
-                conversationReady = true;
-                cv.notify_one();
-            }
-        }));
-    confHandlers.insert(libjami::exportable_callback<libjami::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;
-                cv.notify_one();
-            } else if (accountId == aliceId && conversationId == convId
-                       && message["type"] == "member") {
-                memberMessageGenerated = true;
-                cv.notify_one();
-            }
-        }));
-    libjami::registerSignalHandlers(confHandlers);
+
     libjami::addConversationMember(aliceId, convId, bobUri);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; }));
-    memberMessageGenerated = false;
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
+    auto aliceMsgSize = aliceData.messages.size();
     libjami::acceptConversationRequest(bobId, convId);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return memberMessageGenerated; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 1 == aliceData.messages.size(); }));
 
     // Now check that alice, has the only admin, can remove bob
-    memberMessageGenerated = false;
-    voteMessageGenerated = false;
     libjami::removeConversationMember(aliceId, convId, bobUri);
     CPPUNIT_ASSERT(
-        cv.wait_for(lk, 30s, [&]() { return memberMessageGenerated && voteMessageGenerated; }));
+        cv.wait_for(lk, 30s, [&]() { return aliceData.messages.size() == aliceMsgSize + 3 /* vote + ban */; }));
     auto members = libjami::getConversationMembers(aliceId, convId);
     auto bobBanned = false;
     for (auto& member : members) {
@@ -648,146 +667,65 @@ ConversationMembersEventTest::testRemoveMember()
             bobBanned = member["role"] == "banned";
     }
     CPPUNIT_ASSERT(bobBanned);
-    libjami::unregisterSignalHandlers();
 }
 
 void
 ConversationMembersEventTest::testRemovedMemberDoesNotReceiveMessage()
 {
-    auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
+    connectSignals();
+
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
-    auto aliceUri = aliceAccount->getUsername();
     auto bobUri = bobAccount->getUsername();
     auto convId = libjami::startConversation(aliceId);
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    bool conversationReady = false, requestReceived = false;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
-            [&](const std::string& /*accountId*/,
-                const std::string& /* conversationId */,
-                std::map<std::string, std::string> /*metadatas*/) {
-                requestReceived = true;
-                cv.notify_one();
-            }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& conversationId) {
-            if (accountId == bobId && conversationId == convId) {
-                conversationReady = true;
-                cv.notify_one();
-            }
-        }));
-    bool memberMessageGenerated = false, voteMessageGenerated = false, messageBobReceived = false;
-    confHandlers.insert(libjami::exportable_callback<libjami::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();
-        }));
-    libjami::registerSignalHandlers(confHandlers);
+
     libjami::addConversationMember(aliceId, convId, bobUri);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; }));
-    memberMessageGenerated = false;
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
+    auto aliceMsgSize = aliceData.messages.size();
     libjami::acceptConversationRequest(bobId, convId);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return memberMessageGenerated; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 1 == aliceData.messages.size(); }));
 
     // Now check that alice, has the only admin, can remove bob
-    memberMessageGenerated = false;
-    voteMessageGenerated = false;
     libjami::removeConversationMember(aliceId, convId, bobUri);
     CPPUNIT_ASSERT(
-        cv.wait_for(lk, 30s, [&]() { return memberMessageGenerated && voteMessageGenerated; }));
+        cv.wait_for(lk, 30s, [&]() { return aliceData.messages.size() == aliceMsgSize + 3 /* vote + ban */; }));
 
     // Now, bob is banned so they shoud not receive any message
-    messageBobReceived = false;
+    auto bobMsgSize = bobData.messages.size();
     libjami::sendMessage(aliceId, convId, "hi"s, "");
-    CPPUNIT_ASSERT(!cv.wait_for(lk, 10s, [&]() { return messageBobReceived; }));
-    libjami::unregisterSignalHandlers();
+    CPPUNIT_ASSERT(!cv.wait_for(lk, 10s, [&]() { return bobMsgSize + 1 == bobData.messages.size(); }));
 }
 
 void
 ConversationMembersEventTest::testRemoveInvitedMember()
 {
-    auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
+    connectSignals();
+
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
-    auto aliceUri = aliceAccount->getUsername();
     auto bobUri = bobAccount->getUsername();
     auto carlaUri = carlaAccount->getUsername();
     auto convId = libjami::startConversation(aliceId);
 
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    bool conversationReady = false, requestReceived = false;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
-            [&](const std::string& /*accountId*/,
-                const std::string& /* conversationId */,
-                std::map<std::string, std::string> /*metadatas*/) {
-                requestReceived = true;
-                cv.notify_one();
-            }));
-    bool carlaConnected = false;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConfigurationSignal::VolatileDetailsChanged>(
-            [&](const std::string&, const std::map<std::string, std::string>&) {
-                auto details = carlaAccount->getVolatileAccountDetails();
-                auto deviceAnnounced
-                    = details[libjami::Account::VolatileProperties::DEVICE_ANNOUNCED];
-                if (deviceAnnounced == "true") {
-                    carlaConnected = true;
-                    cv.notify_one();
-                }
-            }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& conversationId) {
-            if (accountId == bobId && conversationId == convId) {
-                conversationReady = true;
-                cv.notify_one();
-            }
-        }));
-    bool voteMessageGenerated = false, memberMessageGenerated = false, carlaMessageReceived;
-    confHandlers.insert(libjami::exportable_callback<libjami::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 == carlaId && message["type"] == "text/plain"
-                     && message["body"] == "hi")
-                carlaMessageReceived = true;
-            cv.notify_one();
-        }));
-    libjami::registerSignalHandlers(confHandlers);
-
     // Add carla
     Manager::instance().sendRegister(carlaId, true);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return carlaConnected; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return carlaData.deviceAnnounced; }));
     libjami::addConversationMember(aliceId, convId, carlaUri);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; }));
-    memberMessageGenerated = false;
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return carlaData.requestReceived; }));
+    auto aliceMsgSize = aliceData.messages.size();
     libjami::acceptConversationRequest(carlaId, convId);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return memberMessageGenerated; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 1 == aliceData.messages.size(); }));
 
     // Invite Alice
-    requestReceived = false;
     libjami::addConversationMember(aliceId, convId, bobUri);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
     auto members = libjami::getConversationMembers(aliceId, convId);
     CPPUNIT_ASSERT(members.size() == 3);
 
     // Now check that alice, has the only admin, can remove bob
-    memberMessageGenerated = false;
-    voteMessageGenerated = false;
+    aliceMsgSize = aliceData.messages.size();
     libjami::removeConversationMember(aliceId, convId, bobUri);
     CPPUNIT_ASSERT(
-        cv.wait_for(lk, 30s, [&]() { return memberMessageGenerated && voteMessageGenerated; }));
+        cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 2 == aliceData.messages.size(); }));
     members = libjami::getConversationMembers(aliceId, convId);
     auto bobBanned = false;
     for (auto& member : members) {
@@ -797,14 +735,16 @@ ConversationMembersEventTest::testRemoveInvitedMember()
     CPPUNIT_ASSERT(bobBanned);
 
     // Check that Carla is still able to sync
+    auto carlaMsgSize = carlaData.messages.size();
     libjami::sendMessage(aliceId, convId, "hi"s, "");
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return carlaMessageReceived; }));
-    libjami::unregisterSignalHandlers();
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return carlaMsgSize + 1 == carlaData.messages.size(); }));
 }
 
 void
 ConversationMembersEventTest::testMemberBanNoBadFile()
 {
+    connectSignals();
+
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto aliceUri = aliceAccount->getUsername();
@@ -814,190 +754,69 @@ ConversationMembersEventTest::testMemberBanNoBadFile()
     auto carlaUri = carlaAccount->getUsername();
     aliceAccount->trackBuddyPresence(carlaUri, true);
 
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    bool conversationReady = false, requestReceived = false, memberMessageGenerated = false,
-         voteMessageGenerated = false, messageBobReceived = false, errorDetected = false,
-         carlaConnected = false;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
-            [&](const std::string& /*accountId*/,
-                const std::string& /* conversationId */,
-                std::map<std::string, std::string> /*metadatas*/) {
-                requestReceived = true;
-                cv.notify_one();
-            }));
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConfigurationSignal::VolatileDetailsChanged>(
-            [&](const std::string&, const std::map<std::string, std::string>&) {
-                auto details = carlaAccount->getVolatileAccountDetails();
-                auto deviceAnnounced
-                    = details[libjami::Account::VolatileProperties::DEVICE_ANNOUNCED];
-                if (deviceAnnounced == "true") {
-                    carlaConnected = true;
-                    cv.notify_one();
-                }
-            }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& conversationId) {
-            if (accountId == bobId && conversationId == convId) {
-                conversationReady = true;
-                cv.notify_one();
-            }
-        }));
-    confHandlers.insert(libjami::exportable_callback<libjami::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(
-        libjami::exportable_callback<libjami::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();
-            }));
-    libjami::registerSignalHandlers(confHandlers);
     Manager::instance().sendRegister(carlaId, true);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&] { return carlaConnected; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&] { return carlaData.deviceAnnounced; }));
     libjami::addConversationMember(aliceId, convId, bobUri);
     CPPUNIT_ASSERT(
-        cv.wait_for(lk, 30s, [&]() { return requestReceived && memberMessageGenerated; }));
-    memberMessageGenerated = false;
+        cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
+    auto aliceMsgSize = aliceData.messages.size();
     libjami::acceptConversationRequest(bobId, convId);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return memberMessageGenerated; }));
-    requestReceived = false;
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 1 == aliceData.messages.size(); }));
     libjami::addConversationMember(aliceId, convId, carlaUri);
     CPPUNIT_ASSERT(
-        cv.wait_for(lk, 30s, [&]() { return requestReceived && memberMessageGenerated; }));
-    memberMessageGenerated = false;
-    messageBobReceived = false;
+        cv.wait_for(lk, 30s, [&]() { return carlaData.requestReceived; }));
+    aliceMsgSize = aliceData.messages.size();
+    auto bobMsgSize = bobData.messages.size();
     libjami::acceptConversationRequest(carlaId, convId);
-    CPPUNIT_ASSERT(
-        cv.wait_for(lk, 30s, [&]() { return memberMessageGenerated && messageBobReceived; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 1 == aliceData.messages.size() && bobMsgSize + 1 == bobData.messages.size(); }));
 
-    memberMessageGenerated = false;
-    voteMessageGenerated = false;
     addFile(aliceAccount, convId, "BADFILE");
     libjami::removeConversationMember(aliceId, convId, carlaUri);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return errorDetected; }));
-    libjami::unregisterSignalHandlers();
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.errorDetected; }));
 }
 
 void
 ConversationMembersEventTest::testMemberTryToRemoveAdmin()
 {
+    connectSignals();
+
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto aliceUri = aliceAccount->getUsername();
     auto bobUri = bobAccount->getUsername();
     auto convId = libjami::startConversation(aliceId);
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    bool conversationReady = false, requestReceived = false, memberMessageGenerated = false;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
-            [&](const std::string& /*accountId*/,
-                const std::string& /* conversationId */,
-                std::map<std::string, std::string> /*metadatas*/) {
-                requestReceived = true;
-                cv.notify_one();
-            }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& conversationId) {
-            if (accountId == bobId && conversationId == convId) {
-                conversationReady = true;
-                cv.notify_one();
-            }
-        }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::MessageReceived>(
-        [&](const std::string& accountId,
-            const std::string& conversationId,
-            std::map<std::string, std::string> message) {
-            if (accountId == aliceId && conversationId == convId && message["type"] == "member")
-                memberMessageGenerated = true;
-            cv.notify_one();
-        }));
-    libjami::registerSignalHandlers(confHandlers);
     libjami::addConversationMember(aliceId, convId, bobUri);
     CPPUNIT_ASSERT(
-        cv.wait_for(lk, 30s, [&]() { return requestReceived && memberMessageGenerated; }));
-    memberMessageGenerated = false;
+        cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
+    auto aliceMsgSize = aliceData.messages.size();
     libjami::acceptConversationRequest(bobId, convId);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return memberMessageGenerated; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 1 == aliceData.messages.size(); }));
 
     // Now check that alice, has the only admin, can remove bob
-    memberMessageGenerated = false;
     libjami::removeConversationMember(bobId, convId, aliceUri);
     auto members = libjami::getConversationMembers(aliceId, convId);
-    CPPUNIT_ASSERT(members.size() == 2 && !memberMessageGenerated);
-    libjami::unregisterSignalHandlers();
+    CPPUNIT_ASSERT(members.size() == 2 && aliceMsgSize + 2 != aliceData.messages.size());
 }
 
 void
 ConversationMembersEventTest::testBannedMemberCannotSendMessage()
 {
-    auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
+    connectSignals();
+
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
-    auto aliceUri = aliceAccount->getUsername();
     auto bobUri = bobAccount->getUsername();
     auto convId = libjami::startConversation(aliceId);
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    bool conversationReady = false, requestReceived = false, memberMessageGenerated = false,
-         voteMessageGenerated = false, aliceMessageReceived = false;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
-            [&](const std::string& /*accountId*/,
-                const std::string& /* conversationId */,
-                std::map<std::string, std::string> /*metadatas*/) {
-                requestReceived = true;
-                cv.notify_one();
-            }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& conversationId) {
-            if (accountId == bobId && conversationId == convId) {
-                conversationReady = true;
-                cv.notify_one();
-            }
-        }));
-    confHandlers.insert(libjami::exportable_callback<libjami::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 == aliceId && conversationId == convId
-                       && message["type"] == "text/plain") {
-                aliceMessageReceived = true;
-            }
-            cv.notify_one();
-        }));
-    libjami::registerSignalHandlers(confHandlers);
+
     libjami::addConversationMember(aliceId, convId, bobUri);
     CPPUNIT_ASSERT(
-        cv.wait_for(lk, 30s, [&]() { return requestReceived && memberMessageGenerated; }));
-    memberMessageGenerated = false;
+        cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
+    auto aliceMsgSize = aliceData.messages.size();
     libjami::acceptConversationRequest(bobId, convId);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return memberMessageGenerated; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 1 == aliceData.messages.size(); }));
 
-    memberMessageGenerated = false;
-    voteMessageGenerated = false;
     libjami::removeConversationMember(aliceId, convId, bobUri);
     CPPUNIT_ASSERT(
-        cv.wait_for(lk, 30s, [&]() { return memberMessageGenerated && voteMessageGenerated; }));
+        cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 3 == aliceData.messages.size(); }));
     auto members = libjami::getConversationMembers(aliceId, convId);
 
     auto bobBanned = false;
@@ -1008,65 +827,33 @@ ConversationMembersEventTest::testBannedMemberCannotSendMessage()
     CPPUNIT_ASSERT(bobBanned);
 
     // Now check that alice doesn't receive a message from Bob
-    aliceMessageReceived = false;
     libjami::sendMessage(bobId, convId, "hi"s, "");
-    CPPUNIT_ASSERT(!cv.wait_for(lk, 30s, [&]() { return aliceMessageReceived; }));
+    CPPUNIT_ASSERT(!cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 4 == aliceData.messages.size(); }));
     libjami::unregisterSignalHandlers();
 }
 
 void
 ConversationMembersEventTest::testAdminCanReAddMember()
 {
+    connectSignals();
+
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto aliceUri = aliceAccount->getUsername();
     auto bobUri = bobAccount->getUsername();
     auto convId = libjami::startConversation(aliceId);
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    bool conversationReady = false, requestReceived = false, memberMessageGenerated = false,
-         voteMessageGenerated = false;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
-            [&](const std::string& /*accountId*/,
-                const std::string& /* conversationId */,
-                std::map<std::string, std::string> /*metadatas*/) {
-                requestReceived = true;
-                cv.notify_one();
-            }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& conversationId) {
-            if (accountId == bobId && conversationId == convId) {
-                conversationReady = true;
-                cv.notify_one();
-            }
-        }));
-    confHandlers.insert(libjami::exportable_callback<libjami::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;
-                cv.notify_one();
-            } else if (accountId == aliceId && conversationId == convId
-                       && message["type"] == "member") {
-                memberMessageGenerated = true;
-                cv.notify_one();
-            }
-        }));
-    libjami::registerSignalHandlers(confHandlers);
+
     libjami::addConversationMember(aliceId, convId, bobUri);
     CPPUNIT_ASSERT(
-        cv.wait_for(lk, 30s, [&]() { return requestReceived && memberMessageGenerated; }));
-    memberMessageGenerated = false;
+        cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
+    auto aliceMsgSize = aliceData.messages.size();
     libjami::acceptConversationRequest(bobId, convId);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return memberMessageGenerated; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 1 == aliceData.messages.size(); }));
 
     // Now check that alice, has the only admin, can remove bob
-    memberMessageGenerated = false;
-    voteMessageGenerated = false;
     libjami::removeConversationMember(aliceId, convId, bobUri);
     CPPUNIT_ASSERT(
-        cv.wait_for(lk, 30s, [&]() { return memberMessageGenerated && voteMessageGenerated; }));
+        cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 3 == aliceData.messages.size(); }));
 
     auto members = libjami::getConversationMembers(aliceId, convId);
 
@@ -1078,211 +865,96 @@ ConversationMembersEventTest::testAdminCanReAddMember()
     CPPUNIT_ASSERT(bobBanned);
 
     // Then check that bobUri can be re-added
-    memberMessageGenerated = false, voteMessageGenerated = false;
+    aliceMsgSize = aliceData.messages.size();
     libjami::addConversationMember(aliceId, convId, bobUri);
     CPPUNIT_ASSERT(
-        cv.wait_for(lk, 30s, [&]() { return memberMessageGenerated && voteMessageGenerated; }));
+        cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 2 == aliceData.messages.size(); }));
 
     members = libjami::getConversationMembers(aliceId, convId);
     CPPUNIT_ASSERT(members.size() == 2);
-    libjami::unregisterSignalHandlers();
 }
 
 void
 ConversationMembersEventTest::testMemberCannotBanOther()
 {
+    connectSignals();
+
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
-    auto aliceUri = aliceAccount->getUsername();
     auto bobUri = bobAccount->getUsername();
     auto convId = libjami::startConversation(aliceId);
     auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
     auto carlaUri = carlaAccount->getUsername();
     aliceAccount->trackBuddyPresence(carlaUri, true);
 
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    bool conversationReady = false, requestReceived = false, memberMessageGenerated = false,
-         voteMessageGenerated = false, messageBobReceived = false, errorDetected = false,
-         carlaConnected = false;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
-            [&](const std::string&, const std::string&, std::map<std::string, std::string>) {
-                requestReceived = true;
-                cv.notify_one();
-            }));
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConfigurationSignal::VolatileDetailsChanged>(
-            [&](const std::string&, const std::map<std::string, std::string>&) {
-                auto details = carlaAccount->getVolatileAccountDetails();
-                auto deviceAnnounced
-                    = details[libjami::Account::VolatileProperties::DEVICE_ANNOUNCED];
-                if (deviceAnnounced == "true") {
-                    carlaConnected = true;
-                    cv.notify_one();
-                }
-            }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& conversationId) {
-            if (accountId == bobId && conversationId == convId) {
-                conversationReady = true;
-                cv.notify_one();
-            }
-        }));
-    confHandlers.insert(libjami::exportable_callback<libjami::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(
-        libjami::exportable_callback<libjami::ConversationSignal::OnConversationError>(
-            [&](const std::string& /* accountId */,
-                const std::string& /* conversationId */,
-                int code,
-                const std::string& /* what */) {
-                if (code == 3)
-                    errorDetected = true;
-                cv.notify_one();
-            }));
-    libjami::registerSignalHandlers(confHandlers);
     Manager::instance().sendRegister(carlaId, true);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&] { return carlaConnected; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&] { return carlaData.deviceAnnounced; }));
     libjami::addConversationMember(aliceId, convId, bobUri);
     CPPUNIT_ASSERT(
-        cv.wait_for(lk, 30s, [&]() { return requestReceived && memberMessageGenerated; }));
-    memberMessageGenerated = false;
+        cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
+    auto aliceMsgSize = aliceData.messages.size();
     libjami::acceptConversationRequest(bobId, convId);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return memberMessageGenerated; }));
-    requestReceived = false;
-    memberMessageGenerated = false;
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 1 == aliceData.messages.size(); }));
     libjami::addConversationMember(aliceId, convId, carlaUri);
     CPPUNIT_ASSERT(
-        cv.wait_for(lk, 30s, [&]() { return requestReceived && memberMessageGenerated; }));
-    memberMessageGenerated = false;
-    messageBobReceived = false;
+        cv.wait_for(lk, 30s, [&]() { return carlaData.requestReceived; }));
+    aliceMsgSize = aliceData.messages.size();
+    auto bobMsgSize = bobData.messages.size();
     libjami::acceptConversationRequest(carlaId, convId);
     CPPUNIT_ASSERT(
-        cv.wait_for(lk, 30s, [&]() { return memberMessageGenerated && messageBobReceived; }));
+        cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 1 == aliceData.messages.size() && bobMsgSize + 1 == bobData.messages.size(); }));
 
     // Now Carla remove Bob as a member
-    errorDetected = false;
-    messageBobReceived = false;
     // remove from member & add into banned without voting for the ban
     simulateRemoval(carlaAccount, convId, bobUri);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return errorDetected; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceData.errorDetected; }));
 
+    bobMsgSize = bobData.messages.size();
     libjami::sendMessage(aliceId, convId, "hi"s, "");
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return messageBobReceived; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobMsgSize + 1 == bobData.messages.size(); }));
 }
 
 void
 ConversationMembersEventTest::testMemberCannotUnBanOther()
 {
+    connectSignals();
+
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
-    auto aliceUri = aliceAccount->getUsername();
     auto bobUri = bobAccount->getUsername();
     auto convId = libjami::startConversation(aliceId);
     auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
     auto carlaUri = carlaAccount->getUsername();
     aliceAccount->trackBuddyPresence(carlaUri, true);
 
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    bool conversationReady = false, requestReceived = false, memberMessageGenerated = false,
-         voteMessageGenerated = false, messageBobReceived = false, errorDetected = false,
-         carlaConnected = false, messageCarlaReceived = false;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
-            [&](const std::string&, const std::string&, std::map<std::string, std::string>) {
-                requestReceived = true;
-                cv.notify_one();
-            }));
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConfigurationSignal::VolatileDetailsChanged>(
-            [&](const std::string&, const std::map<std::string, std::string>&) {
-                auto details = carlaAccount->getVolatileAccountDetails();
-                auto deviceAnnounced
-                    = details[libjami::Account::VolatileProperties::DEVICE_ANNOUNCED];
-                if (deviceAnnounced == "true") {
-                    carlaConnected = true;
-                    cv.notify_one();
-                }
-            }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& conversationId) {
-            if (accountId == bobId && conversationId == convId) {
-                conversationReady = true;
-                cv.notify_one();
-            }
-        }));
-    confHandlers.insert(libjami::exportable_callback<libjami::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;
-            } else if (accountId == carlaId && conversationId == convId) {
-                messageCarlaReceived = true;
-            }
-            cv.notify_one();
-        }));
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::OnConversationError>(
-            [&](const std::string& /* accountId */,
-                const std::string& /* conversationId */,
-                int code,
-                const std::string& /* what */) {
-                if (code == 3)
-                    errorDetected = true;
-                cv.notify_one();
-            }));
-    libjami::registerSignalHandlers(confHandlers);
     Manager::instance().sendRegister(carlaId, true);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&] { return carlaConnected; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&] { return carlaData.deviceAnnounced; }));
     libjami::addConversationMember(aliceId, convId, bobUri);
     CPPUNIT_ASSERT(
-        cv.wait_for(lk, 30s, [&]() { return requestReceived && memberMessageGenerated; }));
-    memberMessageGenerated = false;
+        cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
+    auto aliceMsgSize = aliceData.messages.size();
     libjami::acceptConversationRequest(bobId, convId);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return memberMessageGenerated; }));
-    requestReceived = false;
-    memberMessageGenerated = false;
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 1 == aliceData.messages.size(); }));
     libjami::addConversationMember(aliceId, convId, carlaUri);
     CPPUNIT_ASSERT(
-        cv.wait_for(lk, 30s, [&]() { return requestReceived && memberMessageGenerated; }));
-    memberMessageGenerated = false;
-    messageBobReceived = false;
+        cv.wait_for(lk, 30s, [&]() { return carlaData.requestReceived; }));
+    aliceMsgSize = aliceData.messages.size();
+    auto bobMsgSize = bobData.messages.size();
     libjami::acceptConversationRequest(carlaId, convId);
     CPPUNIT_ASSERT(
-        cv.wait_for(lk, 30s, [&]() { return memberMessageGenerated && messageBobReceived; }));
+        cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 1 == aliceData.messages.size() && bobMsgSize + 1 == bobData.messages.size(); }));
 
     // Now check that alice, has the only admin, can remove bob
-    memberMessageGenerated = false;
-    messageCarlaReceived = false;
-    voteMessageGenerated = false;
+    auto carlaMsgSize = carlaData.messages.size();
     libjami::removeConversationMember(aliceId, convId, bobUri);
     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
-        return memberMessageGenerated && voteMessageGenerated && messageCarlaReceived;
+        return carlaMsgSize + 2 == carlaData.messages.size();;
     }));
 
-    memberMessageGenerated = false;
-    voteMessageGenerated = false;
+    aliceMsgSize = aliceData.messages.size();
     libjami::addConversationMember(carlaId, convId, bobUri);
     CPPUNIT_ASSERT(
-        !cv.wait_for(lk, 30s, [&]() { return memberMessageGenerated && voteMessageGenerated; }));
+        !cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 2 == aliceData.messages.size();; }));
     auto members = libjami::getConversationMembers(aliceId, convId);
     auto bobBanned = false;
     for (auto& member : members) {
@@ -1295,134 +967,46 @@ ConversationMembersEventTest::testMemberCannotUnBanOther()
 void
 ConversationMembersEventTest::testCheckAdminFakeAVoteIsDetected()
 {
+    connectSignals();
+
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
-    auto aliceUri = aliceAccount->getUsername();
     auto bobUri = bobAccount->getUsername();
     auto convId = libjami::startConversation(aliceId);
     auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
     auto carlaUri = carlaAccount->getUsername();
     aliceAccount->trackBuddyPresence(carlaUri, true);
 
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    bool conversationReady = false, requestReceived = false, memberMessageGenerated = false,
-         voteMessageGenerated = false, messageBobReceived = false, errorDetected = false,
-         carlaConnected = false;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
-            [&](const std::string& /*accountId*/,
-                const std::string& /* conversationId */,
-                std::map<std::string, std::string> /*metadatas*/) {
-                requestReceived = true;
-                cv.notify_one();
-            }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& conversationId) {
-            if (accountId == bobId && conversationId == convId) {
-                conversationReady = true;
-                cv.notify_one();
-            }
-        }));
-    confHandlers.insert(libjami::exportable_callback<libjami::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(
-        libjami::exportable_callback<libjami::ConfigurationSignal::VolatileDetailsChanged>(
-            [&](const std::string&, const std::map<std::string, std::string>&) {
-                auto details = carlaAccount->getVolatileAccountDetails();
-                auto deviceAnnounced
-                    = details[libjami::Account::VolatileProperties::DEVICE_ANNOUNCED];
-                if (deviceAnnounced == "true") {
-                    carlaConnected = true;
-                    cv.notify_one();
-                }
-            }));
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::OnConversationError>(
-            [&](const std::string& /* accountId */,
-                const std::string& /* conversationId */,
-                int code,
-                const std::string& /* what */) {
-                if (code == 3)
-                    errorDetected = true;
-                cv.notify_one();
-            }));
-    libjami::registerSignalHandlers(confHandlers);
     Manager::instance().sendRegister(carlaId, true);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&] { return carlaConnected; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&] { return carlaData.deviceAnnounced; }));
     libjami::addConversationMember(aliceId, convId, bobUri);
     CPPUNIT_ASSERT(
-        cv.wait_for(lk, 30s, [&]() { return requestReceived && memberMessageGenerated; }));
-    memberMessageGenerated = false;
+        cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
+    auto aliceMsgSize = aliceData.messages.size();
     libjami::acceptConversationRequest(bobId, convId);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return memberMessageGenerated; }));
-    requestReceived = false;
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 1 == aliceData.messages.size(); }));
     libjami::addConversationMember(aliceId, convId, carlaUri);
     CPPUNIT_ASSERT(
-        cv.wait_for(lk, 30s, [&]() { return requestReceived && memberMessageGenerated; }));
-    memberMessageGenerated = false;
-    messageBobReceived = false;
+        cv.wait_for(lk, 30s, [&]() { return carlaData.requestReceived; }));
+    aliceMsgSize = aliceData.messages.size();
+    auto bobMsgSize = bobData.messages.size();
     libjami::acceptConversationRequest(carlaId, convId);
     CPPUNIT_ASSERT(
-        cv.wait_for(lk, 30s, [&]() { return memberMessageGenerated && messageBobReceived; }));
+        cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 1 == aliceData.messages.size() && bobMsgSize + 1 == bobData.messages.size(); }));
 
     // Now Alice remove Carla without a vote. Bob will not receive the message
-    errorDetected = false;
     simulateRemoval(aliceAccount, convId, carlaUri);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return errorDetected; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.errorDetected; }));
 }
 
 void
 ConversationMembersEventTest::testAdminCannotKickTheirself()
 {
+    connectSignals();
+
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto aliceUri = aliceAccount->getUsername();
     auto convId = libjami::startConversation(aliceId);
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    bool conversationReady = false, requestReceived = false, memberMessageGenerated = false,
-         voteMessageGenerated = false, aliceMessageReceived = false;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
-            [&](const std::string& /*accountId*/,
-                const std::string& /* conversationId */,
-                std::map<std::string, std::string> /*metadatas*/) {
-                requestReceived = true;
-                cv.notify_one();
-            }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& conversationId) {
-            if (accountId == bobId && conversationId == convId) {
-                conversationReady = true;
-                cv.notify_one();
-            }
-        }));
-    confHandlers.insert(libjami::exportable_callback<libjami::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 == aliceId && conversationId == convId
-                       && message["type"] == "text/plain") {
-                aliceMessageReceived = true;
-            }
-            cv.notify_one();
-        }));
-    libjami::registerSignalHandlers(confHandlers);
     auto members = libjami::getConversationMembers(aliceId, convId);
     CPPUNIT_ASSERT(members.size() == 1);
     libjami::removeConversationMember(aliceId, convId, aliceUri);
@@ -1433,68 +1017,26 @@ ConversationMembersEventTest::testAdminCannotKickTheirself()
 void
 ConversationMembersEventTest::testCommitUnauthorizedUser()
 {
+    connectSignals();
+
     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::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    auto messageBobReceived = 0, messageAliceReceived = 0;
-    bool requestReceived = false;
-    bool conversationReady = false;
-    bool errorDetected = false;
-    confHandlers.insert(libjami::exportable_callback<libjami::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(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
-            [&](const std::string& /*accountId*/,
-                const std::string& /* conversationId */,
-                std::map<std::string, std::string> /*metadatas*/) {
-                requestReceived = true;
-                cv.notify_one();
-            }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& /* conversationId */) {
-            if (accountId == bobId) {
-                conversationReady = true;
-                cv.notify_one();
-            }
-        }));
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::OnConversationError>(
-            [&](const std::string& /* accountId */,
-                const std::string& /* conversationId */,
-                int code,
-                const std::string& /* what */) {
-                if (code == 3)
-                    errorDetected = true;
-                cv.notify_one();
-            }));
-    libjami::registerSignalHandlers(confHandlers);
-
     auto convId = libjami::startConversation(aliceId);
 
     libjami::addConversationMember(aliceId, convId, bobUri);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
 
+    auto aliceMsgSize = aliceData.messages.size();
     libjami::acceptConversationRequest(bobId, convId);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return conversationReady; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 1 == aliceData.messages.size();; }));
 
     // Assert that repository exists
-    auto repoPath = fileutils::get_data_dir() / bobAccount->getAccountID()
+    auto repoPath = fileutils::get_data_dir() / bobId
                     / "conversations" / convId;
     CPPUNIT_ASSERT(std::filesystem::is_directory(repoPath));
-    // Wait that alice sees Bob
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return messageAliceReceived == 2; }));
 
     // Add commit from invalid user
     Json::Value root;
@@ -1506,73 +1048,33 @@ ConversationMembersEventTest::testCommitUnauthorizedUser()
     auto message = Json::writeString(wbuilder, root);
     commitInRepo(repoPath, carlaAccount, message);
 
-    errorDetected = false;
     libjami::sendMessage(bobId, convId, "hi"s, "");
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return errorDetected; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceData.errorDetected; }));
     libjami::unregisterSignalHandlers();
 }
 
 void
 ConversationMembersEventTest::testMemberJoinsNoBadFile()
 {
+    connectSignals();
+
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
     auto carlaUri = carlaAccount->getUsername();
     aliceAccount->trackBuddyPresence(carlaUri, true);
     auto convId = libjami::startConversation(aliceId);
 
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    bool conversationReady = false, errorDetected = false, carlaConnected = false,
-         memberMessageGenerated = false;
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& /* conversationId */) {
-            if (accountId == carlaId) {
-                conversationReady = true;
-                cv.notify_one();
-            }
-        }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::MessageReceived>(
-        [&](const std::string& accountId,
-            const std::string& conversationId,
-            std::map<std::string, std::string> message) {
-            if (accountId == aliceId && conversationId == convId && message["type"] == "member") {
-                memberMessageGenerated = true;
-                cv.notify_one();
-            }
-        }));
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConfigurationSignal::VolatileDetailsChanged>(
-            [&](const std::string&, const std::map<std::string, std::string>&) {
-                auto details = carlaAccount->getVolatileAccountDetails();
-                auto deviceAnnounced
-                    = details[libjami::Account::VolatileProperties::DEVICE_ANNOUNCED];
-                if (deviceAnnounced == "true") {
-                    carlaConnected = true;
-                    cv.notify_one();
-                }
-            }));
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::OnConversationError>(
-            [&](const std::string& /* accountId */,
-                const std::string& /* conversationId */,
-                int code,
-                const std::string& /* what */) {
-                if (code == 3)
-                    errorDetected = true;
-                cv.notify_one();
-            }));
-    libjami::registerSignalHandlers(confHandlers);
-
+    auto aliceMsgSize = aliceData.messages.size();
     aliceAccount->convModule()->addConversationMember(convId, carlaUri, false);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&] { return memberMessageGenerated; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&] { return aliceMsgSize + 1 == aliceData.messages.size(); }));
 
     // Cp conversations & convInfo
-    auto repoPathAlice = fileutils::get_data_dir() / aliceAccount->getAccountID() / "conversations";
-    auto repoPathCarla = fileutils::get_data_dir() / carlaAccount->getAccountID() / "conversations";
+    auto repoPathAlice = fileutils::get_data_dir() / aliceId / "conversations";
+    auto repoPathCarla = fileutils::get_data_dir() / carlaId / "conversations";
     std::filesystem::copy(repoPathAlice, repoPathCarla, std::filesystem::copy_options::recursive);
-    auto ciPathAlice = fileutils::get_data_dir() / aliceAccount->getAccountID()
+    auto ciPathAlice = fileutils::get_data_dir() / aliceId
                        / "convInfo";
-    auto ciPathCarla = fileutils::get_data_dir() / carlaAccount->getAccountID()
+    auto ciPathCarla = fileutils::get_data_dir() / carlaId
                        / "convInfo";
     std::remove(ciPathCarla.c_str());
     std::filesystem::copy(ciPathAlice, ciPathCarla);
@@ -1603,76 +1105,35 @@ ConversationMembersEventTest::testMemberJoinsNoBadFile()
     // Start Carla, should merge and all messages should be there
     carlaAccount->convModule()->loadConversations(); // Because of the copy
     Manager::instance().sendRegister(carlaId, true);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return carlaConnected; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return carlaData.deviceAnnounced; }));
 
-    errorDetected = false;
     libjami::sendMessage(carlaId, convId, "hi"s, "");
 
-    CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&] { return errorDetected; }));
-    libjami::unregisterSignalHandlers();
+    CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&] { return aliceData.errorDetected; }));
 }
 
 void
 ConversationMembersEventTest::testMemberAddedNoCertificate()
 {
+    connectSignals();
+
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
     auto carlaUri = carlaAccount->getUsername();
     aliceAccount->trackBuddyPresence(carlaUri, true);
     auto convId = libjami::startConversation(aliceId);
 
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    bool conversationReady = false, errorDetected = false, carlaConnected = false,
-         memberMessageGenerated = false;
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& /* conversationId */) {
-            if (accountId == carlaId) {
-                conversationReady = true;
-                cv.notify_one();
-            }
-        }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::MessageReceived>(
-        [&](const std::string& accountId,
-            const std::string& conversationId,
-            std::map<std::string, std::string> message) {
-            if (accountId == aliceId && conversationId == convId && message["type"] == "member") {
-                memberMessageGenerated = true;
-                cv.notify_one();
-            }
-        }));
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConfigurationSignal::VolatileDetailsChanged>(
-            [&](const std::string&, const std::map<std::string, std::string>&) {
-                auto details = carlaAccount->getVolatileAccountDetails();
-                auto deviceAnnounced
-                    = details[libjami::Account::VolatileProperties::DEVICE_ANNOUNCED];
-                if (deviceAnnounced == "true") {
-                    carlaConnected = true;
-                    cv.notify_one();
-                }
-            }));
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::OnConversationError>(
-            [&](const std::string& /* accountId */,
-                const std::string& /* conversationId */,
-                int code,
-                const std::string& /* what */) {
-                if (code == 3)
-                    errorDetected = true;
-                cv.notify_one();
-            }));
-    libjami::registerSignalHandlers(confHandlers);
-
+    auto aliceMsgSize = aliceData.messages.size();
     aliceAccount->convModule()->addConversationMember(convId, carlaUri, false);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&] { return memberMessageGenerated; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&] { return aliceMsgSize + 1 == aliceData.messages.size(); }));
 
     // Cp conversations & convInfo
-    auto repoPathAlice = fileutils::get_data_dir() / aliceAccount->getAccountID() / "conversations";
-    auto repoPathCarla = fileutils::get_data_dir() / carlaAccount->getAccountID() / "conversations";
+    auto repoPathAlice = fileutils::get_data_dir() / aliceId / "conversations";
+    auto repoPathCarla = fileutils::get_data_dir() / carlaId / "conversations";
     std::filesystem::copy(repoPathAlice, repoPathCarla, std::filesystem::copy_options::recursive);
-    auto ciPathAlice = fileutils::get_data_dir() / aliceAccount->getAccountID()
+    auto ciPathAlice = fileutils::get_data_dir() / aliceId
                        / "convInfo";
-    auto ciPathCarla = fileutils::get_data_dir() / carlaAccount->getAccountID()
+    auto ciPathCarla = fileutils::get_data_dir() / carlaId
                        / "convInfo";
     std::remove(ciPathCarla.c_str());
     std::filesystem::copy(ciPathAlice, ciPathCarla);
@@ -1694,75 +1155,34 @@ ConversationMembersEventTest::testMemberAddedNoCertificate()
     // Start Carla, should merge and all messages should be there
     carlaAccount->convModule()->loadConversations(); // Because of the copy
     Manager::instance().sendRegister(carlaId, true);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return carlaConnected; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return carlaData.deviceAnnounced; }));
 
     libjami::sendMessage(carlaId, convId, "hi"s, "");
-    errorDetected = false;
 
-    CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&] { return errorDetected; }));
-    libjami::unregisterSignalHandlers();
+    CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&] { return aliceData.errorDetected; }));
 }
 
 void
 ConversationMembersEventTest::testMemberJoinsInviteRemoved()
 {
+    connectSignals();
+
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
     auto carlaUri = carlaAccount->getUsername();
     aliceAccount->trackBuddyPresence(carlaUri, true);
     auto convId = libjami::startConversation(aliceId);
 
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    bool conversationReady = false, errorDetected = false, carlaConnected = false,
-         memberMessageGenerated = false;
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& /* conversationId */) {
-            if (accountId == carlaId) {
-                conversationReady = true;
-                cv.notify_one();
-            }
-        }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::MessageReceived>(
-        [&](const std::string& accountId,
-            const std::string& conversationId,
-            std::map<std::string, std::string> message) {
-            if (accountId == aliceId && conversationId == convId && message["type"] == "member") {
-                memberMessageGenerated = true;
-                cv.notify_one();
-            }
-        }));
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConfigurationSignal::VolatileDetailsChanged>(
-            [&](const std::string&, const std::map<std::string, std::string>&) {
-                auto details = carlaAccount->getVolatileAccountDetails();
-                auto deviceAnnounced
-                    = details[libjami::Account::VolatileProperties::DEVICE_ANNOUNCED];
-                if (deviceAnnounced == "true") {
-                    carlaConnected = true;
-                    cv.notify_one();
-                }
-            }));
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::OnConversationError>(
-            [&](const std::string& /* accountId */,
-                const std::string& /* conversationId */,
-                int code,
-                const std::string& /* what */) {
-                if (code == 3)
-                    errorDetected = true;
-                cv.notify_one();
-            }));
-    libjami::registerSignalHandlers(confHandlers);
-
+    auto aliceMsgSize = aliceData.messages.size();
     aliceAccount->convModule()->addConversationMember(convId, carlaUri, false);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&] { return memberMessageGenerated; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&] { return aliceMsgSize + 1 == aliceData.messages.size(); }));
 
     // Cp conversations & convInfo
-    auto repoPathAlice = fileutils::get_data_dir() / aliceAccount->getAccountID() / "conversations";
-    auto repoPathCarla = fileutils::get_data_dir() / carlaAccount->getAccountID() / "conversations";
+    auto repoPathAlice = fileutils::get_data_dir() / aliceId / "conversations";
+    auto repoPathCarla = fileutils::get_data_dir() / carlaId / "conversations";
     std::filesystem::copy(repoPathAlice, repoPathCarla, std::filesystem::copy_options::recursive);
-    auto ciPathAlice = fileutils::get_data_dir() / aliceAccount->getAccountID() / "convInfo";
-    auto ciPathCarla = fileutils::get_data_dir() / carlaAccount->getAccountID() / "convInfo";
+    auto ciPathAlice = fileutils::get_data_dir() / aliceId / "convInfo";
+    auto ciPathCarla = fileutils::get_data_dir() / carlaId / "convInfo";
     std::remove(ciPathCarla.c_str());
     std::filesystem::copy(ciPathAlice, ciPathCarla);
 
@@ -1792,186 +1212,78 @@ ConversationMembersEventTest::testMemberJoinsInviteRemoved()
     // Start Carla, should merge and all messages should be there
     carlaAccount->convModule()->loadConversations(); // Because of the copy
     Manager::instance().sendRegister(carlaId, true);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return carlaConnected; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return carlaData.deviceAnnounced; }));
 
     libjami::sendMessage(carlaId, convId, "hi"s, "");
-    errorDetected = false;
-
-    CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&] { return errorDetected; }));
-    libjami::unregisterSignalHandlers();
+    CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&] { return aliceData.errorDetected; }));
 }
 
 void
 ConversationMembersEventTest::testFailAddMemberInOneToOne()
 {
+    connectSignals();
+
     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();
-    auto aliceUri = aliceAccount->getUsername();
     auto carlaUri = carlaAccount->getUsername();
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    bool conversationReady = false, requestReceived = false, memberMessageGenerated = false;
-    std::string convId = "";
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConfigurationSignal::IncomingTrustRequest>(
-            [&](const std::string& account_id,
-                const std::string& /*from*/,
-                const std::string& /*conversationId*/,
-                const std::vector<uint8_t>& /*payload*/,
-                time_t /*received*/) {
-                if (account_id == bobId)
-                    requestReceived = true;
-                cv.notify_one();
-            }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& conversationId) {
-            if (accountId == aliceId) {
-                convId = conversationId;
-            } else if (accountId == bobId) {
-                conversationReady = true;
-            }
-            cv.notify_one();
-        }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::MessageReceived>(
-        [&](const std::string& accountId,
-            const std::string& conversationId,
-            std::map<std::string, std::string> message) {
-            if (accountId == aliceId && conversationId == convId && message["type"] == "member") {
-                memberMessageGenerated = true;
-                cv.notify_one();
-            }
-        }));
-    libjami::registerSignalHandlers(confHandlers);
+
     aliceAccount->addContact(bobUri);
     aliceAccount->sendTrustRequest(bobUri, {});
-    CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&]() { return !convId.empty(); }));
-    memberMessageGenerated = false;
-    libjami::addConversationMember(aliceId, convId, carlaUri);
-    CPPUNIT_ASSERT(!cv.wait_for(lk, 5s, [&]() { return memberMessageGenerated; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&]() { return bobData.requestReceived; }));
+    auto aliceMsgSize = aliceData.messages.size();
+    libjami::addConversationMember(aliceId, aliceData.conversationId, carlaUri);
+    CPPUNIT_ASSERT(!cv.wait_for(lk, 5s, [&]() { return aliceMsgSize + 1 == aliceData.messages.size(); }));
 }
 
 void
 ConversationMembersEventTest::testOneToOneFetchWithNewMemberRefused()
 {
+    connectSignals();
+
     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();
     auto aliceUri = aliceAccount->getUsername();
     auto carlaUri = carlaAccount->getUsername();
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    bool conversationReady = false, requestReceived = false, memberMessageGenerated = false,
-         messageBob = false, errorDetected = false;
-    std::string convId = "";
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConfigurationSignal::IncomingTrustRequest>(
-            [&](const std::string& account_id,
-                const std::string& /*from*/,
-                const std::string& /*conversationId*/,
-                const std::vector<uint8_t>& /*payload*/,
-                time_t /*received*/) {
-                if (account_id == bobId)
-                    requestReceived = true;
-                cv.notify_one();
-            }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& conversationId) {
-            if (accountId == aliceId) {
-                convId = conversationId;
-            } else if (accountId == bobId) {
-                conversationReady = true;
-            }
-            cv.notify_one();
-        }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::MessageReceived>(
-        [&](const std::string& accountId,
-            const std::string& conversationId,
-            std::map<std::string, std::string> message) {
-            if (accountId == aliceId && conversationId == convId && message["type"] == "member") {
-                memberMessageGenerated = true;
-            } else if (accountId == bobId && conversationId == convId
-                       && message["type"] == "member") {
-                messageBob = true;
-            }
-            cv.notify_one();
-        }));
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::OnConversationError>(
-            [&](const std::string& /* accountId */,
-                const std::string& /* conversationId */,
-                int code,
-                const std::string& /* what */) {
-                if (code == 3)
-                    errorDetected = true;
-                cv.notify_one();
-            }));
-    libjami::registerSignalHandlers(confHandlers);
+
     aliceAccount->addContact(bobUri);
     aliceAccount->sendTrustRequest(bobUri, {});
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !convId.empty() && requestReceived; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
+    auto aliceMsgSize = aliceData.messages.size();
     CPPUNIT_ASSERT(bobAccount->acceptTrustRequest(aliceUri));
-    memberMessageGenerated = false;
     CPPUNIT_ASSERT(
-        cv.wait_for(lk, 30s, [&]() { return conversationReady && memberMessageGenerated; }));
+        cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 1 == aliceData.messages.size();; }));
 
-    errorDetected = false;
     // NOTE: Add certificate because no DHT lookup
     aliceAccount->certStore().pinCertificate(carlaAccount->identity().second);
-    generateFakeInvite(aliceAccount, convId, carlaUri);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return errorDetected; }));
+    generateFakeInvite(aliceAccount, aliceData.conversationId, carlaUri);
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.errorDetected; }));
 }
 
 void
 ConversationMembersEventTest::testConversationMemberEvent()
 {
+    connectSignals();
+
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto bobUri = bobAccount->getUsername();
     auto convId = libjami::startConversation(aliceId);
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    bool conversationReady = false, requestReceived = false, memberAddGenerated = false;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
-            [&](const std::string& /*accountId*/,
-                const std::string& /* conversationId */,
-                std::map<std::string, std::string> /*metadatas*/) {
-                requestReceived = true;
-                cv.notify_one();
-            }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& /* conversationId */) {
-            if (accountId == bobId) {
-                conversationReady = true;
-                cv.notify_one();
-            }
-        }));
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::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();
-            }));
-    libjami::registerSignalHandlers(confHandlers);
     libjami::addConversationMember(aliceId, convId, bobUri);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return memberAddGenerated; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
     // Assert that repository exists
-    auto repoPath = fileutils::get_data_dir() / aliceAccount->getAccountID()
+    auto repoPath = fileutils::get_data_dir() / aliceId
                     / "conversations" / convId;
     CPPUNIT_ASSERT(std::filesystem::is_directory(repoPath));
     // Check created files
     auto bobInvited = repoPath / "invited" / bobUri;
     CPPUNIT_ASSERT(std::filesystem::is_regular_file(bobInvited));
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; }));
     libjami::acceptConversationRequest(bobId, convId);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return conversationReady; }));
-    auto clonedPath = fileutils::get_data_dir() / bobAccount->getAccountID()
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !bobData.conversationId.empty(); }));
+    auto clonedPath = fileutils::get_data_dir() / bobId
                       / "conversations" / convId;
     CPPUNIT_ASSERT(std::filesystem::is_directory(clonedPath));
     bobInvited = clonedPath / "invited" / bobUri;
@@ -1983,42 +1295,20 @@ ConversationMembersEventTest::testConversationMemberEvent()
 void
 ConversationMembersEventTest::testGetConversationsMembersWhileSyncing()
 {
+    connectSignals();
+
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto bobUri = bobAccount->getUsername();
     auto aliceUri = aliceAccount->getUsername();
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    bool conversationReady = false, requestReceived = false;
-    std::string convId = "";
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConfigurationSignal::IncomingTrustRequest>(
-            [&](const std::string& account_id,
-                const std::string& /*from*/,
-                const std::string& /*conversationId*/,
-                const std::vector<uint8_t>& /*payload*/,
-                time_t /*received*/) {
-                if (account_id == bobId)
-                    requestReceived = true;
-                cv.notify_one();
-            }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& conversationId) {
-            if (accountId == aliceId) {
-                convId = conversationId;
-            } else if (accountId == bobId) {
-                conversationReady = true;
-            }
-            cv.notify_one();
-        }));
-    libjami::registerSignalHandlers(confHandlers);
     aliceAccount->addContact(bobUri);
     aliceAccount->sendTrustRequest(bobUri, {});
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
 
     Manager::instance().sendRegister(aliceId, false); // This avoid to sync immediately
     CPPUNIT_ASSERT(bobAccount->acceptTrustRequest(aliceUri));
 
-    auto members = libjami::getConversationMembers(bobId, convId);
+    auto members = libjami::getConversationMembers(bobId, aliceData.conversationId);
     CPPUNIT_ASSERT(std::find_if(members.begin(),
                                 members.end(),
                                 [&](auto memberInfo) { return memberInfo["uri"] == aliceUri; })
@@ -2032,6 +1322,8 @@ ConversationMembersEventTest::testGetConversationsMembersWhileSyncing()
 void
 ConversationMembersEventTest::testGetConversationMembersWithSelfOneOne()
 {
+    connectSignals();
+
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto aliceUri = aliceAccount->getUsername();
     std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
@@ -2054,66 +1346,25 @@ ConversationMembersEventTest::testGetConversationMembersWithSelfOneOne()
 void
 ConversationMembersEventTest::testAvoidTwoOneToOne()
 {
+    connectSignals();
+
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto bobUri = bobAccount->getUsername();
     auto aliceUri = aliceAccount->getUsername();
 
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    auto requestReceived = false;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
-            [&](const std::string& accountId,
-                const std::string& conversationId,
-                std::map<std::string, std::string> /*metadatas*/) {
-                if (accountId == bobId)
-                    requestReceived = true;
-                cv.notify_one();
-            }));
-    std::string convId = "";
-    auto conversationReadyBob = false;
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& conversationId) {
-            if (accountId == aliceId) {
-                convId = conversationId;
-            } else if (accountId == bobId && conversationId == convId) {
-                conversationReadyBob = true;
-            }
-            cv.notify_one();
-        }));
-    auto memberMessageGenerated = false;
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::MessageReceived>(
-        [&](const std::string& accountId,
-            const std::string& conversationId,
-            std::map<std::string, std::string> message) {
-            if (accountId == aliceId && conversationId == convId) {
-                if (message["type"] == "member")
-                    memberMessageGenerated = true;
-            }
-            cv.notify_one();
-        }));
-    auto conversationRmBob = false;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRemoved>(
-            [&](const std::string& accountId, const std::string&) {
-                if (accountId == bobId)
-                    conversationRmBob = true;
-                cv.notify_one();
-            }));
-    libjami::registerSignalHandlers(confHandlers);
-
     // Alice adds bob
-    requestReceived = false;
     aliceAccount->addContact(bobUri);
     aliceAccount->sendTrustRequest(bobUri, {});
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; }));
-    libjami::acceptConversationRequest(bobId, convId);
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
+    auto aliceMsgSize = aliceData.messages.size();
+    libjami::acceptConversationRequest(bobId, aliceData.conversationId);
     CPPUNIT_ASSERT(
-        cv.wait_for(lk, 30s, [&]() { return conversationReadyBob && memberMessageGenerated; }));
+        cv.wait_for(lk, 30s, [&]() { return aliceData.messages.size() == aliceMsgSize + 1; }));
 
     // Remove contact
     bobAccount->removeContact(aliceUri, false);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return conversationRmBob; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.removed; }));
 
     // wait that connections are closed.
     std::this_thread::sleep_for(10s);
@@ -2121,78 +1372,19 @@ ConversationMembersEventTest::testAvoidTwoOneToOne()
     // Bob add Alice, this should re-add old conversation
     bobAccount->addContact(aliceUri);
     bobAccount->sendTrustRequest(aliceUri, {});
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return conversationReadyBob; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.conversationId == aliceData.conversationId; }));
 }
 
 void
 ConversationMembersEventTest::testAvoidTwoOneToOneMultiDevices()
 {
+    connectSignals();
+
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto bobUri = bobAccount->getUsername();
     auto aliceUri = aliceAccount->getUsername();
 
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    auto requestReceived = false, requestReceivedBob2 = false;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
-            [&](const std::string& accountId,
-                const std::string& conversationId,
-                std::map<std::string, std::string> /*metadatas*/) {
-                if (accountId == bobId)
-                    requestReceived = true;
-                else if (accountId == bob2Id)
-                    requestReceivedBob2 = true;
-                cv.notify_one();
-            }));
-    std::string convId = "";
-    auto conversationReadyBob = false, conversationReadyBob2 = false;
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& conversationId) {
-            if (accountId == aliceId) {
-                convId = conversationId;
-            } else if (accountId == bobId && conversationId == convId) {
-                conversationReadyBob = true;
-            } else if (accountId == bob2Id && conversationId == convId) {
-                conversationReadyBob2 = true;
-            }
-            cv.notify_one();
-        }));
-    auto memberMessageGenerated = false;
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::MessageReceived>(
-        [&](const std::string& accountId,
-            const std::string& conversationId,
-            std::map<std::string, std::string> message) {
-            if (accountId == aliceId && conversationId == convId) {
-                if (message["type"] == "member")
-                    memberMessageGenerated = true;
-            }
-            cv.notify_one();
-        }));
-    auto bob2Started = false;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConfigurationSignal::VolatileDetailsChanged>(
-            [&](const std::string& accountId, const std::map<std::string, std::string>& details) {
-                if (accountId == bob2Id) {
-                    auto daemonStatus = details.at(
-                        libjami::Account::VolatileProperties::DEVICE_ANNOUNCED);
-                    if (daemonStatus == "true")
-                        bob2Started = true;
-                }
-                cv.notify_one();
-            }));
-    auto conversationRmBob = false, conversationRmBob2 = false;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRemoved>(
-            [&](const std::string& accountId, const std::string&) {
-                if (accountId == bobId)
-                    conversationRmBob = true;
-                else if (accountId == bob2Id)
-                    conversationRmBob2 = true;
-                cv.notify_one();
-            }));
-    libjami::registerSignalHandlers(confHandlers);
-
     // Bob creates a second device
     auto bobArchive = std::filesystem::current_path().string() + "/bob.gz";
     std::remove(bobArchive.c_str());
@@ -2207,21 +1399,21 @@ ConversationMembersEventTest::testAvoidTwoOneToOneMultiDevices()
     details[ConfProperties::ARCHIVE_PATH] = bobArchive;
     bob2Id = Manager::instance().addAccount(details);
 
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bob2Started; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bob2Data.deviceAnnounced; }));
 
     // Alice adds bob
-    requestReceived = false;
     aliceAccount->addContact(bobUri);
     aliceAccount->sendTrustRequest(bobUri, {});
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived && requestReceivedBob2; }));
-    libjami::acceptConversationRequest(bobId, convId);
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived && bob2Data.requestReceived; }));
+    auto aliceMsgSize = aliceData.messages.size();
+    libjami::acceptConversationRequest(bobId, aliceData.conversationId);
     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
-        return conversationReadyBob && conversationReadyBob2 && memberMessageGenerated;
+        return !bobData.conversationId.empty() && !bob2Data.conversationId.empty() && aliceMsgSize + 1 == aliceData.messages.size();
     }));
 
     // Remove contact
     bobAccount->removeContact(aliceUri, false);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return conversationRmBob && conversationRmBob2; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.removed && bob2Data.removed; }));
 
     // wait that connections are closed.
     std::this_thread::sleep_for(10s);
@@ -2230,52 +1422,19 @@ ConversationMembersEventTest::testAvoidTwoOneToOneMultiDevices()
     bobAccount->addContact(aliceUri);
     bobAccount->sendTrustRequest(aliceUri, {});
     CPPUNIT_ASSERT(
-        cv.wait_for(lk, 30s, [&]() { return conversationReadyBob && conversationReadyBob2; }));
+        cv.wait_for(lk, 30s, [&]() { return bobData.conversationId == aliceData.conversationId && bob2Data.conversationId == aliceData.conversationId; }));
 }
 
 void
 ConversationMembersEventTest::testRemoveRequestBannedMultiDevices()
 {
+    connectSignals();
+
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto bobUri = bobAccount->getUsername();
     auto aliceUri = aliceAccount->getUsername();
 
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    auto requestReceived = false, requestReceivedBob2 = false;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
-            [&](const std::string& accountId,
-                const std::string& conversationId,
-                std::map<std::string, std::string> /*metadatas*/) {
-                if (accountId == bobId)
-                    requestReceived = true;
-                else if (accountId == bob2Id)
-                    requestReceivedBob2 = true;
-                cv.notify_one();
-            }));
-    auto bob2Started = false;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConfigurationSignal::VolatileDetailsChanged>(
-            [&](const std::string& accountId, const std::map<std::string, std::string>& details) {
-                if (accountId == bob2Id) {
-                    auto daemonStatus = details.at(
-                        libjami::Account::VolatileProperties::DEVICE_ANNOUNCED);
-                    if (daemonStatus == "true")
-                        bob2Started = true;
-                }
-                cv.notify_one();
-            }));
-    auto bob2ContactRemoved = false;
-    confHandlers.insert(libjami::exportable_callback<libjami::ConfigurationSignal::ContactRemoved>(
-        [&](const std::string& accountId, const std::string& uri, bool banned) {
-            if (accountId == bob2Id && uri == aliceUri && banned) {
-                bob2ContactRemoved = true;
-            }
-            cv.notify_one();
-        }));
-    libjami::registerSignalHandlers(confHandlers);
-
     // Bob creates a second device
     auto bobArchive = std::filesystem::current_path().string() + "/bob.gz";
     std::remove(bobArchive.c_str());
@@ -2290,92 +1449,29 @@ ConversationMembersEventTest::testRemoveRequestBannedMultiDevices()
     details[ConfProperties::ARCHIVE_PATH] = bobArchive;
     bob2Id = Manager::instance().addAccount(details);
 
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bob2Started; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bob2Data.deviceAnnounced; }));
 
     // Alice adds bob
-    requestReceived = false;
     aliceAccount->addContact(bobUri);
     aliceAccount->sendTrustRequest(bobUri, {});
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived && requestReceivedBob2; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived && bob2Data.requestReceived; }));
     CPPUNIT_ASSERT(libjami::getConversationRequests(bob2Id).size() == 1);
 
     // Bob bans alice, should update bob2
     bobAccount->removeContact(aliceUri, true);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bob2ContactRemoved; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bob2Data.contactRemoved; }));
     CPPUNIT_ASSERT(libjami::getConversationRequests(bob2Id).size() == 0);
 }
 
 void
 ConversationMembersEventTest::testBanUnbanMultiDevice()
 {
-    auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
+    connectSignals();
+
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto bobUri = bobAccount->getUsername();
-    auto aliceUri = aliceAccount->getUsername();
-
     auto convId = libjami::startConversation(aliceId);
 
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    auto requestReceived = false, requestReceivedBob2 = false;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
-            [&](const std::string& accountId,
-                const std::string& conversationId,
-                std::map<std::string, std::string> /*metadatas*/) {
-                if (accountId == bobId)
-                    requestReceived = true;
-                else if (accountId == bob2Id)
-                    requestReceivedBob2 = true;
-                cv.notify_one();
-            }));
-    auto bob2Started = false;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConfigurationSignal::VolatileDetailsChanged>(
-            [&](const std::string& accountId, const std::map<std::string, std::string>& details) {
-                if (accountId == bob2Id) {
-                    auto daemonStatus = details.at(
-                        libjami::Account::VolatileProperties::DEVICE_ANNOUNCED);
-                    if (daemonStatus == "true")
-                        bob2Started = true;
-                }
-                cv.notify_one();
-            }));
-    auto bob2ContactRemoved = false;
-    confHandlers.insert(libjami::exportable_callback<libjami::ConfigurationSignal::ContactRemoved>(
-        [&](const std::string& accountId, const std::string& uri, bool banned) {
-            if (accountId == bob2Id && uri == aliceUri && banned) {
-                bob2ContactRemoved = true;
-            }
-            cv.notify_one();
-        }));
-    auto memberMessageGenerated = false, voteMessageGenerated = false;
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::MessageReceived>(
-        [&](const std::string& accountId,
-            const std::string& conversationId,
-            std::map<std::string, std::string> message) {
-            auto itFind = message.find("type");
-            if (itFind == message.end())
-                return;
-            if (accountId == aliceId && conversationId == convId) {
-                if (itFind->second == "member")
-                    memberMessageGenerated = true;
-                if (itFind->second == "vote")
-                    voteMessageGenerated = true;
-            }
-            cv.notify_one();
-        }));
-    auto conversationReadyBob = false, conversationReadyBob2 = false;
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& conversationId) {
-            if (accountId == bobId && conversationId == convId) {
-                conversationReadyBob = true;
-            } else if (accountId == bob2Id && conversationId == convId) {
-                conversationReadyBob2 = true;
-            }
-            cv.notify_one();
-        }));
-    libjami::registerSignalHandlers(confHandlers);
-
     // Bob creates a second device
     auto bobArchive = std::filesystem::current_path().string() + "/bob.gz";
     std::remove(bobArchive.c_str());
@@ -2390,117 +1486,40 @@ ConversationMembersEventTest::testBanUnbanMultiDevice()
     details[ConfProperties::ARCHIVE_PATH] = bobArchive;
     bob2Id = Manager::instance().addAccount(details);
 
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bob2Started; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bob2Data.deviceAnnounced; }));
 
     // Alice adds bob
-    requestReceived = false;
     libjami::addConversationMember(aliceId, convId, bobUri);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
-        return memberMessageGenerated && requestReceived && requestReceivedBob2;
-    }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived && bob2Data.requestReceived; }));
 
     // Alice kick Bob while invited
-    memberMessageGenerated = false;
-    voteMessageGenerated = false;
+    auto aliceMsgSize = aliceData.messages.size();
     libjami::removeConversationMember(aliceId, convId, bobUri);
     CPPUNIT_ASSERT(
-        cv.wait_for(lk, 30s, [&]() { return memberMessageGenerated && voteMessageGenerated; }));
+        cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 2 == aliceData.messages.size(); }));
 
     // Alice re-add Bob while invited
-    memberMessageGenerated = false;
-    voteMessageGenerated = false;
+    aliceMsgSize = aliceData.messages.size();
     libjami::addConversationMember(aliceId, convId, bobUri);
     CPPUNIT_ASSERT(
-        cv.wait_for(lk, 30s, [&]() { return memberMessageGenerated && voteMessageGenerated; }));
+        cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 2 == aliceData.messages.size(); }));
 
     // bob accepts
     libjami::acceptConversationRequest(bobId, convId);
     CPPUNIT_ASSERT(
-        cv.wait_for(lk, 30s, [&]() { return conversationReadyBob && conversationReadyBob2; }));
+        cv.wait_for(lk, 30s, [&]() { return bobData.conversationId == aliceData.conversationId && bob2Data.conversationId == aliceData.conversationId; }));
 }
 
 void
 ConversationMembersEventTest::testBanUnbanGotFirstConv()
 {
+    connectSignals();
+
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto bobUri = bobAccount->getUsername();
     auto aliceUri = aliceAccount->getUsername();
 
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    auto requestReceived = false, requestReceivedBob2 = false;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
-            [&](const std::string& accountId,
-                const std::string& conversationId,
-                std::map<std::string, std::string> /*metadatas*/) {
-                if (accountId == bobId)
-                    requestReceived = true;
-                else if (accountId == bob2Id)
-                    requestReceivedBob2 = true;
-                cv.notify_one();
-            }));
-    std::string convId;
-    auto conversationReadyBob = false, conversationReadyBob2 = false;
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& conversationId) {
-            if (accountId == aliceId) {
-                convId = conversationId;
-            } else if (accountId == bobId && conversationId == convId) {
-                conversationReadyBob = true;
-            } else if (accountId == bob2Id && conversationId == convId) {
-                conversationReadyBob2 = true;
-            }
-            cv.notify_one();
-        }));
-    auto bob2Started = false;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConfigurationSignal::VolatileDetailsChanged>(
-            [&](const std::string& accountId, const std::map<std::string, std::string>& details) {
-                if (accountId == bob2Id) {
-                    auto daemonStatus = details.at(
-                        libjami::Account::VolatileProperties::DEVICE_ANNOUNCED);
-                    if (daemonStatus == "true")
-                        bob2Started = true;
-                }
-                cv.notify_one();
-            }));
-    auto bob2ContactRemoved = false;
-    confHandlers.insert(libjami::exportable_callback<libjami::ConfigurationSignal::ContactRemoved>(
-        [&](const std::string& accountId, const std::string& uri, bool banned) {
-            if (accountId == bob2Id && uri == aliceUri && banned) {
-                bob2ContactRemoved = true;
-            }
-            cv.notify_one();
-        }));
-    auto bobMsgReceived = false, bob2MsgReceived = false, memberMessageGenerated = false;
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::MessageReceived>(
-        [&](const std::string& accountId,
-            const std::string& conversationId,
-            std::map<std::string, std::string> message) {
-            if (accountId == aliceId && conversationId == convId) {
-                auto itFind = message.find("type");
-                if (itFind != message.end() && itFind->second == "member")
-                    memberMessageGenerated = true;
-            } else if (accountId == bobId && conversationId == convId)
-                bobMsgReceived = true;
-            else if (accountId == bob2Id && conversationId == convId)
-                bob2MsgReceived = true;
-            cv.notify_one();
-        }));
-    auto contactAddedBob = false, contactAddedBob2 = false;
-    confHandlers.insert(libjami::exportable_callback<libjami::ConfigurationSignal::ContactAdded>(
-        [&](const std::string& accountId, const std::string& uri, bool) {
-            if (accountId == bobId && uri == aliceUri) {
-                contactAddedBob = true;
-            } else if (accountId == bob2Id && uri == aliceUri) {
-                contactAddedBob2 = true;
-            }
-            cv.notify_one();
-        }));
-
-    libjami::registerSignalHandlers(confHandlers);
-
     // Bob creates a second device
     auto bobArchive = std::filesystem::current_path().string() + "/bob.gz";
     std::remove(bobArchive.c_str());
@@ -2515,105 +1534,72 @@ ConversationMembersEventTest::testBanUnbanGotFirstConv()
     details[ConfProperties::ARCHIVE_PATH] = bobArchive;
     bob2Id = Manager::instance().addAccount(details);
 
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bob2Started; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bob2Data.deviceAnnounced; }));
 
     // Alice adds bob
-    requestReceived = false;
     aliceAccount->addContact(bobUri);
     aliceAccount->sendTrustRequest(bobUri, {});
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived && requestReceivedBob2; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived && bob2Data.requestReceived; }));
     CPPUNIT_ASSERT(libjami::getConversationRequests(bob2Id).size() == 1);
 
     // Accepts requests
-    libjami::acceptConversationRequest(bobId, convId);
+    auto aliceMsgSize = aliceData.messages.size();
+    libjami::acceptConversationRequest(bobId, aliceData.conversationId);
     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
-        return conversationReadyBob && conversationReadyBob2 && memberMessageGenerated;
+        return !bobData.conversationId.empty() && !bob2Data.conversationId.empty() && aliceMsgSize + 1 == aliceData.messages.size();
     }));
 
     // Bob bans alice, should update bob2
     bobAccount->removeContact(aliceUri, true);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bob2ContactRemoved; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bob2Data.contactRemoved; }));
 
     // Alice sends messages, bob & bob2 should not get it!
-    bobMsgReceived = false, bob2MsgReceived = false;
-    libjami::sendMessage(aliceId, convId, "hi"s, "");
-    CPPUNIT_ASSERT(!cv.wait_for(lk, 30s, [&]() { return bobMsgReceived && bob2MsgReceived; }));
+    auto bobMsgSize = bobData.messages.size();
+    auto bob2MsgSize = bob2Data.messages.size();
+    libjami::sendMessage(aliceId, aliceData.conversationId, "hi"s, "");
+    CPPUNIT_ASSERT(!cv.wait_for(lk, 30s, [&]() { return bobMsgSize != bobData.messages.size() && bob2MsgSize != bob2Data.messages.size(); }));
 
     // Bobs re-add Alice
-    contactAddedBob = false, contactAddedBob2 = false;
     bobAccount->addContact(aliceUri);
     bobAccount->sendTrustRequest(aliceUri, {});
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return contactAddedBob && contactAddedBob2; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.conversationId == aliceData.conversationId && bob2Data.conversationId == aliceData.conversationId; }));
 
     // Alice can sends some messages now
-    bobMsgReceived = false, bob2MsgReceived = false;
-    libjami::sendMessage(aliceId, convId, "hi"s, "");
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobMsgReceived && bob2MsgReceived; }));
+    bobMsgSize = bobData.messages.size();
+    bob2MsgSize = bob2Data.messages.size();
+    libjami::sendMessage(aliceId, aliceData.conversationId, "hi"s, "");
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobMsgSize != bobData.messages.size() && bob2MsgSize != bob2Data.messages.size(); }));
 }
 
 void
 ConversationMembersEventTest::testBanHostWhileHosting()
 {
-    auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
+    connectSignals();
+
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
-    auto aliceUri = aliceAccount->getUsername();
     auto bobUri = bobAccount->getUsername();
     auto convId = libjami::startConversation(aliceId);
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    bool conversationReady = false, requestReceived = false;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
-            [&](const std::string& /*accountId*/,
-                const std::string& /* conversationId */,
-                std::map<std::string, std::string> /*metadatas*/) {
-                requestReceived = true;
-                cv.notify_one();
-            }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& conversationId) {
-            if (accountId == bobId && conversationId == convId) {
-                conversationReady = true;
-                cv.notify_one();
-            }
-        }));
-    bool memberMessageGenerated = false, callMessageGenerated = false, voteMessageGenerated = false;
-    confHandlers.insert(libjami::exportable_callback<libjami::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;
-                cv.notify_one();
-            } else if (accountId == aliceId && conversationId == convId) {
-                if (message["type"] == "application/call-history+json") {
-                    callMessageGenerated = true;
-                } else if (message["type"] == "member") {
-                    memberMessageGenerated = true;
-                }
-                cv.notify_one();
-            }
-        }));
-    libjami::registerSignalHandlers(confHandlers);
+
     libjami::addConversationMember(aliceId, convId, bobUri);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; }));
-    memberMessageGenerated = false;
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
+    auto aliceMsgSize = aliceData.messages.size();
     libjami::acceptConversationRequest(bobId, convId);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return memberMessageGenerated; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 1 == aliceData.messages.size(); }));
 
     // Now, Bob starts a call
+    aliceMsgSize = aliceData.messages.size();
     auto callId = libjami::placeCallWithMedia(bobId, "swarm:" + convId, {});
     // should get message
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return callMessageGenerated; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize != aliceData.messages.size(); }));
 
     // get active calls = 1
     CPPUNIT_ASSERT(libjami::getActiveCalls(aliceId, convId).size() == 1);
 
     // Now check that alice, has the only admin, can remove bob
-    memberMessageGenerated = false;
-    voteMessageGenerated = false;
+    aliceMsgSize = aliceData.messages.size();
     libjami::removeConversationMember(aliceId, convId, bobUri);
     CPPUNIT_ASSERT(
-        cv.wait_for(lk, 30s, [&]() { return memberMessageGenerated && voteMessageGenerated; }));
+        cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 2 == aliceData.messages.size(); }));
     auto members = libjami::getConversationMembers(aliceId, convId);
     auto bobBanned = false;
     for (auto& member : members) {
@@ -2621,141 +1607,120 @@ ConversationMembersEventTest::testBanHostWhileHosting()
             bobBanned = member["role"] == "banned";
     }
     CPPUNIT_ASSERT(bobBanned);
-
-    libjami::unregisterSignalHandlers();
 }
 
 void
 ConversationMembersEventTest::testRemoveContactTwice()
 {
+    connectSignals();
+
     std::cout << "\nRunning test: " << __func__ << std::endl;
 
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto bobUri = bobAccount->getUsername();
     auto aliceUri = aliceAccount->getUsername();
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    bool requestReceived = false;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
-            [&](const std::string& accountId,
-                const std::string&,
-                std::map<std::string, std::string> /*metadatas*/) {
-                if (accountId == bobId)
-                    requestReceived = true;
-                cv.notify_one();
-            }));
-    std::string convId = ""; auto conversationReady = false;
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& conversationId) {
-            if (accountId == aliceId)
-                convId = conversationId;
-            else if (accountId == bobId)
-                conversationReady = true;
-            cv.notify_one();
-        }));
-    auto conversationRemoved = false;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRemoved>(
-            [&](const std::string& accountId, const std::string&) {
-                if (accountId == bobId)
-                    conversationRemoved = true;
-                cv.notify_one();
-            }));
-    auto contactRemoved = false;
-    confHandlers.insert(libjami::exportable_callback<libjami::ConfigurationSignal::ContactRemoved>(
-        [&](const std::string& accountId, const std::string& uri, bool) {
-            if (accountId == bobId && uri == aliceUri)
-                contactRemoved = true;
-            cv.notify_one();
-        }));
-    auto memberMessageGenerated = false;
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::MessageReceived>(
-        [&](const std::string& accountId,
-            const std::string& /* conversationId */,
-            std::map<std::string, std::string> message) {
-            if (accountId == aliceId && message["type"] == "member")
-                memberMessageGenerated = true;
-            cv.notify_one();
-        }));
-    libjami::registerSignalHandlers(confHandlers);
-    requestReceived = false;
+
     aliceAccount->addContact(bobUri);
     aliceAccount->sendTrustRequest(bobUri, {});
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
+    auto aliceMsgSize = aliceData.messages.size();
     CPPUNIT_ASSERT(bobAccount->acceptTrustRequest(aliceUri));
     CPPUNIT_ASSERT(
-        cv.wait_for(lk, 30s, [&]() { return conversationReady && memberMessageGenerated; }));
+        cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 1 == aliceData.messages.size(); }));
     // removeContact
     bobAccount->removeContact(aliceUri, false);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return contactRemoved; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.removed; }));
     // wait that connections are closed.
     std::this_thread::sleep_for(10s);
     // re-add via a new message. Trigger a new request
-    requestReceived = false;
-    libjami::sendMessage(aliceId, convId, "foo"s, "");
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; }));
+    bobData.requestReceived = false;
+    libjami::sendMessage(aliceId, aliceData.conversationId, "foo"s, "");
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
     // removeContact again (should remove the trust request/conversation)
-    contactRemoved = false;
+    bobData.removed = false;
     bobAccount->removeContact(aliceUri, false);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return contactRemoved; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.removed; }));
 }
 
 void
 ConversationMembersEventTest::testAddContactTwice()
 {
+    connectSignals();
     std::cout << "\nRunning test: " << __func__ << std::endl;
 
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto bobUri = bobAccount->getUsername();
-    auto aliceUri = aliceAccount->getUsername();
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    bool requestReceived = false;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
-            [&](const std::string& accountId,
-                const std::string&,
-                std::map<std::string, std::string> /*metadatas*/) {
-                if (accountId == bobId)
-                    requestReceived = true;
-                cv.notify_one();
-            }));
-    std::string convId = "";
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& conversationId) {
-            if (accountId == aliceId)
-                convId = conversationId;
-            cv.notify_one();
-        }));
-    auto requestDeclined = false;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestDeclined>(
-            [&](const std::string& accountId, const std::string&) {
-                if (accountId == bobId)
-                    requestDeclined = true;
-                cv.notify_one();
-            }));
-    auto contactRemoved = false;
-    confHandlers.insert(libjami::exportable_callback<libjami::ConfigurationSignal::ContactRemoved>(
-        [&](const std::string& accountId, const std::string& uri, bool) {
-            if (accountId == aliceId && uri == bobUri)
-                contactRemoved = true;
-            cv.notify_one();
-        }));
-    libjami::registerSignalHandlers(confHandlers);
-    requestReceived = false;
+
     aliceAccount->addContact(bobUri);
     aliceAccount->sendTrustRequest(bobUri, {});
-    CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&]() { return requestReceived; }));
-    requestReceived = false;
+    CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&]() { return bobData.requestReceived; }));
     aliceAccount->removeContact(bobUri, false);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&]() { return contactRemoved; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&]() { return aliceData.removed; }));
     // wait that connections are closed.
     std::this_thread::sleep_for(10s);
+    bobData.requestReceived = false;
     aliceAccount->addContact(bobUri);
     aliceAccount->sendTrustRequest(bobUri, {});
-    CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&]() { return requestDeclined && requestReceived; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&]() { return bobData.requestRemoved && bobData.requestReceived; }));
+}
+
+void
+ConversationMembersEventTest::testBanFromNewDevice()
+{
+    connectSignals();
+
+    auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
+    auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
+    auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
+    auto aliceUri = aliceAccount->getUsername();
+    auto bobUri = bobAccount->getUsername();
+    auto carlaUri = carlaAccount->getUsername();
+
+    Manager::instance().sendRegister(carlaId, true);
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return carlaData.deviceAnnounced; }));
+
+    auto convId = libjami::startConversation(bobId);
+
+    libjami::addConversationMember(bobId, convId, aliceUri);
+    libjami::addConversationMember(bobId, convId, carlaUri);
+    CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&] { return carlaData.requestReceived; }));
+
+    libjami::acceptConversationRequest(carlaId, convId);
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !carlaData.conversationId.empty(); }));
+
+    Manager::instance().sendRegister(carlaId, false);
+
+    // Bob creates a second device
+    auto bobArchive = std::filesystem::current_path().string() + "/bob.gz";
+    std::remove(bobArchive.c_str());
+    bobAccount->exportArchive(bobArchive);
+    std::map<std::string, std::string> details = libjami::getAccountTemplate("RING");
+    details[ConfProperties::TYPE] = "RING";
+    details[ConfProperties::DISPLAYNAME] = "BOB2";
+    details[ConfProperties::ALIAS] = "BOB2";
+    details[ConfProperties::UPNP_ENABLED] = "true";
+    details[ConfProperties::ARCHIVE_PASSWORD] = "";
+    details[ConfProperties::ARCHIVE_PIN] = "";
+    details[ConfProperties::ARCHIVE_PATH] = bobArchive;
+    bob2Id = Manager::instance().addAccount(details);
+
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bob2Data.deviceAnnounced; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !bob2Data.conversationId.empty(); }));
+
+
+    auto bobMsgSize = bobData.messages.size();
+    libjami::removeConversationMember(bob2Id, convId, aliceUri);
+    CPPUNIT_ASSERT(
+        cv.wait_for(lk, 30s, [&]() { return bobData.messages.size() == bobMsgSize + 2 /* vote + ban */; }));
+
+    Manager::instance().sendRegister(bob2Id, false);
+    auto carlaMsgSize = carlaData.messages.size();
+    Manager::instance().sendRegister(carlaId, true);
+    // Should sync!
+    CPPUNIT_ASSERT(
+        cv.wait_for(lk, 30s, [&]() { return carlaData.messages.size() > carlaMsgSize; }));
 }
 
 } // namespace test
diff --git a/test/unitTest/conversation/conversationRequest.cpp b/test/unitTest/conversation/conversationRequest.cpp
index 646ddfc7a32cd141893d2554b0767dc0581a4242..4bb753fd871fa817e9dc1b5d57a4915463e6265e 100644
--- a/test/unitTest/conversation/conversationRequest.cpp
+++ b/test/unitTest/conversation/conversationRequest.cpp
@@ -46,6 +46,22 @@ using namespace libjami::Account;
 namespace jami {
 namespace test {
 
+struct UserData {
+    std::string conversationId;
+    bool removed {false};
+    bool requestReceived {false};
+    bool requestRemoved {false};
+    bool registered {false};
+    bool stopped {false};
+    bool deviceAnnounced {false};
+    bool contactAdded {false};
+    bool contactRemoved {false};
+    std::string profilePath;
+    std::string payloadTrustRequest;
+    std::vector<libjami::SwarmMessage> messages;
+    std::vector<libjami::SwarmMessage> messagesUpdated;
+};
+
 class ConversationRequestTest : public CppUnit::TestFixture
 {
 public:
@@ -62,7 +78,6 @@ public:
     void testDeclineConversationRequestRemoveTrustRequest();
     void testMalformedTrustRequest();
     void testAddContactDeleteAndReAdd();
-    void testInviteFromMessageAfterRemoved();
     void testRemoveContact();
     void testRemoveContactMultiDevice();
     void testRemoveSelfDoesntRemoveConversation();
@@ -79,9 +94,19 @@ public:
     void testRemoveContactRemoveTrustRequest();
     void testAddConversationNoPresenceThenConnects();
     std::string aliceId;
+    UserData aliceData;
     std::string bobId;
+    UserData bobData;
     std::string bob2Id;
+    UserData bob2Data;
     std::string carlaId;
+    UserData carlaData;
+
+    std::mutex mtx;
+    std::unique_lock<std::mutex> lk {mtx};
+    std::condition_variable cv;
+
+    void connectSignals();
 
 private:
     CPPUNIT_TEST_SUITE(ConversationRequestTest);
@@ -93,7 +118,6 @@ private:
     CPPUNIT_TEST(testDeclineConversationRequestRemoveTrustRequest);
     CPPUNIT_TEST(testMalformedTrustRequest);
     CPPUNIT_TEST(testAddContactDeleteAndReAdd);
-    CPPUNIT_TEST(testInviteFromMessageAfterRemoved);
     CPPUNIT_TEST(testRemoveContact);
     CPPUNIT_TEST(testRemoveContactMultiDevice);
     CPPUNIT_TEST(testRemoveSelfDoesntRemoveConversation);
@@ -127,10 +151,178 @@ ConversationRequestTest::setUp()
     aliceId = actors["alice"];
     bobId = actors["bob"];
     carlaId = actors["carla"];
+    aliceData = {};
+    bobData = {};
+    bob2Data = {};
+    carlaData = {};
 
     Manager::instance().sendRegister(carlaId, false);
     wait_for_announcement_of({aliceId, bobId});
 }
+void
+ConversationRequestTest::connectSignals()
+{
+    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
+    confHandlers.insert(
+        libjami::exportable_callback<libjami::ConfigurationSignal::VolatileDetailsChanged>(
+            [&](const std::string& accountId, const std::map<std::string, std::string>&) {
+                if (accountId == aliceId) {
+                    auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
+                    auto details = aliceAccount->getVolatileAccountDetails();
+                    auto daemonStatus = details[libjami::Account::ConfProperties::Registration::STATUS];
+                    if (daemonStatus == "REGISTERED") {
+                        aliceData.registered = true;
+                    } else if (daemonStatus == "UNREGISTERED") {
+                        aliceData.stopped = true;
+                    }
+                    auto deviceAnnounced = details[libjami::Account::VolatileProperties::DEVICE_ANNOUNCED];
+                    aliceData.deviceAnnounced = deviceAnnounced == "true";
+                } else if (accountId == bobId) {
+                    auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
+                    auto details = bobAccount->getVolatileAccountDetails();
+                    auto daemonStatus = details[libjami::Account::ConfProperties::Registration::STATUS];
+                    if (daemonStatus == "REGISTERED") {
+                        bobData.registered = true;
+                    } else if (daemonStatus == "UNREGISTERED") {
+                        bobData.stopped = true;
+                    }
+                    auto deviceAnnounced = details[libjami::Account::VolatileProperties::DEVICE_ANNOUNCED];
+                    bobData.deviceAnnounced = deviceAnnounced == "true";
+                } else if (accountId == bob2Id) {
+                    auto bob2Account = Manager::instance().getAccount<JamiAccount>(bob2Id);
+                    auto details = bob2Account->getVolatileAccountDetails();
+                    auto daemonStatus = details[libjami::Account::ConfProperties::Registration::STATUS];
+                    if (daemonStatus == "REGISTERED") {
+                        bob2Data.registered = true;
+                    } else if (daemonStatus == "UNREGISTERED") {
+                        bob2Data.stopped = true;
+                    }
+                    auto deviceAnnounced = details[libjami::Account::VolatileProperties::DEVICE_ANNOUNCED];
+                    bob2Data.deviceAnnounced = deviceAnnounced == "true";
+                } else if (accountId == carlaId) {
+                    auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
+                    auto details = carlaAccount->getVolatileAccountDetails();
+                    auto daemonStatus = details[libjami::Account::ConfProperties::Registration::STATUS];
+                    if (daemonStatus == "REGISTERED") {
+                        carlaData.registered = true;
+                    } else if (daemonStatus == "UNREGISTERED") {
+                        carlaData.stopped = true;
+                    }
+                    auto deviceAnnounced = details[libjami::Account::VolatileProperties::DEVICE_ANNOUNCED];
+                    carlaData.deviceAnnounced = deviceAnnounced == "true";
+                }
+                cv.notify_one();
+            }));
+    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
+        [&](const std::string& accountId, const std::string& conversationId) {
+            if (accountId == aliceId) {
+                aliceData.conversationId = conversationId;
+            } else if (accountId == bobId) {
+                bobData.conversationId = conversationId;
+            } else if (accountId == bob2Id) {
+                bob2Data.conversationId = conversationId;
+            } else if (accountId == carlaId) {
+                carlaData.conversationId = conversationId;
+            }
+            cv.notify_one();
+        }));
+    confHandlers.insert(
+        libjami::exportable_callback<libjami::ConfigurationSignal::IncomingTrustRequest>(
+            [&](const std::string& account_id,
+                const std::string& /*from*/,
+                const std::string& /*conversationId*/,
+                const std::vector<uint8_t>& payload,
+                time_t /*received*/) {
+                auto payloadStr = std::string(payload.data(), payload.data() + payload.size());
+                if (account_id == aliceId)
+                    aliceData.payloadTrustRequest = payloadStr;
+                else if (account_id == bobId)
+                    bobData.payloadTrustRequest = payloadStr;
+                cv.notify_one();
+            }));
+    confHandlers.insert(
+        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
+            [&](const std::string& accountId,
+                const std::string& /* conversationId */,
+                std::map<std::string, std::string> /*metadatas*/) {
+                if (accountId == aliceId) {
+                    aliceData.requestReceived = true;
+                } else if (accountId == bobId) {
+                    bobData.requestReceived = true;
+                } else if (accountId == bob2Id) {
+                    bob2Data.requestReceived = true;
+                } else if (accountId == carlaId) {
+                    carlaData.requestReceived = true;
+                }
+                cv.notify_one();
+            }));
+    confHandlers.insert(
+        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestDeclined>(
+            [&](const std::string& accountId, const std::string&) {
+                if (accountId == bobId) {
+                    bobData.requestRemoved = true;
+                } else if (accountId == bob2Id) {
+                    bob2Data.requestRemoved = true;
+                }
+                cv.notify_one();
+            }));
+    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::SwarmMessageReceived>(
+        [&](const std::string& accountId,
+            const std::string& /* conversationId */,
+            libjami::SwarmMessage message) {
+            if (accountId == aliceId) {
+                aliceData.messages.emplace_back(message);
+            } else if (accountId == bobId) {
+                bobData.messages.emplace_back(message);
+            } else if (accountId == carlaId) {
+                carlaData.messages.emplace_back(message);
+            }
+            cv.notify_one();
+        }));
+    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::SwarmMessageUpdated>(
+        [&](const std::string& accountId,
+            const std::string& /* conversationId */,
+            libjami::SwarmMessage message) {
+            if (accountId == aliceId) {
+                aliceData.messagesUpdated.emplace_back(message);
+            } else if (accountId == bobId) {
+                bobData.messagesUpdated.emplace_back(message);
+            } else if (accountId == carlaId) {
+                carlaData.messagesUpdated.emplace_back(message);
+            }
+            cv.notify_one();
+        }));
+    confHandlers.insert(
+        libjami::exportable_callback<libjami::ConversationSignal::ConversationRemoved>(
+            [&](const std::string& accountId, const std::string&) {
+                if (accountId == aliceId)
+                    aliceData.removed = true;
+                else if (accountId == bobId)
+                    bobData.removed = true;
+                else if (accountId == bob2Id)
+                    bob2Data.removed = true;
+                cv.notify_one();
+            }));
+    confHandlers.insert(libjami::exportable_callback<libjami::ConfigurationSignal::ContactAdded>(
+        [&](const std::string& accountId, const std::string&, bool) {
+            if (accountId == bobId) {
+                bobData.contactAdded = true;
+            }
+            cv.notify_one();
+        }));
+    confHandlers.insert(libjami::exportable_callback<libjami::ConfigurationSignal::ContactRemoved>(
+        [&](const std::string& accountId, const std::string&, bool) {
+            if (accountId == bobId) {
+                bobData.contactRemoved = true;
+            } else if (accountId == bob2Id) {
+                bob2Data.contactRemoved = true;
+            }
+            cv.notify_one();
+        }));
+
+    libjami::registerSignalHandlers(confHandlers);
+}
+
 
 void
 ConversationRequestTest::tearDown()
@@ -148,224 +340,109 @@ ConversationRequestTest::tearDown()
 void
 ConversationRequestTest::testAcceptTrustRemoveConvReq()
 {
+    connectSignals();
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto bobUri = bobAccount->getUsername();
-    auto aliceUri = aliceAccount->getUsername();
-    std::mutex mtx;
-    std::unique_lock<std::mutex> lk {mtx};
-    std::condition_variable cv;
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    bool conversationReady = false, requestReceived = false;
-    std::string convId = "";
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
-            [&](const std::string& /*accountId*/,
-                const std::string& /* conversationId */,
-                std::map<std::string, std::string> /*metadatas*/) {
-                requestReceived = true;
-                cv.notify_one();
-            }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& conversationId) {
-            if (accountId == aliceId) {
-                convId = conversationId;
-            } else if (accountId == bobId) {
-                conversationReady = true;
-            }
-            cv.notify_one();
-        }));
-    libjami::registerSignalHandlers(confHandlers);
+
     aliceAccount->addContact(bobUri);
     aliceAccount->sendTrustRequest(bobUri, {});
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
 
     CPPUNIT_ASSERT(bobAccount->getTrustRequests().size() == 1);
-    libjami::acceptConversationRequest(bobId, convId);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return conversationReady; }));
+    libjami::acceptConversationRequest(bobId, aliceData.conversationId);
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !bobData.conversationId.empty(); }));
     CPPUNIT_ASSERT(bobAccount->getTrustRequests().size() == 0);
 }
 
 void
 ConversationRequestTest::acceptConvReqAlsoAddContact()
 {
+    connectSignals();
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto bobUri = bobAccount->getUsername();
-    auto aliceUri = aliceAccount->getUsername();
-    std::mutex mtx;
-    std::unique_lock<std::mutex> lk {mtx};
-    std::condition_variable cv;
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    bool requestReceived = false, memberMessageGenerated = false;
-    int conversationReady = 0;
-    std::string convId = "";
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
-            [&](const std::string& /*accountId*/,
-                const std::string& /* conversationId */,
-                std::map<std::string, std::string> /*metadatas*/) {
-                requestReceived = true;
-                cv.notify_one();
-            }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& conversationId) {
-            if (accountId == aliceId) {
-                convId = conversationId;
-            } else if (accountId == bobId) {
-                conversationReady += 1;
-            }
-            cv.notify_one();
-        }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::MessageReceived>(
-        [&](const std::string& accountId,
-            const std::string& conversationId,
-            std::map<std::string, std::string> message) {
-            if (accountId == aliceId && conversationId == convId && message["type"] == "member") {
-                memberMessageGenerated = true;
-                cv.notify_one();
-            }
-        }));
-    libjami::registerSignalHandlers(confHandlers);
+
     aliceAccount->addContact(bobUri);
     aliceAccount->sendTrustRequest(bobUri, {});
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
 
+    bobData.requestReceived = false;
     auto convId2 = libjami::startConversation(aliceId);
-    requestReceived = false;
     libjami::addConversationMember(aliceId, convId2, bobUri);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
 
     libjami::acceptConversationRequest(bobId, convId2);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return conversationReady == 2; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !bobData.conversationId.empty(); }));
+    std::this_thread::sleep_for(5s);
     CPPUNIT_ASSERT(bobAccount->getTrustRequests().size() == 0);
 }
 
 void
 ConversationRequestTest::testGetRequests()
 {
-    auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
+    connectSignals();
+
     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<libjami::CallbackWrapperBase>> confHandlers;
-    bool requestReceived = false;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
-            [&](const std::string& /*accountId*/,
-                const std::string& /* conversationId */,
-                std::map<std::string, std::string> /*metadatas*/) {
-                requestReceived = true;
-                cv.notify_one();
-            }));
-    libjami::registerSignalHandlers(confHandlers);
 
     auto convId = libjami::startConversation(aliceId);
 
     libjami::addConversationMember(aliceId, convId, bobUri);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
 
     auto requests = libjami::getConversationRequests(bobId);
     CPPUNIT_ASSERT(requests.size() == 1);
     CPPUNIT_ASSERT(requests.front()["id"] == convId);
-    libjami::unregisterSignalHandlers();
 }
 
 void
 ConversationRequestTest::testDeclineRequest()
 {
+    connectSignals();
+
     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<libjami::CallbackWrapperBase>> confHandlers;
-    bool requestReceived = false;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
-            [&](const std::string& /*accountId*/,
-                const std::string& /* conversationId */,
-                std::map<std::string, std::string> /*metadatas*/) {
-                requestReceived = true;
-                cv.notify_one();
-            }));
-    libjami::registerSignalHandlers(confHandlers);
-
     auto convId = libjami::startConversation(aliceId);
 
     libjami::addConversationMember(aliceId, convId, bobUri);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
 
     libjami::declineConversationRequest(bobId, convId);
     // Decline request
     auto requests = libjami::getConversationRequests(bobId);
     CPPUNIT_ASSERT(requests.size() == 0);
-    libjami::unregisterSignalHandlers();
 }
 
 void
 ConversationRequestTest::testAddContact()
 {
+    connectSignals();
+
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto bobUri = bobAccount->getUsername();
     auto aliceUri = aliceAccount->getUsername();
-    std::mutex mtx;
-    std::unique_lock<std::mutex> lk {mtx};
-    std::condition_variable cv;
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    bool conversationReady = false, requestReceived = false, memberMessageGenerated = false;
-    std::string convId = "";
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConfigurationSignal::IncomingTrustRequest>(
-            [&](const std::string& account_id,
-                const std::string& /*from*/,
-                const std::string& /*conversationId*/,
-                const std::vector<uint8_t>& /*payload*/,
-                time_t /*received*/) {
-                if (account_id == bobId)
-                    requestReceived = true;
-                cv.notify_one();
-            }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& conversationId) {
-            if (accountId == aliceId) {
-                convId = conversationId;
-            } else if (accountId == bobId) {
-                conversationReady = true;
-            }
-            cv.notify_one();
-        }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::MessageReceived>(
-        [&](const std::string& accountId,
-            const std::string& conversationId,
-            std::map<std::string, std::string> message) {
-            if (accountId == aliceId && conversationId == convId && message["type"] == "member") {
-                memberMessageGenerated = true;
-                cv.notify_one();
-            }
-        }));
-    libjami::registerSignalHandlers(confHandlers);
+
     aliceAccount->addContact(bobUri);
     aliceAccount->sendTrustRequest(bobUri, {});
-    CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&]() { return !convId.empty(); }));
-    ConversationRepository repo(aliceAccount, convId);
+    CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&]() { return !aliceData.conversationId.empty(); }));
+    ConversationRepository repo(aliceAccount, aliceData.conversationId);
     // Mode must be one to one
     CPPUNIT_ASSERT(repo.mode() == ConversationMode::ONE_TO_ONE);
     // Assert that repository exists
-    auto repoPath = fileutils::get_data_dir() / aliceAccount->getAccountID()
-                    / "conversations" / convId;
+    auto repoPath = fileutils::get_data_dir() / aliceId
+                    / "conversations" / aliceData.conversationId;
     CPPUNIT_ASSERT(std::filesystem::is_directory(repoPath));
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
+    auto aliceMsgSize = aliceData.messages.size();
     CPPUNIT_ASSERT(bobAccount->acceptTrustRequest(aliceUri));
     CPPUNIT_ASSERT(
-        cv.wait_for(lk, 30s, [&]() { return conversationReady && memberMessageGenerated; }));
-    auto clonedPath = fileutils::get_data_dir() / bobAccount->getAccountID()
-                      / "conversations" / convId;
+        cv.wait_for(lk, 30s, [&]() { return !bobData.conversationId.empty() && aliceMsgSize + 1 == aliceData.messages.size(); }));
+    auto clonedPath = fileutils::get_data_dir() / bobId
+                      / "conversations" / aliceData.conversationId;
     CPPUNIT_ASSERT(std::filesystem::is_directory(clonedPath));
     auto bobMember = clonedPath / "members" / (bobUri + ".crt");
     CPPUNIT_ASSERT(std::filesystem::is_regular_file(bobMember));
@@ -374,56 +451,22 @@ ConversationRequestTest::testAddContact()
 void
 ConversationRequestTest::testDeclineConversationRequestRemoveTrustRequest()
 {
+    connectSignals();
+
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto bobUri = bobAccount->getUsername();
-    auto aliceUri = aliceAccount->getUsername();
-    std::mutex mtx;
-    std::unique_lock<std::mutex> lk {mtx};
-    std::condition_variable cv;
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    bool conversationReady = false, requestReceived = false, memberMessageGenerated = false;
-    std::string convId = "";
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConfigurationSignal::IncomingTrustRequest>(
-            [&](const std::string& account_id,
-                const std::string& /*from*/,
-                const std::string& /*conversationId*/,
-                const std::vector<uint8_t>& /*payload*/,
-                time_t /*received*/) {
-                if (account_id == bobId)
-                    requestReceived = true;
-                cv.notify_one();
-            }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& conversationId) {
-            if (accountId == aliceId) {
-                convId = conversationId;
-            } else if (accountId == bobId) {
-                conversationReady = true;
-            }
-            cv.notify_one();
-        }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::MessageReceived>(
-        [&](const std::string& accountId,
-            const std::string& conversationId,
-            std::map<std::string, std::string> message) {
-            if (accountId == aliceId && conversationId == convId && message["type"] == "member") {
-                memberMessageGenerated = true;
-                cv.notify_one();
-            }
-        }));
-    libjami::registerSignalHandlers(confHandlers);
+
     aliceAccount->addContact(bobUri);
     aliceAccount->sendTrustRequest(bobUri, {});
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !convId.empty() && requestReceived; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
 
     // Decline request
     auto requests = libjami::getConversationRequests(bobId);
     CPPUNIT_ASSERT(requests.size() == 1);
     auto trustRequests = libjami::getTrustRequests(bobId);
     CPPUNIT_ASSERT(trustRequests.size() == 1);
-    libjami::declineConversationRequest(bobId, convId);
+    libjami::declineConversationRequest(bobId, aliceData.conversationId);
     requests = libjami::getConversationRequests(bobId);
     CPPUNIT_ASSERT(requests.size() == 0);
     trustRequests = libjami::getTrustRequests(bobId);
@@ -433,51 +476,15 @@ ConversationRequestTest::testDeclineConversationRequestRemoveTrustRequest()
 void
 ConversationRequestTest::testMalformedTrustRequest()
 {
+    connectSignals();
+
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto bobUri = bobAccount->getUsername();
-    auto aliceUri = aliceAccount->getUsername();
-    std::mutex mtx;
-    std::unique_lock<std::mutex> lk {mtx};
-    std::condition_variable cv;
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    bool conversationReady = false, requestReceived = false, memberMessageGenerated = false,
-         requestDeclined = false;
-    std::string convId = "";
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConfigurationSignal::IncomingTrustRequest>(
-            [&](const std::string& account_id,
-                const std::string& /*from*/,
-                const std::string& /*conversationId*/,
-                const std::vector<uint8_t>& /*payload*/,
-                time_t /*received*/) {
-                if (account_id == bobId)
-                    requestReceived = true;
-                cv.notify_one();
-            }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& conversationId) {
-            if (accountId == aliceId) {
-                convId = conversationId;
-            } else if (accountId == bobId) {
-                conversationReady = true;
-            }
-            cv.notify_one();
-        }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::MessageReceived>(
-        [&](const std::string& accountId,
-            const std::string& conversationId,
-            std::map<std::string, std::string> message) {
-            if (accountId == aliceId && conversationId == convId && message["type"] == "member") {
-                memberMessageGenerated = true;
-                cv.notify_one();
-            }
-        }));
 
-    libjami::registerSignalHandlers(confHandlers);
     aliceAccount->addContact(bobUri);
     aliceAccount->sendTrustRequest(bobUri, {});
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !convId.empty() && requestReceived; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
 
     // Decline request
     auto requests = libjami::getConversationRequests(bobId);
@@ -485,7 +492,7 @@ ConversationRequestTest::testMalformedTrustRequest()
     auto trustRequests = libjami::getTrustRequests(bobId);
     CPPUNIT_ASSERT(trustRequests.size() == 1);
     // This will let the trust request (not libjami::declineConversationRequest)
-    bobAccount->convModule()->declineConversationRequest(convId);
+    bobAccount->convModule()->declineConversationRequest(aliceData.conversationId);
     requests = libjami::getConversationRequests(bobId);
     CPPUNIT_ASSERT(requests.size() == 0);
     trustRequests = libjami::getTrustRequests(bobId);
@@ -497,6 +504,7 @@ ConversationRequestTest::testMalformedTrustRequest()
 
     auto start = std::chrono::steady_clock::now();
 
+    auto requestDeclined = false;
     do {
         trustRequests = libjami::getTrustRequests(bobId);
         requestDeclined = trustRequests.size() == 0;
@@ -510,227 +518,83 @@ ConversationRequestTest::testMalformedTrustRequest()
 void
 ConversationRequestTest::testAddContactDeleteAndReAdd()
 {
+    connectSignals();
+
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto bobUri = bobAccount->getUsername();
     auto aliceUri = aliceAccount->getUsername();
-    std::mutex mtx;
-    std::unique_lock<std::mutex> lk {mtx};
-    std::condition_variable cv;
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    bool conversationReady = false, requestReceived = false, memberMessageGenerated = false;
-    std::string convId = "";
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConfigurationSignal::IncomingTrustRequest>(
-            [&](const std::string& account_id,
-                const std::string& /*from*/,
-                const std::string& /*conversationId*/,
-                const std::vector<uint8_t>& /*payload*/,
-                time_t /*received*/) {
-                if (account_id == bobId)
-                    requestReceived = true;
-                cv.notify_one();
-            }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& conversationId) {
-            if (accountId == aliceId) {
-                convId = conversationId;
-            } else if (accountId == bobId) {
-                conversationReady = true;
-            }
-            cv.notify_one();
-        }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::MessageReceived>(
-        [&](const std::string& accountId,
-            const std::string& conversationId,
-            std::map<std::string, std::string> message) {
-            if (accountId == aliceId && conversationId == convId && message["type"] == "member") {
-                memberMessageGenerated = true;
-                cv.notify_one();
-            }
-        }));
-    libjami::registerSignalHandlers(confHandlers);
-    requestReceived = false;
+
     aliceAccount->addContact(bobUri);
     aliceAccount->sendTrustRequest(bobUri, {});
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
+    auto aliceMsgSize = aliceData.messages.size();
     CPPUNIT_ASSERT(bobAccount->acceptTrustRequest(aliceUri));
     CPPUNIT_ASSERT(
-        cv.wait_for(lk, 30s, [&]() { return conversationReady && memberMessageGenerated; }));
+        cv.wait_for(lk, 30s, [&]() { return !bobData.conversationId.empty() && aliceMsgSize + 1 == aliceData.messages.size(); }));
 
     // removeContact
     aliceAccount->removeContact(bobUri, false);
     std::this_thread::sleep_for(5s); // wait a bit that connections are closed
 
     // re-add
-    CPPUNIT_ASSERT(convId != "");
-    auto oldConvId = convId;
-    convId = "";
+    CPPUNIT_ASSERT(aliceData.conversationId != "");
+    auto oldConvId = aliceData.conversationId;
     aliceAccount->addContact(bobUri);
     aliceAccount->sendTrustRequest(bobUri, {});
     // Should retrieve previous conversation
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return oldConvId == convId; }));
-}
-
-void
-ConversationRequestTest::testInviteFromMessageAfterRemoved()
-{
-    auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
-    auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
-    auto bobUri = bobAccount->getUsername();
-    auto aliceUri = aliceAccount->getUsername();
-    std::mutex mtx;
-    std::unique_lock<std::mutex> lk {mtx};
-    std::condition_variable cv;
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    bool conversationReady = false, requestReceived = false, memberMessageGenerated = false;
-    std::string convId = "";
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
-            [&](const std::string& accountId,
-                const std::string&,
-                std::map<std::string, std::string> /*metadatas*/) {
-                if (accountId == bobId)
-                    requestReceived = true;
-                cv.notify_one();
-            }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& conversationId) {
-            if (accountId == aliceId) {
-                convId = conversationId;
-            } else if (accountId == bobId) {
-                conversationReady = true;
-            }
-            cv.notify_one();
-        }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::MessageReceived>(
-        [&](const std::string& accountId,
-            const std::string& conversationId,
-            std::map<std::string, std::string> message) {
-            if (accountId == aliceId && conversationId == convId && message["type"] == "member") {
-                memberMessageGenerated = true;
-                cv.notify_one();
-            }
-        }));
-    libjami::registerSignalHandlers(confHandlers);
-    requestReceived = false;
-    aliceAccount->addContact(bobUri);
-    aliceAccount->sendTrustRequest(bobUri, {});
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; }));
-    CPPUNIT_ASSERT(bobAccount->acceptTrustRequest(aliceUri));
-    CPPUNIT_ASSERT(
-        cv.wait_for(lk, 30s, [&]() { return conversationReady && memberMessageGenerated; }));
-
-    // removeContact
-    bobAccount->removeContact(aliceUri, false);
-    std::this_thread::sleep_for(10s); // wait a bit that connections are closed
-
-    // bob sends a message, this should generate a new request for Alice
-    requestReceived = false;
-    libjami::sendMessage(aliceId, convId, "hi"s, "");
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; }));
-    conversationReady = false;
-    CPPUNIT_ASSERT(bobAccount->getContacts().size() == 0);
-    libjami::acceptConversationRequest(bobId, convId);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return conversationReady; }));
-    CPPUNIT_ASSERT(bobAccount->getContacts().size() == 1);
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return oldConvId == aliceData.conversationId; }));
 }
 
 void
 ConversationRequestTest::testRemoveContact()
 {
+    connectSignals();
+
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto bobUri = bobAccount->getUsername();
     auto aliceUri = aliceAccount->getUsername();
-    std::mutex mtx;
-    std::unique_lock<std::mutex> lk {mtx};
-    std::condition_variable cv;
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    bool conversationReady = false, requestReceived = false, memberMessageGenerated = false;
-    std::string convId = "";
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConfigurationSignal::IncomingTrustRequest>(
-            [&](const std::string& account_id,
-                const std::string& /*from*/,
-                const std::string& /*conversationId*/,
-                const std::vector<uint8_t>& /*payload*/,
-                time_t /*received*/) {
-                if (account_id == bobId)
-                    requestReceived = true;
-                cv.notify_one();
-            }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& conversationId) {
-            if (accountId == aliceId) {
-                convId = conversationId;
-            } else if (accountId == bobId) {
-                conversationReady = true;
-            }
-            cv.notify_one();
-        }));
-    bool conversationRemovedAlice = false, conversationRemovedBob = false;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRemoved>(
-            [&](const std::string& accountId, const std::string&) {
-                if (accountId == aliceId)
-                    conversationRemovedAlice = true;
-                else if (accountId == bobId)
-                    conversationRemovedBob = true;
-                cv.notify_one();
-            }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::MessageReceived>(
-        [&](const std::string& accountId,
-            const std::string& conversationId,
-            std::map<std::string, std::string> message) {
-            if (accountId == aliceId && conversationId == convId && message["type"] == "member") {
-                memberMessageGenerated = true;
-            }
-            cv.notify_one();
-        }));
-    libjami::registerSignalHandlers(confHandlers);
+
     aliceAccount->addContact(bobUri);
     aliceAccount->sendTrustRequest(bobUri, {});
     // Check created files
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !convId.empty() && requestReceived; }));
-    memberMessageGenerated = false;
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
+    auto aliceMsgSize = aliceData.messages.size();
     CPPUNIT_ASSERT(bobAccount->acceptTrustRequest(aliceUri));
     CPPUNIT_ASSERT(
-        cv.wait_for(lk, 30s, [&]() { return conversationReady && memberMessageGenerated; }));
+        cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 1 == aliceData.messages.size(); }));
 
-    conversationRemovedBob = false;
     bobAccount->removeContact(aliceUri, false);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return conversationRemovedBob; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.removed; }));
 
     auto details = bobAccount->getContactDetails(aliceUri);
-    CPPUNIT_ASSERT(details.size() == 0);
+    CPPUNIT_ASSERT(details.find("removed") != details.end() && details["removed"] != "0");
 
-    conversationRemovedAlice = false;
     aliceAccount->removeContact(bobUri, false);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&]() { return conversationRemovedAlice; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&]() { return aliceData.removed; }));
 
     std::this_thread::sleep_for(
         10s); // There is no signal, but daemon should then erase the repository
 
-    auto repoPath = fileutils::get_data_dir() / aliceAccount->getAccountID()
-                    / "conversations" / convId;
+    auto repoPath = fileutils::get_data_dir() / aliceId
+                    / "conversations" / aliceData.conversationId;
     CPPUNIT_ASSERT(!std::filesystem::is_directory(repoPath));
 
-    repoPath = fileutils::get_data_dir() / bobAccount->getAccountID()
-               / "conversations" / convId;
+    repoPath = fileutils::get_data_dir() / bobId
+               / "conversations" / bobData.conversationId;
     CPPUNIT_ASSERT(!std::filesystem::is_directory(repoPath));
 }
 
 void
 ConversationRequestTest::testRemoveContactMultiDevice()
 {
+    connectSignals();
+
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto bobUri = bobAccount->getUsername();
     auto aliceUri = aliceAccount->getUsername();
-    std::mutex mtx;
-    std::unique_lock<std::mutex> lk {mtx};
-    std::condition_variable cv;
 
     // Add second device for Bob
     std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
@@ -749,50 +613,102 @@ ConversationRequestTest::testRemoveContactMultiDevice()
 
     bob2Id = Manager::instance().addAccount(details);
 
-    wait_for_announcement_of(bob2Id);
-    auto bob2Account = Manager::instance().getAccount<JamiAccount>(bob2Id);
-
-    bool requestB1Removed = false, requestB2Removed = false, requestB1Received = false, requestB2Received = false;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConfigurationSignal::IncomingTrustRequest>(
-            [&](const std::string& account_id,
-                const std::string& /*from*/,
-                const std::string& /*conversationId*/,
-                const std::vector<uint8_t>& /*payload*/,
-                time_t /*received*/) {
-                if (account_id == bobId)
-                    requestB1Received = true;
-                else if (account_id == bob2Id)
-                    requestB2Received = true;
-                cv.notify_one();
-            }));
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestDeclined>(
-            [&](const std::string& accountId, const std::string&) {
-                if (accountId == bobId) {
-                    requestB1Removed = true;
-                } else if (accountId == bob2Id) {
-                    requestB2Removed = true;
-                }
-                cv.notify_one();
-            }));
-    libjami::registerSignalHandlers(confHandlers);
-
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
+        return bob2Data.deviceAnnounced;
+    }));
     // First, Alice adds Bob
     aliceAccount->addContact(bobUri);
     aliceAccount->sendTrustRequest(bobUri, {});
     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
-        return requestB1Received && requestB2Received;
+        return bobData.requestReceived && bob2Data.requestReceived;
     }));
 
     // Bob1 decline via removeContact, both device should remove the request
     bobAccount->removeContact(aliceUri, false);
     CPPUNIT_ASSERT(
-        cv.wait_for(lk, 30s, [&]() { return requestB1Removed && requestB2Removed; }));
+        cv.wait_for(lk, 30s, [&]() { return bobData.requestRemoved && bob2Data.requestRemoved; }));
 }
 
 void
 ConversationRequestTest::testRemoveSelfDoesntRemoveConversation()
+{
+    connectSignals();
+
+    auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
+    auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
+    auto bobUri = bobAccount->getUsername();
+    auto aliceUri = aliceAccount->getUsername();
+
+    aliceAccount->addContact(bobUri);
+    aliceAccount->sendTrustRequest(bobUri, {});
+    // Check created files
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
+    auto aliceMsgSize = aliceData.messages.size();
+    CPPUNIT_ASSERT(bobAccount->acceptTrustRequest(aliceUri));
+    CPPUNIT_ASSERT(
+        cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 1 == aliceData.messages.size(); }));
+
+    aliceAccount->removeContact(aliceUri, false);
+    CPPUNIT_ASSERT(!cv.wait_for(lk, 10s, [&]() { return aliceData.removed; }));
+    auto repoPath = fileutils::get_data_dir() / aliceId
+                    / "conversations" / aliceData.conversationId;
+    CPPUNIT_ASSERT(std::filesystem::is_directory(repoPath));
+}
+
+void
+ConversationRequestTest::testRemoveConversationUpdateContactDetails()
+{
+    connectSignals();
+
+    auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
+    auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
+    auto bobUri = bobAccount->getUsername();
+    auto aliceUri = aliceAccount->getUsername();
+
+    aliceAccount->addContact(bobUri);
+    aliceAccount->sendTrustRequest(bobUri, {});
+    // Check created files
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
+    auto aliceMsgSize = aliceData.messages.size();
+    CPPUNIT_ASSERT(bobAccount->acceptTrustRequest(aliceUri));
+    CPPUNIT_ASSERT(
+        cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 1 == aliceData.messages.size(); }));
+
+    libjami::removeConversation(bobId, bobData.conversationId);
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.removed; }));
+
+    auto details = bobAccount->getContactDetails(aliceUri);
+    CPPUNIT_ASSERT(details[libjami::Account::TrustRequest::CONVERSATIONID] == "");
+}
+
+void
+ConversationRequestTest::testBanContact()
+{
+    connectSignals();
+
+    auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
+    auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
+    auto bobUri = bobAccount->getUsername();
+    auto aliceUri = aliceAccount->getUsername();
+
+    aliceAccount->addContact(bobUri);
+    aliceAccount->sendTrustRequest(bobUri, {});
+    // Check created files
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
+    auto aliceMsgSize = aliceData.messages.size();
+    CPPUNIT_ASSERT(bobAccount->acceptTrustRequest(aliceUri));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 1 == aliceData.messages.size(); }));
+
+    bobAccount->removeContact(aliceUri, true);
+    CPPUNIT_ASSERT(!cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 2 == aliceData.messages.size(); }));
+    auto repoPath = fileutils::get_data_dir() / bobId
+                    / "conversations" / bobData.conversationId;
+    CPPUNIT_ASSERT(std::filesystem::is_directory(repoPath));
+}
+
+
+void
+ConversationRequestTest::testBanContactRestartAccount()
 {
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
@@ -802,205 +718,7 @@ ConversationRequestTest::testRemoveSelfDoesntRemoveConversation()
     std::unique_lock<std::mutex> lk {mtx};
     std::condition_variable cv;
     std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    bool conversationReady = false, requestReceived = false, memberMessageGenerated = false,
-         conversationRemoved = false;
-    std::string convId = "";
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConfigurationSignal::IncomingTrustRequest>(
-            [&](const std::string& account_id,
-                const std::string& /*from*/,
-                const std::string& /*conversationId*/,
-                const std::vector<uint8_t>& /*payload*/,
-                time_t /*received*/) {
-                if (account_id == bobId)
-                    requestReceived = true;
-                cv.notify_one();
-            }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& conversationId) {
-            if (accountId == aliceId) {
-                convId = conversationId;
-            } else if (accountId == bobId) {
-                conversationReady = true;
-            }
-            cv.notify_one();
-        }));
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRemoved>(
-            [&](const std::string& accountId, const std::string&) {
-                if (accountId == bobId)
-                    conversationRemoved = true;
-                cv.notify_one();
-            }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::MessageReceived>(
-        [&](const std::string& accountId,
-            const std::string& conversationId,
-            std::map<std::string, std::string> message) {
-            if (accountId == aliceId && conversationId == convId && message["type"] == "member") {
-                memberMessageGenerated = true;
-            }
-            cv.notify_one();
-        }));
-    libjami::registerSignalHandlers(confHandlers);
-    aliceAccount->addContact(bobUri);
-    aliceAccount->sendTrustRequest(bobUri, {});
-    // Check created files
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !convId.empty() && requestReceived; }));
-    memberMessageGenerated = false;
-    CPPUNIT_ASSERT(bobAccount->acceptTrustRequest(aliceUri));
-    CPPUNIT_ASSERT(
-        cv.wait_for(lk, 30s, [&]() { return conversationReady && memberMessageGenerated; }));
-
-    conversationRemoved = false;
-    aliceAccount->removeContact(aliceUri, false);
-    CPPUNIT_ASSERT(!cv.wait_for(lk, 10s, [&]() { return conversationRemoved; }));
-    auto repoPath = fileutils::get_data_dir() / aliceAccount->getAccountID()
-                    / "conversations" / convId;
-    CPPUNIT_ASSERT(std::filesystem::is_directory(repoPath));
-}
-
-void
-ConversationRequestTest::testRemoveConversationUpdateContactDetails()
-{
-    auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
-    auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
-    auto bobUri = bobAccount->getUsername();
-    auto aliceUri = aliceAccount->getUsername();
-    std::mutex mtx;
-    std::unique_lock<std::mutex> lk {mtx};
-    std::condition_variable cv;
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    bool conversationReady = false, requestReceived = false, memberMessageGenerated = false,
-         conversationRemoved = false;
-    std::string convId = "";
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConfigurationSignal::IncomingTrustRequest>(
-            [&](const std::string& account_id,
-                const std::string& /*from*/,
-                const std::string& /*conversationId*/,
-                const std::vector<uint8_t>& /*payload*/,
-                time_t /*received*/) {
-                if (account_id == bobId)
-                    requestReceived = true;
-                cv.notify_one();
-            }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& conversationId) {
-            if (accountId == aliceId) {
-                convId = conversationId;
-            } else if (accountId == bobId) {
-                conversationReady = true;
-            }
-            cv.notify_one();
-        }));
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRemoved>(
-            [&](const std::string& accountId, const std::string&) {
-                if (accountId == bobId)
-                    conversationRemoved = true;
-                cv.notify_one();
-            }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::MessageReceived>(
-        [&](const std::string& accountId,
-            const std::string& conversationId,
-            std::map<std::string, std::string> message) {
-            if (accountId == aliceId && conversationId == convId && message["type"] == "member") {
-                memberMessageGenerated = true;
-            }
-            cv.notify_one();
-        }));
-    libjami::registerSignalHandlers(confHandlers);
-    aliceAccount->addContact(bobUri);
-    aliceAccount->sendTrustRequest(bobUri, {});
-    // Check created files
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !convId.empty() && requestReceived; }));
-    memberMessageGenerated = false;
-    CPPUNIT_ASSERT(bobAccount->acceptTrustRequest(aliceUri));
-    CPPUNIT_ASSERT(
-        cv.wait_for(lk, 30s, [&]() { return conversationReady && memberMessageGenerated; }));
-
-    conversationRemoved = false;
-    libjami::removeConversation(bobId, convId);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return conversationRemoved; }));
-
-    auto details = bobAccount->getContactDetails(aliceUri);
-    CPPUNIT_ASSERT(details[libjami::Account::TrustRequest::CONVERSATIONID] == "");
-}
-
-void
-ConversationRequestTest::testBanContact()
-{
-    auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
-    auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
-    auto bobUri = bobAccount->getUsername();
-    auto aliceUri = aliceAccount->getUsername();
-    std::mutex mtx;
-    std::unique_lock<std::mutex> lk {mtx};
-    std::condition_variable cv;
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    bool conversationReady = false, requestReceived = false, memberMessageGenerated = false;
-    std::string convId = "";
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConfigurationSignal::IncomingTrustRequest>(
-            [&](const std::string& account_id,
-                const std::string& /*from*/,
-                const std::string& /*conversationId*/,
-                const std::vector<uint8_t>& /*payload*/,
-                time_t /*received*/) {
-                if (account_id == bobId)
-                    requestReceived = true;
-                cv.notify_one();
-            }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& conversationId) {
-            if (accountId == aliceId) {
-                convId = conversationId;
-            } else if (accountId == bobId) {
-                conversationReady = true;
-            }
-            cv.notify_one();
-        }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::MessageReceived>(
-        [&](const std::string& accountId,
-            const std::string& conversationId,
-            std::map<std::string, std::string> message) {
-            if (accountId == aliceId && conversationId == convId && message["type"] == "member") {
-                memberMessageGenerated = true;
-            }
-            cv.notify_one();
-        }));
-    libjami::registerSignalHandlers(confHandlers);
-    aliceAccount->addContact(bobUri);
-    aliceAccount->sendTrustRequest(bobUri, {});
-    CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&]() { return !convId.empty(); }));
-    // Check created files
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; }));
-    memberMessageGenerated = false;
-    CPPUNIT_ASSERT(bobAccount->acceptTrustRequest(aliceUri));
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return conversationReady; }));
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return memberMessageGenerated; }));
-
-    memberMessageGenerated = false;
-    bobAccount->removeContact(aliceUri, true);
-    CPPUNIT_ASSERT(!cv.wait_for(lk, 30s, [&]() { return memberMessageGenerated; }));
-    auto repoPath = fileutils::get_data_dir() / bobAccount->getAccountID()
-                    / "conversations" / convId;
-    CPPUNIT_ASSERT(std::filesystem::is_directory(repoPath));
-}
-
-
-void
-ConversationRequestTest::testBanContactRestartAccount()
-{
-    auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
-    auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
-    auto bobUri = bobAccount->getUsername();
-    auto aliceUri = aliceAccount->getUsername();
-    std::mutex mtx;
-    std::unique_lock<std::mutex> lk {mtx};
-    std::condition_variable cv;
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    bool conversationReady = false, requestReceived = false, memberMessageGenerated = false;
+    bool conversationReady = false, requestReceived = false, memberMessageGenerated = false;
     std::string convId = "";
     confHandlers.insert(
         libjami::exportable_callback<libjami::ConfigurationSignal::IncomingTrustRequest>(
@@ -1088,50 +806,19 @@ ConversationRequestTest::testBanContactRestartAccount()
 void
 ConversationRequestTest::testBanContactRemoveTrustRequest()
 {
+    connectSignals();
+
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto bobUri = bobAccount->getUsername();
     auto aliceUri = aliceAccount->getUsername();
-    std::mutex mtx;
-    std::unique_lock<std::mutex> lk {mtx};
-    std::condition_variable cv;
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    bool conversationReady = false, requestReceived = false, requestDeclined = true;
-    std::string convId = "";
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConfigurationSignal::IncomingTrustRequest>(
-            [&](const std::string& account_id,
-                const std::string& /*from*/,
-                const std::string& /*conversationId*/,
-                const std::vector<uint8_t>& /*payload*/,
-                time_t /*received*/) {
-                if (account_id == bobId)
-                    requestReceived = true;
-                cv.notify_one();
-            }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& conversationId) {
-            if (accountId == aliceId) {
-                convId = conversationId;
-            } else if (accountId == bobId) {
-                conversationReady = true;
-            }
-            cv.notify_one();
-        }));
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestDeclined>(
-            [&](const std::string& accountId, const std::string&) {
-                if (accountId == bobId)
-                    requestDeclined = true;
-                cv.notify_one();
-            }));
-    libjami::registerSignalHandlers(confHandlers);
+
     aliceAccount->addContact(bobUri);
     aliceAccount->sendTrustRequest(bobUri, {});
     // Check created files
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
     bobAccount->removeContact(aliceUri, true);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&]() { return requestDeclined; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&]() { return bobData.requestRemoved; }));
     auto requests = libjami::getConversationRequests(bobId);
     CPPUNIT_ASSERT(requests.size() == 0);
 }
@@ -1139,194 +826,71 @@ ConversationRequestTest::testBanContactRemoveTrustRequest()
 void
 ConversationRequestTest::testAddOfflineContactThenConnect()
 {
+    connectSignals();
+
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
     auto carlaUri = carlaAccount->getUsername();
     auto aliceUri = aliceAccount->getUsername();
     aliceAccount->trackBuddyPresence(carlaUri, true);
 
-    std::mutex mtx;
-    std::unique_lock<std::mutex> lk {mtx};
-    std::condition_variable cv;
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    bool conversationReady = false, requestReceived = false, memberMessageGenerated = false;
-    std::string convId = "";
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConfigurationSignal::IncomingTrustRequest>(
-            [&](const std::string& account_id,
-                const std::string& /*from*/,
-                const std::string& /*conversationId*/,
-                const std::vector<uint8_t>& /*payload*/,
-                time_t /*received*/) {
-                if (account_id == carlaId)
-                    requestReceived = true;
-                cv.notify_one();
-            }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& conversationId) {
-            if (accountId == aliceId) {
-                convId = conversationId;
-            } else if (accountId == carlaId) {
-                conversationReady = true;
-            }
-            cv.notify_one();
-        }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::MessageReceived>(
-        [&](const std::string& accountId,
-            const std::string& conversationId,
-            std::map<std::string, std::string> message) {
-            if (accountId == aliceId && conversationId == convId && message["type"] == "member") {
-                memberMessageGenerated = true;
-                cv.notify_one();
-            }
-        }));
-    libjami::registerSignalHandlers(confHandlers);
     aliceAccount->addContact(carlaUri);
     aliceAccount->sendTrustRequest(carlaUri, {});
     cv.wait_for(lk, 5s); // Wait 5 secs for the put to happen
-    CPPUNIT_ASSERT(!convId.empty());
     Manager::instance().sendRegister(carlaId, true);
-    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(60), [&]() { return requestReceived; }));
-    memberMessageGenerated = false;
+    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(60), [&]() { return carlaData.requestReceived; }));
+    auto aliceMsgSize = aliceData.messages.size();
     CPPUNIT_ASSERT(carlaAccount->acceptTrustRequest(aliceUri));
     CPPUNIT_ASSERT(
-        cv.wait_for(lk, 30s, [&]() { return conversationReady && memberMessageGenerated; }));
+        cv.wait_for(lk, 30s, [&]() { return aliceData.messages.size() == aliceMsgSize + 1; }));
 }
 
 void
 ConversationRequestTest::testDeclineTrustRequestDoNotGenerateAnother()
 {
+    connectSignals();
+
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto bobUri = bobAccount->getUsername();
     auto aliceUri = aliceAccount->getUsername();
     aliceAccount->trackBuddyPresence(bobUri, true);
 
-    std::mutex mtx;
-    std::unique_lock<std::mutex> lk {mtx};
-    std::condition_variable cv;
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    bool conversationReady = false, requestReceived = false, memberMessageGenerated = false;
-    std::string convId = "";
-    auto bobConnected = false;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConfigurationSignal::IncomingTrustRequest>(
-            [&](const std::string& account_id,
-                const std::string& /*from*/,
-                const std::string& /*conversationId*/,
-                const std::vector<uint8_t>& /*payload*/,
-                time_t /*received*/) {
-                if (account_id == bobId)
-                    requestReceived = true;
-                cv.notify_one();
-            }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& conversationId) {
-            if (accountId == aliceId) {
-                convId = conversationId;
-            } else if (accountId == bobId) {
-                conversationReady = true;
-            }
-            cv.notify_one();
-        }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::MessageReceived>(
-        [&](const std::string& accountId,
-            const std::string& conversationId,
-            std::map<std::string, std::string> message) {
-            if (accountId == aliceId && conversationId == convId && message["type"] == "member") {
-                memberMessageGenerated = true;
-                cv.notify_one();
-            }
-        }));
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConfigurationSignal::VolatileDetailsChanged>(
-            [&](const std::string&, const std::map<std::string, std::string>&) {
-                auto details = bobAccount->getVolatileAccountDetails();
-                auto daemonStatus = details[libjami::Account::ConfProperties::Registration::STATUS];
-                if (daemonStatus == "REGISTERED") {
-                    bobConnected = true;
-                    cv.notify_one();
-                } else if (daemonStatus == "UNREGISTERED") {
-                    bobConnected = false;
-                    cv.notify_one();
-                }
-            }));
-    libjami::registerSignalHandlers(confHandlers);
     aliceAccount->addContact(bobUri);
     aliceAccount->sendTrustRequest(bobUri, {});
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
     CPPUNIT_ASSERT(bobAccount->discardTrustRequest(aliceUri));
     cv.wait_for(lk, 10s); // Wait a bit
-    bobConnected = true;
     Manager::instance().sendRegister(bobId, false);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !bobConnected; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.stopped; }));
     // Trigger on peer online
-    requestReceived = false;
+    bobData.deviceAnnounced = false; bobData.requestReceived = false;
     Manager::instance().sendRegister(bobId, true);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobConnected; }));
-    CPPUNIT_ASSERT(!cv.wait_for(lk, 30s, [&]() { return requestReceived; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.deviceAnnounced; }));
+    CPPUNIT_ASSERT(!cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
 }
 
 void
 ConversationRequestTest::testRemoveContactRemoveSyncing()
 {
+    connectSignals();
+
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto bobUri = bobAccount->getUsername();
     auto aliceUri = aliceAccount->getUsername();
-    std::mutex mtx;
-    std::unique_lock<std::mutex> lk {mtx};
-    std::condition_variable cv;
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    bool conversationReady = false, contactAdded = false, requestReceived = false;
-    std::string convId = "";
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConfigurationSignal::IncomingTrustRequest>(
-            [&](const std::string& account_id,
-                const std::string& /*from*/,
-                const std::string& convId,
-                const std::vector<uint8_t>& /*payload*/,
-                time_t /*received*/) {
-                if (account_id == bobId && !convId.empty())
-                    requestReceived = true;
-                cv.notify_one();
-            }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& conversationId) {
-            if (accountId == aliceId) {
-                convId = conversationId;
-            } else if (accountId == bobId) {
-                conversationReady = true;
-            }
-            cv.notify_one();
-        }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConfigurationSignal::ContactAdded>(
-        [&](const std::string& accountId, const std::string& uri, bool) {
-            if (accountId == bobId && uri == aliceUri) {
-                contactAdded = true;
-            }
-            cv.notify_one();
-        }));
-    bool contactRemoved = false;
-    confHandlers.insert(libjami::exportable_callback<libjami::ConfigurationSignal::ContactRemoved>(
-        [&](const std::string& accountId, const std::string& uri, bool) {
-            if (accountId == bobId && uri == aliceUri) {
-                contactRemoved = true;
-            }
-            cv.notify_one();
-        }));
-    libjami::registerSignalHandlers(confHandlers);
+
     aliceAccount->addContact(bobUri);
     aliceAccount->sendTrustRequest(bobUri, {});
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
 
     Manager::instance().sendRegister(aliceId, false); // This avoid to sync immediately
     CPPUNIT_ASSERT(bobAccount->acceptTrustRequest(aliceUri));
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return contactAdded; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.contactAdded; }));
 
     CPPUNIT_ASSERT(libjami::getConversations(bobId).size() == 1);
     bobAccount->removeContact(aliceUri, false);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return contactRemoved; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.contactRemoved; }));
 
     CPPUNIT_ASSERT(libjami::getConversations(bobId).size() == 0);
 }
@@ -1334,65 +898,25 @@ ConversationRequestTest::testRemoveContactRemoveSyncing()
 void
 ConversationRequestTest::testRemoveConversationRemoveSyncing()
 {
+    connectSignals();
+
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto bobUri = bobAccount->getUsername();
     auto aliceUri = aliceAccount->getUsername();
-    std::mutex mtx;
-    std::unique_lock<std::mutex> lk {mtx};
-    std::condition_variable cv;
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    bool conversationReady = false, contactAdded = false, requestReceived = false,
-         conversationRemoved = false;
-    std::string convId = "";
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConfigurationSignal::IncomingTrustRequest>(
-            [&](const std::string& account_id,
-                const std::string& /*from*/,
-                const std::string& convId,
-                const std::vector<uint8_t>& /*payload*/,
-                time_t /*received*/) {
-                if (account_id == bobId && !convId.empty())
-                    requestReceived = true;
-                cv.notify_one();
-            }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& conversationId) {
-            if (accountId == aliceId) {
-                convId = conversationId;
-            } else if (accountId == bobId) {
-                conversationReady = true;
-            }
-            cv.notify_one();
-        }));
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRemoved>(
-            [&](const std::string& accountId, const std::string&) {
-                if (accountId == bobId) {
-                    conversationRemoved = true;
-                }
-                cv.notify_one();
-            }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConfigurationSignal::ContactAdded>(
-        [&](const std::string& accountId, const std::string& uri, bool) {
-            if (accountId == bobId && uri == aliceUri) {
-                contactAdded = true;
-            }
-            cv.notify_one();
-        }));
-    libjami::registerSignalHandlers(confHandlers);
+
     aliceAccount->addContact(bobUri);
     aliceAccount->sendTrustRequest(bobUri, {});
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
 
     Manager::instance().sendRegister(aliceId, false); // This avoid to sync immediately
     CPPUNIT_ASSERT(bobAccount->acceptTrustRequest(aliceUri));
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return contactAdded; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.contactAdded; }));
     // At this point the conversation should be there and syncing.
 
     CPPUNIT_ASSERT(libjami::getConversations(bobId).size() == 1);
-    libjami::removeConversation(bobId, convId);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return conversationRemoved; }));
+    libjami::removeConversation(bobId, aliceData.conversationId);
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.removed; }));
 
     CPPUNIT_ASSERT(libjami::getConversations(bobId).size() == 0);
 }
@@ -1400,91 +924,47 @@ ConversationRequestTest::testRemoveConversationRemoveSyncing()
 void
 ConversationRequestTest::testCacheRequestFromClient()
 {
+    connectSignals();
+
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto bobUri = bobAccount->getUsername();
-    auto aliceUri = aliceAccount->getUsername();
-    std::mutex mtx;
-    std::unique_lock<std::mutex> lk {mtx};
-    std::condition_variable cv;
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    bool conversationReady = false, requestReceived = false;
-    std::string convId = "";
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
-            [&](const std::string& /*accountId*/,
-                const std::string& /* conversationId */,
-                std::map<std::string, std::string> /*metadatas*/) {
-                requestReceived = true;
-                cv.notify_one();
-            }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& conversationId) {
-            if (accountId == aliceId) {
-                convId = conversationId;
-            } else if (accountId == bobId) {
-                conversationReady = true;
-            }
-            cv.notify_one();
-        }));
-    libjami::registerSignalHandlers(confHandlers);
+
     aliceAccount->addContact(bobUri);
     std::vector<uint8_t> payload = {0x64, 0x64, 0x64};
     aliceAccount->sendTrustRequest(bobUri,
                                    payload); // Random payload, just care with the file
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; }));
-    auto cachedPath = fileutils::get_cache_dir() / aliceAccount->getAccountID()
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
+    auto cachedPath = fileutils::get_cache_dir() / aliceId
                       / "requests" / bobUri;
     CPPUNIT_ASSERT(std::filesystem::is_regular_file(cachedPath));
     CPPUNIT_ASSERT(fileutils::loadFile(cachedPath) == payload);
 
     CPPUNIT_ASSERT(bobAccount->getTrustRequests().size() == 1);
-    libjami::acceptConversationRequest(bobId, convId);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return conversationReady; }));
+    libjami::acceptConversationRequest(bobId, aliceData.conversationId);
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return !bobData.conversationId.empty(); }));
     CPPUNIT_ASSERT(!std::filesystem::is_regular_file(cachedPath));
 }
 
 void
 ConversationRequestTest::testNeedsSyncingWithForCloning()
 {
+    connectSignals();
+
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto bobUri = bobAccount->getUsername();
     auto aliceUri = aliceAccount->getUsername();
     auto aliceDevice = std::string(aliceAccount->currentDeviceId());
-    std::mutex mtx;
-    std::unique_lock<std::mutex> lk {mtx};
-    std::condition_variable cv;
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    bool contactAdded = false, requestReceived = false;
-    std::string convId = "";
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConfigurationSignal::IncomingTrustRequest>(
-            [&](const std::string& account_id,
-                const std::string& /*from*/,
-                const std::string& convId,
-                const std::vector<uint8_t>& /*payload*/,
-                time_t /*received*/) {
-                if (account_id == bobId && !convId.empty())
-                    requestReceived = true;
-                cv.notify_one();
-            }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConfigurationSignal::ContactAdded>(
-        [&](const std::string& accountId, const std::string& uri, bool) {
-            if (accountId == bobId && uri == aliceUri) {
-                contactAdded = true;
-            }
-            cv.notify_one();
-        }));
-    libjami::registerSignalHandlers(confHandlers);
+
     CPPUNIT_ASSERT(!bobAccount->convModule()->needsSyncingWith(aliceUri, aliceDevice));
     aliceAccount->addContact(bobUri);
     aliceAccount->sendTrustRequest(bobUri, {});
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
 
     Manager::instance().sendRegister(aliceId, false); // This avoid to sync immediately
     CPPUNIT_ASSERT(bobAccount->acceptTrustRequest(aliceUri));
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return contactAdded; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.contactAdded; }));
     // At this point the conversation should be there and syncing.
 
     CPPUNIT_ASSERT(libjami::getConversations(bobId).size() == 1);
@@ -1494,13 +974,12 @@ ConversationRequestTest::testNeedsSyncingWithForCloning()
 void
 ConversationRequestTest::testRemoveContactRemoveTrustRequest()
 {
+    connectSignals();
+
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto bobUri = bobAccount->getUsername();
     auto aliceUri = aliceAccount->getUsername();
-    std::mutex mtx;
-    std::unique_lock<std::mutex> lk {mtx};
-    std::condition_variable cv;
 
     // Add second device for Bob
     std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
@@ -1519,75 +998,29 @@ ConversationRequestTest::testRemoveContactRemoveTrustRequest()
 
     bob2Id = Manager::instance().addAccount(details);
 
-    wait_for_announcement_of(bob2Id);
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
+        return bob2Data.deviceAnnounced;
+    }));
     auto bob2Account = Manager::instance().getAccount<JamiAccount>(bob2Id);
 
-    bool conversationB1Ready = false, conversationB2Ready = false, conversationB1Removed = false,
-         conversationB2Removed = false, requestB1Received = false, requestB2Received = false,
-         memberMessageGenerated = false;
-    std::string convId = "";
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConfigurationSignal::IncomingTrustRequest>(
-            [&](const std::string& account_id,
-                const std::string& /*from*/,
-                const std::string& /*conversationId*/,
-                const std::vector<uint8_t>& /*payload*/,
-                time_t /*received*/) {
-                if (account_id == bobId)
-                    requestB1Received = true;
-                else if (account_id == bob2Id)
-                    requestB2Received = true;
-                cv.notify_one();
-            }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& conversationId) {
-            if (accountId == aliceId) {
-                convId = conversationId;
-            } else if (accountId == bobId) {
-                conversationB1Ready = true;
-            } else if (accountId == bob2Id) {
-                conversationB2Ready = true;
-            }
-            cv.notify_one();
-        }));
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRemoved>(
-            [&](const std::string& accountId, const std::string&) {
-                if (accountId == bobId) {
-                    conversationB1Removed = true;
-                } else if (accountId == bob2Id) {
-                    conversationB2Removed = true;
-                }
-                cv.notify_one();
-            }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::MessageReceived>(
-        [&](const std::string& accountId,
-            const std::string& conversationId,
-            std::map<std::string, std::string> message) {
-            if (accountId == aliceId && conversationId == convId && message["type"] == "member") {
-                memberMessageGenerated = true;
-                cv.notify_one();
-            }
-        }));
-    libjami::registerSignalHandlers(confHandlers);
-
     // First, Alice adds Bob
     aliceAccount->addContact(bobUri);
     aliceAccount->sendTrustRequest(bobUri, {});
     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
-        return !convId.empty() && requestB1Received && requestB2Received;
+        return bobData.requestReceived && bob2Data.requestReceived;
     }));
 
     // Bob1 accepts, both device should get it
+    auto aliceMsgSize = aliceData.messages.size();
     CPPUNIT_ASSERT(bobAccount->acceptTrustRequest(aliceUri));
     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
-        return conversationB1Ready && conversationB2Ready && memberMessageGenerated;
+        return !bobData.conversationId.empty() && !bob2Data.conversationId.empty() && aliceMsgSize + 1 == aliceData.messages.size();
     }));
 
     // Bob2 remove Alice ; Bob1 should not have any trust requests
     bob2Account->removeContact(aliceUri, false);
     CPPUNIT_ASSERT(
-        cv.wait_for(lk, 30s, [&]() { return conversationB1Removed && conversationB2Removed; }));
+        cv.wait_for(lk, 30s, [&]() { return bobData.contactRemoved && bob2Data.contactRemoved; }));
     std::this_thread::sleep_for(10s); // Wait a bit to ensure that everything is update (via synced)
     CPPUNIT_ASSERT(bobAccount->getTrustRequests().size() == 0);
     CPPUNIT_ASSERT(bob2Account->getTrustRequests().size() == 0);
diff --git a/test/unitTest/syncHistory/syncHistory.cpp b/test/unitTest/syncHistory/syncHistory.cpp
index 7c2d52469c50140936c0bd4d3ff56766aac11602..9d58e838882157a1d993e7089625bd2ff6abe1d1 100644
--- a/test/unitTest/syncHistory/syncHistory.cpp
+++ b/test/unitTest/syncHistory/syncHistory.cpp
@@ -41,6 +41,24 @@ using namespace std::literals::chrono_literals;
 namespace jami {
 namespace test {
 
+struct UserData {
+    std::string conversationId;
+    bool removed {false};
+    bool requestReceived {false};
+    bool requestRemoved {false};
+    bool errorDetected {false};
+    bool registered {false};
+    bool stopped {false};
+    bool deviceAnnounced {false};
+    bool sending {false};
+    bool sent {false};
+    std::string profilePath;
+    std::string payloadTrustRequest;
+    std::vector<libjami::SwarmMessage> messages;
+    std::vector<libjami::SwarmMessage> messagesLoaded;
+    std::vector<libjami::SwarmMessage> messagesUpdated;
+};
+
 class SyncHistoryTest : public CppUnit::TestFixture
 {
 public:
@@ -57,8 +75,16 @@ public:
     void tearDown();
 
     std::string aliceId;
+    UserData aliceData;
     std::string bobId;
+    UserData bobData;
     std::string alice2Id;
+    UserData alice2Data;
+
+    std::mutex mtx;
+    std::unique_lock<std::mutex> lk {mtx};
+    std::condition_variable cv;
+    void connectSignals();
 
 private:
     void testCreateConversationThenSync();
@@ -103,6 +129,9 @@ SyncHistoryTest::setUp()
     aliceId = actors["alice"];
     bobId = actors["bob"];
     alice2Id = "";
+    aliceData = {};
+    bobData = {};
+    alice2Data = {};
 }
 
 void
@@ -117,9 +146,201 @@ SyncHistoryTest::tearDown()
     }
 }
 
+void
+SyncHistoryTest::connectSignals()
+{
+    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
+    confHandlers.insert(
+        libjami::exportable_callback<libjami::ConfigurationSignal::VolatileDetailsChanged>(
+            [&](const std::string& accountId, const std::map<std::string, std::string>&) {
+                if (accountId == aliceId) {
+                    auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
+                    auto details = aliceAccount->getVolatileAccountDetails();
+                    auto daemonStatus = details[libjami::Account::ConfProperties::Registration::STATUS];
+                    if (daemonStatus == "REGISTERED") {
+                        aliceData.registered = true;
+                    } else if (daemonStatus == "UNREGISTERED") {
+                        aliceData.stopped = true;
+                    }
+                    auto deviceAnnounced = details[libjami::Account::VolatileProperties::DEVICE_ANNOUNCED];
+                    aliceData.deviceAnnounced = deviceAnnounced == "true";
+                } else if (accountId == bobId) {
+                    auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
+                    auto details = bobAccount->getVolatileAccountDetails();
+                    auto daemonStatus = details[libjami::Account::ConfProperties::Registration::STATUS];
+                    if (daemonStatus == "REGISTERED") {
+                        bobData.registered = true;
+                    } else if (daemonStatus == "UNREGISTERED") {
+                        bobData.stopped = true;
+                    }
+                    auto deviceAnnounced = details[libjami::Account::VolatileProperties::DEVICE_ANNOUNCED];
+                    bobData.deviceAnnounced = deviceAnnounced == "true";
+                } else if (accountId == alice2Id) {
+                    auto alice2Account = Manager::instance().getAccount<JamiAccount>(alice2Id);
+                    auto details = alice2Account->getVolatileAccountDetails();
+                    auto daemonStatus = details[libjami::Account::ConfProperties::Registration::STATUS];
+                    if (daemonStatus == "REGISTERED") {
+                        alice2Data.registered = true;
+                    } else if (daemonStatus == "UNREGISTERED") {
+                        alice2Data.stopped = true;
+                    }
+                    auto deviceAnnounced = details[libjami::Account::VolatileProperties::DEVICE_ANNOUNCED];
+                    alice2Data.deviceAnnounced = deviceAnnounced == "true";
+                }
+                cv.notify_one();
+            }));
+    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
+        [&](const std::string& accountId, const std::string& conversationId) {
+            if (accountId == aliceId) {
+                aliceData.conversationId = conversationId;
+            } else if (accountId == bobId) {
+                bobData.conversationId = conversationId;
+            } else if (accountId == alice2Id) {
+                alice2Data.conversationId = conversationId;
+            }
+            cv.notify_one();
+        }));
+    confHandlers.insert(libjami::exportable_callback<libjami::ConfigurationSignal::ProfileReceived>(
+        [&](const std::string& accountId, const std::string& peerId, const std::string& path) {
+            if (accountId == bobId)
+                bobData.profilePath = path;
+            else if (accountId == aliceId)
+                aliceData.profilePath = path;
+            else if (accountId == alice2Id)
+                alice2Data.profilePath = path;
+            cv.notify_one();
+        }));
+    confHandlers.insert(
+        libjami::exportable_callback<libjami::ConfigurationSignal::AccountMessageStatusChanged>(
+            [&](const std::string& accountId,
+                const std::string& /*conversationId*/,
+                const std::string& /*peer*/,
+                const std::string& /*msgId*/,
+                int status) {
+                if (accountId == aliceId) {
+                    if (status == 2)
+                        aliceData.sending = true;
+                    if (status == 3)
+                        aliceData.sent = true;
+                } else if (accountId == alice2Id) {
+                    if (status == 2)
+                        alice2Data.sending = true;
+                    if (status == 3)
+                        alice2Data.sent = true;
+                } else if (accountId == bobId) {
+                    if (status == 2)
+                        bobData.sending = true;
+                    if (status == 3)
+                        bobData.sent = true;
+                }
+                cv.notify_one();
+            }));
+    confHandlers.insert(
+        libjami::exportable_callback<libjami::ConfigurationSignal::IncomingTrustRequest>(
+            [&](const std::string& account_id,
+                const std::string& /*from*/,
+                const std::string& /*conversationId*/,
+                const std::vector<uint8_t>& payload,
+                time_t /*received*/) {
+                auto payloadStr = std::string(payload.data(), payload.data() + payload.size());
+                if (account_id == aliceId)
+                    aliceData.payloadTrustRequest = payloadStr;
+                else if (account_id == bobId)
+                    bobData.payloadTrustRequest = payloadStr;
+                cv.notify_one();
+            }));
+    confHandlers.insert(
+        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
+            [&](const std::string& accountId,
+                const std::string& /* conversationId */,
+                std::map<std::string, std::string> /*metadatas*/) {
+                if (accountId == aliceId) {
+                    aliceData.requestReceived = true;
+                } else if (accountId == bobId) {
+                    bobData.requestReceived = true;
+                } else if (accountId == alice2Id) {
+                    alice2Data.requestReceived = true;
+                }
+                cv.notify_one();
+            }));
+    confHandlers.insert(
+        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestDeclined>(
+            [&](const std::string& accountId, const std::string&) {
+                if (accountId == bobId) {
+                    bobData.requestRemoved = true;
+                } else if (accountId == aliceId) {
+                    aliceData.requestRemoved = true;
+                } else if (accountId == alice2Id) {
+                    alice2Data.requestRemoved = true;
+                }
+                cv.notify_one();
+            }));
+    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::SwarmMessageReceived>(
+        [&](const std::string& accountId,
+            const std::string& /* conversationId */,
+            libjami::SwarmMessage message) {
+            if (accountId == aliceId) {
+                aliceData.messages.emplace_back(message);
+            } else if (accountId == bobId) {
+                bobData.messages.emplace_back(message);
+            }
+            cv.notify_one();
+        }));
+    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::SwarmLoaded>(
+        [&](uint32_t, const std::string& accountId,
+            const std::string& /* conversationId */,
+            std::vector<libjami::SwarmMessage> messages) {
+            if (accountId == aliceId) {
+                aliceData.messagesLoaded.insert(aliceData.messagesLoaded.end(), messages.begin(), messages.end());
+            } else if (accountId == alice2Id) {
+                alice2Data.messagesLoaded.insert(alice2Data.messagesLoaded.end(), messages.begin(), messages.end());
+            } else if (accountId == bobId) {
+                bobData.messagesLoaded.insert(bobData.messagesLoaded.end(), messages.begin(), messages.end());
+            }
+            cv.notify_one();
+        }));
+    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::SwarmMessageUpdated>(
+        [&](const std::string& accountId,
+            const std::string& /* conversationId */,
+            libjami::SwarmMessage message) {
+            if (accountId == aliceId) {
+                aliceData.messagesUpdated.emplace_back(message);
+            } else if (accountId == bobId) {
+                bobData.messagesUpdated.emplace_back(message);
+            }
+            cv.notify_one();
+        }));
+    confHandlers.insert(
+        libjami::exportable_callback<libjami::ConversationSignal::OnConversationError>(
+            [&](const std::string& accountId,
+                const std::string& /* conversationId */,
+                int /*code*/,
+                const std::string& /* what */) {
+                if (accountId == aliceId)
+                    aliceData.errorDetected = true;
+                else if (accountId == bobId)
+                    bobData.errorDetected = true;
+                cv.notify_one();
+            }));
+    confHandlers.insert(
+        libjami::exportable_callback<libjami::ConversationSignal::ConversationRemoved>(
+            [&](const std::string& accountId, const std::string&) {
+                if (accountId == aliceId)
+                    aliceData.removed = true;
+                else if (accountId == bobId)
+                    bobData.removed = true;
+                else if (accountId == alice2Id)
+                    alice2Data.removed = true;
+                cv.notify_one();
+            }));
+    libjami::registerSignalHandlers(confHandlers);
+}
+
 void
 SyncHistoryTest::testCreateConversationThenSync()
 {
+    connectSignals();
+
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     // Start conversation
     auto convId = libjami::startConversation(aliceId);
@@ -136,37 +357,14 @@ SyncHistoryTest::testCreateConversationThenSync()
     details[ConfProperties::ARCHIVE_PIN] = "";
     details[ConfProperties::ARCHIVE_PATH] = aliceArchive;
 
-    std::mutex mtx;
-    std::unique_lock<std::mutex> lk {mtx};
-    std::condition_variable cv;
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    auto conversationReady = false, alice2Ready = false;
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& conversationId) {
-            if (accountId == alice2Id && conversationId == convId) {
-                conversationReady = true;
-                cv.notify_one();
-            }
-        }));
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConfigurationSignal::VolatileDetailsChanged>(
-            [&](const std::string& accountId, const std::map<std::string, std::string>& details) {
-                if (alice2Id != accountId) {
-                    return;
-                }
-                alice2Ready = details.at(libjami::Account::VolatileProperties::DEVICE_ANNOUNCED)
-                              == "true";
-                cv.notify_one();
-            }));
-    libjami::registerSignalHandlers(confHandlers);
     alice2Id = Manager::instance().addAccount(details);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return alice2Ready && conversationReady; }));
-    libjami::unregisterSignalHandlers();
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return !alice2Data.conversationId.empty(); }));
 }
 
 void
 SyncHistoryTest::testCreateConversationWithOnlineDevice()
 {
+    connectSignals();
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
 
     // Now create alice2
@@ -180,77 +378,26 @@ SyncHistoryTest::testCreateConversationWithOnlineDevice()
     details[ConfProperties::ARCHIVE_PASSWORD] = "";
     details[ConfProperties::ARCHIVE_PIN] = "";
     details[ConfProperties::ARCHIVE_PATH] = aliceArchive;
-
-    std::mutex mtx;
-    std::unique_lock<std::mutex> lk {mtx};
-    std::condition_variable cv;
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-
-    // Start conversation now
     auto convId = libjami::startConversation(aliceId);
-    auto conversationReady = false, alice2Ready = false;
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& conversationId) {
-            if (accountId == alice2Id && conversationId == convId) {
-                conversationReady = true;
-                cv.notify_one();
-            }
-        }));
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConfigurationSignal::VolatileDetailsChanged>(
-            [&](const std::string& accountId, const std::map<std::string, std::string>& details) {
-                if (alice2Id != accountId) {
-                    return;
-                }
-                alice2Ready = details.at(libjami::Account::VolatileProperties::DEVICE_ANNOUNCED)
-                              == "true";
-                cv.notify_one();
-            }));
-    libjami::registerSignalHandlers(confHandlers);
     alice2Id = Manager::instance().addAccount(details);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&] { return alice2Ready && conversationReady; }));
-    libjami::unregisterSignalHandlers();
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return !alice2Data.conversationId.empty(); }));
 }
 
 void
 SyncHistoryTest::testCreateConversationWithMessagesThenAddDevice()
 {
+    connectSignals();
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto convId = libjami::startConversation(aliceId);
 
-    std::mutex mtx;
-    std::unique_lock<std::mutex> lk {mtx};
-    std::condition_variable cv;
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    auto conversationReady = false;
-    auto messageReceived = false;
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::MessageReceived>(
-        [&](const std::string& /* accountId */,
-            const std::string& /* conversationId */,
-            std::map<std::string, std::string> /*message*/) {
-            messageReceived = true;
-            cv.notify_one();
-        }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& conversationId) {
-            if (accountId == alice2Id && conversationId == convId) {
-                conversationReady = true;
-                cv.notify_one();
-            }
-        }));
-    libjami::registerSignalHandlers(confHandlers);
-    confHandlers.clear();
-
     // Start conversation
-    messageReceived = false;
+    auto aliceMsgSize = aliceData.messages.size();
     libjami::sendMessage(aliceId, convId, std::string("Message 1"), "");
-    CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return messageReceived; }));
-    messageReceived = false;
+    CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return aliceMsgSize + 1 == aliceData.messages.size(); }));
     libjami::sendMessage(aliceId, convId, std::string("Message 2"), "");
-    CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return messageReceived; }));
-    messageReceived = false;
+    CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return aliceMsgSize + 2 == aliceData.messages.size(); }));
     libjami::sendMessage(aliceId, convId, std::string("Message 3"), "");
-    CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return messageReceived; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return aliceMsgSize + 3 == aliceData.messages.size(); }));
 
     // Now create alice2
     auto aliceArchive = std::filesystem::current_path().string() + "/alice.gz";
@@ -264,31 +411,15 @@ SyncHistoryTest::testCreateConversationWithMessagesThenAddDevice()
     details[ConfProperties::ARCHIVE_PIN] = "";
     details[ConfProperties::ARCHIVE_PATH] = aliceArchive;
     alice2Id = Manager::instance().addAccount(details);
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return !alice2Data.conversationId.empty(); }));
 
-    // Check if conversation is ready
-    CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&]() { return conversationReady; }));
-    std::vector<std::map<std::string, std::string>> messages;
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationLoaded>(
-        [&](uint32_t,
-            const std::string& accountId,
-            const std::string& conversationId,
-            std::vector<std::map<std::string, std::string>> msg) {
-            if (accountId == alice2Id && conversationId == convId) {
-                messages = msg;
-                cv.notify_one();
-            }
-        }));
-    libjami::registerSignalHandlers(confHandlers);
-    libjami::loadConversationMessages(alice2Id, convId, "", 0);
-    cv.wait_for(lk, 30s);
-    libjami::unregisterSignalHandlers();
-    confHandlers.clear();
+    libjami::loadConversation(alice2Id, convId, "", 0);
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return alice2Data.messagesLoaded.size() == 4; }));
 
     // Check messages
-    CPPUNIT_ASSERT(messages.size() == 4 /* 3 + initial */);
-    CPPUNIT_ASSERT(messages[0]["body"] == "Message 3");
-    CPPUNIT_ASSERT(messages[1]["body"] == "Message 2");
-    CPPUNIT_ASSERT(messages[2]["body"] == "Message 1");
+    CPPUNIT_ASSERT(alice2Data.messagesLoaded[0].body["body"] == "Message 3");
+    CPPUNIT_ASSERT(alice2Data.messagesLoaded[1].body["body"] == "Message 2");
+    CPPUNIT_ASSERT(alice2Data.messagesLoaded[2].body["body"] == "Message 1");
 }
 
 void
@@ -346,56 +477,25 @@ SyncHistoryTest::testCreateMultipleConversationThenAddDevice()
 
     // Check if conversation is ready
     CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&]() { return conversationReady == 4; }));
-    libjami::unregisterSignalHandlers();
 }
 
 void
 SyncHistoryTest::testReceivesInviteThenAddDevice()
 {
+    connectSignals();
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
 
     // Export alice
     auto aliceArchive = std::filesystem::current_path().string() + "/alice.gz";
     aliceAccount->exportArchive(aliceArchive);
-
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto uri = aliceAccount->getUsername();
 
     // Start conversation for Alice
     auto convId = libjami::startConversation(bobId);
 
-    // Check that alice receives the request
-    std::mutex mtx;
-    std::unique_lock<std::mutex> lk {mtx};
-    std::condition_variable cv;
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    auto requestReceived = false, memberEvent = false;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
-            [&](const std::string& accountId,
-                const std::string& conversationId,
-                std::map<std::string, std::string> /*metadatas*/) {
-                if (accountId == aliceId && conversationId == convId) {
-                    requestReceived = true;
-                    cv.notify_one();
-                }
-            }));
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationMemberEvent>(
-            [&](const std::string& /*accountId*/,
-                const std::string& /*conversationId*/,
-                const std::string& /*memberUri*/,
-                int /*event*/) {
-                memberEvent = true;
-                cv.notify_one();
-            }));
-    libjami::registerSignalHandlers(confHandlers);
-
-    memberEvent = false;
     libjami::addConversationMember(bobId, convId, uri);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return memberEvent && requestReceived; }));
-    libjami::unregisterSignalHandlers();
-    confHandlers.clear();
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return aliceData.requestReceived; }));
 
     // Now create alice2
     std::map<std::string, std::string> details = libjami::getAccountTemplate("RING");
@@ -408,26 +508,13 @@ SyncHistoryTest::testReceivesInviteThenAddDevice()
     details[ConfProperties::ARCHIVE_PATH] = aliceArchive;
     alice2Id = Manager::instance().addAccount(details);
 
-    requestReceived = false;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
-            [&](const std::string& accountId,
-                const std::string& conversationId,
-                std::map<std::string, std::string> /*metadatas*/) {
-                if (accountId == alice2Id && conversationId == convId) {
-                    requestReceived = true;
-                    cv.notify_one();
-                }
-            }));
-    libjami::registerSignalHandlers(confHandlers);
-
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return requestReceived; }));
-    libjami::unregisterSignalHandlers();
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return alice2Data.requestReceived; }));
 }
 
 void
 SyncHistoryTest::testRemoveConversationOnAllDevices()
 {
+    connectSignals();
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
 
     // Now create alice2
@@ -441,53 +528,18 @@ SyncHistoryTest::testRemoveConversationOnAllDevices()
     details[ConfProperties::ARCHIVE_PASSWORD] = "";
     details[ConfProperties::ARCHIVE_PIN] = "";
     details[ConfProperties::ARCHIVE_PATH] = aliceArchive;
-
-    std::mutex mtx;
-    std::unique_lock<std::mutex> lk {mtx};
-    std::condition_variable cv;
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-
-    // Start conversation now
     auto convId = libjami::startConversation(aliceId);
-    bool alice2Ready = false;
-    auto conversationReady = false, conversationRemoved = false;
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& conversationId) {
-            if (accountId == alice2Id && conversationId == convId) {
-                conversationReady = true;
-                cv.notify_one();
-            }
-        }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationRemoved>(
-        [&](const std::string& accountId, const std::string& conversationId) {
-            if (accountId == alice2Id && conversationId == convId) {
-                conversationRemoved = true;
-                cv.notify_one();
-            }
-        }));
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConfigurationSignal::VolatileDetailsChanged>(
-            [&](const std::string& accountId, const std::map<std::string, std::string>& details) {
-                if (alice2Id != accountId) {
-                    return;
-                }
-                alice2Ready = details.at(libjami::Account::VolatileProperties::DEVICE_ANNOUNCED)
-                              == "true";
-                cv.notify_one();
-            }));
-    libjami::registerSignalHandlers(confHandlers);
 
     alice2Id = Manager::instance().addAccount(details);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&] { return alice2Ready && conversationReady; }));
-    libjami::removeConversation(aliceId, convId);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return conversationRemoved; }));
-
-    libjami::unregisterSignalHandlers();
+    CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&] { return !alice2Data.conversationId.empty(); }));
+    libjami::removeConversation(aliceId, aliceData.conversationId);
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return alice2Data.removed; }));
 }
 
 void
 SyncHistoryTest::testSyncCreateAccountExportDeleteReimportOldBackup()
 {
+    connectSignals();
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto bobUri = bobAccount->getUsername();
@@ -499,65 +551,11 @@ SyncHistoryTest::testSyncCreateAccountExportDeleteReimportOldBackup()
     // Start conversation
     auto convId = libjami::startConversation(aliceId);
 
-    std::mutex mtx;
-    std::unique_lock<std::mutex> lk {mtx};
-    std::condition_variable cv;
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    auto messageBobReceived = 0, messageAliceReceived = 0;
-    bool requestReceived = false;
-    bool conversationReady = false;
-    bool alice2Ready = false;
-    confHandlers.insert(libjami::exportable_callback<libjami::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(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
-            [&](const std::string& /*accountId*/,
-                const std::string& /* conversationId */,
-                std::map<std::string, std::string> /*metadatas*/) {
-                requestReceived = true;
-                cv.notify_one();
-            }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& conversationId) {
-            if (accountId == bobId) {
-                conversationReady = true;
-                cv.notify_one();
-            }
-
-            if (accountId == alice2Id && conversationId == convId) {
-                conversationReady = true;
-                cv.notify_one();
-            }
-        }));
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConfigurationSignal::VolatileDetailsChanged>(
-            [&](const std::string& accountId, const std::map<std::string, std::string>& details) {
-                if (alice2Id != accountId) {
-                    return;
-                }
-                alice2Ready = details.at(libjami::Account::VolatileProperties::DEVICE_ANNOUNCED)
-                              == "true";
-                cv.notify_one();
-            }));
-    libjami::registerSignalHandlers(confHandlers);
-
     libjami::addConversationMember(aliceId, convId, bobUri);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; }));
-
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
+    auto aliceMsgSize = aliceData.messages.size();
     libjami::acceptConversationRequest(bobId, convId);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return conversationReady; }));
-
-    // Wait that alice sees Bob
-    cv.wait_for(lk, 30s, [&]() { return messageAliceReceived == 1; });
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 1 == aliceData.messages.size() && !bobData.conversationId.empty(); }));
 
     // disable account (same as removed)
     Manager::instance().sendRegister(aliceId, false);
@@ -571,27 +569,25 @@ SyncHistoryTest::testSyncCreateAccountExportDeleteReimportOldBackup()
     details[ConfProperties::ARCHIVE_PASSWORD] = "";
     details[ConfProperties::ARCHIVE_PIN] = "";
     details[ConfProperties::ARCHIVE_PATH] = aliceArchive;
-    requestReceived = false;
-    conversationReady = false;
     alice2Id = Manager::instance().addAccount(details);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return alice2Ready; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return alice2Data.deviceAnnounced; }));
 
     // This will trigger a conversation request. Cause alice2 can't know first conversation
     libjami::sendMessage(bobId, convId, std::string("hi"), "");
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return requestReceived; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return alice2Data.requestReceived; }));
 
     libjami::acceptConversationRequest(alice2Id, convId);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return conversationReady; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return !alice2Data.conversationId.empty(); }));
 
-    messageBobReceived = 0;
+    auto bobMsgSize = bobData.messages.size();
     libjami::sendMessage(alice2Id, convId, std::string("hi"), "");
-    cv.wait_for(lk, 30s, [&]() { return messageBobReceived == 1; });
-    libjami::unregisterSignalHandlers();
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobMsgSize + 1 == bobData.messages.size(); }));
 }
 
 void
 SyncHistoryTest::testSyncCreateAccountExportDeleteReimportWithConvId()
 {
+    connectSignals();
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto aliceUri = aliceAccount->getUsername();
@@ -600,76 +596,16 @@ SyncHistoryTest::testSyncCreateAccountExportDeleteReimportWithConvId()
     // Start conversation
     auto convId = libjami::startConversation(aliceId);
 
-    std::mutex mtx;
-    std::unique_lock<std::mutex> lk {mtx};
-    std::condition_variable cv;
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    auto messageBobReceived = 0;
-    bool requestReceived = false;
-    bool conversationReady = false;
-    bool alice2Ready = false;
-    bool memberAddGenerated = false;
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::MessageReceived>(
-        [&](const std::string& accountId,
-            const std::string& /* conversationId */,
-            std::map<std::string, std::string> /*message*/) {
-            if (accountId == bobId) {
-                messageBobReceived += 1;
-            }
-            cv.notify_one();
-        }));
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::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();
-            }));
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
-            [&](const std::string& /*accountId*/,
-                const std::string& /* conversationId */,
-                std::map<std::string, std::string> /*metadatas*/) {
-                requestReceived = true;
-                cv.notify_one();
-            }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& conversationId) {
-            if (conversationId != convId)
-                return;
-            if (accountId == bobId || accountId == alice2Id)
-                conversationReady = true;
-            cv.notify_one();
-        }));
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConfigurationSignal::VolatileDetailsChanged>(
-            [&](const std::string& accountId, const std::map<std::string, std::string>& details) {
-                if (alice2Id != accountId) {
-                    return;
-                }
-                alice2Ready = details.at(libjami::Account::VolatileProperties::DEVICE_ANNOUNCED)
-                              == "true";
-                cv.notify_one();
-            }));
-    libjami::registerSignalHandlers(confHandlers);
-
     libjami::addConversationMember(aliceId, convId, bobUri);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
 
+    auto aliceMsgSize = aliceData.messages.size();
     libjami::acceptConversationRequest(bobId, convId);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return conversationReady; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 1 == aliceData.messages.size(); }));
 
     // We need to track presence to know when to sync
     bobAccount->trackBuddyPresence(aliceUri, true);
 
-    // Wait that alice sees Bob
-    cv.wait_for(lk, 30s, [&]() { return memberAddGenerated; });
-
     // Backup alice after startConversation with member
     auto aliceArchive = std::filesystem::current_path().string() + "/alice.gz";
     aliceAccount->exportArchive(aliceArchive);
@@ -686,83 +622,27 @@ SyncHistoryTest::testSyncCreateAccountExportDeleteReimportWithConvId()
     details[ConfProperties::ARCHIVE_PASSWORD] = "";
     details[ConfProperties::ARCHIVE_PIN] = "";
     details[ConfProperties::ARCHIVE_PATH] = aliceArchive;
-    requestReceived = false;
-    conversationReady = false;
     alice2Id = Manager::instance().addAccount(details);
     // Should retrieve conversation, no need for action as the convInfos is in the archive
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return alice2Ready && conversationReady; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return !alice2Data.conversationId.empty(); }));
 
-    messageBobReceived = 0;
+    auto bobMsgSize = bobData.messages.size();
     libjami::sendMessage(alice2Id, convId, std::string("hi"), "");
-    cv.wait_for(lk, 30s, [&]() { return messageBobReceived == 1; });
-    libjami::unregisterSignalHandlers();
+    cv.wait_for(lk, 30s, [&]() { return bobMsgSize + 1 == bobData.messages.size(); });
 }
 
 void
 SyncHistoryTest::testSyncCreateAccountExportDeleteReimportWithConvReq()
 {
+    connectSignals();
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
-    auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
-    auto bobUri = bobAccount->getUsername();
     auto aliceUri = aliceAccount->getUsername();
 
     // Start conversation
     auto convId = libjami::startConversation(bobId);
 
-    std::mutex mtx;
-    std::unique_lock<std::mutex> lk {mtx};
-    std::condition_variable cv;
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    auto messageBobReceived = 0, messageAliceReceived = 0;
-    bool requestReceived = false;
-    bool conversationReady = false;
-    bool alice2Ready = false;
-    confHandlers.insert(libjami::exportable_callback<libjami::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(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
-            [&](const std::string& accountId,
-                const std::string& /* conversationId */,
-                std::map<std::string, std::string> /*metadatas*/) {
-                if (accountId == aliceId)
-                    requestReceived = true;
-                cv.notify_one();
-            }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& conversationId) {
-            if (accountId == bobId) {
-                conversationReady = true;
-                cv.notify_one();
-            }
-
-            if (accountId == alice2Id && conversationId == convId) {
-                conversationReady = true;
-                cv.notify_one();
-            }
-        }));
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConfigurationSignal::VolatileDetailsChanged>(
-            [&](const std::string& accountId, const std::map<std::string, std::string>& details) {
-                if (alice2Id != accountId) {
-                    return;
-                }
-                alice2Ready = details.at(libjami::Account::VolatileProperties::DEVICE_ANNOUNCED)
-                              == "true";
-                cv.notify_one();
-            }));
-    libjami::registerSignalHandlers(confHandlers);
-
     libjami::addConversationMember(bobId, convId, aliceUri);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceData.requestReceived; }));
 
     // Backup alice after startConversation with member
     auto aliceArchive = std::filesystem::current_path().string() + "/alice.gz";
@@ -780,54 +660,24 @@ SyncHistoryTest::testSyncCreateAccountExportDeleteReimportWithConvReq()
     details[ConfProperties::ARCHIVE_PASSWORD] = "";
     details[ConfProperties::ARCHIVE_PIN] = "";
     details[ConfProperties::ARCHIVE_PATH] = aliceArchive;
-    conversationReady = false;
     alice2Id = Manager::instance().addAccount(details);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return alice2Ready; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return alice2Data.deviceAnnounced; }));
 
     // Should get the same request as before.
-    messageBobReceived = 0;
+    auto bobMsgSize = bobData.messages.size();
     libjami::acceptConversationRequest(alice2Id, convId);
     CPPUNIT_ASSERT(
-        cv.wait_for(lk, 30s, [&]() { return conversationReady && messageBobReceived == 1; }));
-    libjami::unregisterSignalHandlers();
+        cv.wait_for(lk, 30s, [&]() { return bobMsgSize + 1 == bobData.messages.size(); }));
 }
 
 void
 SyncHistoryTest::testSyncOneToOne()
 {
+    connectSignals();
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
-    // Start conversation
-    std::string convId;
-
-    std::mutex mtx;
-    std::unique_lock<std::mutex> lk {mtx};
-    std::condition_variable cv;
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    auto conversationReady = false, alice2Ready = false;
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& conversationId) {
-            if (accountId == aliceId)
-                convId = conversationId;
-            else if (accountId == alice2Id && conversationId == convId)
-                conversationReady = true;
-            cv.notify_one();
-        }));
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConfigurationSignal::VolatileDetailsChanged>(
-            [&](const std::string& accountId, const std::map<std::string, std::string>& details) {
-                if (alice2Id != accountId) {
-                    return;
-                }
-                alice2Ready = details.at(libjami::Account::VolatileProperties::DEVICE_ANNOUNCED)
-                              == "true";
-                cv.notify_one();
-            }));
-    libjami::registerSignalHandlers(confHandlers);
 
     aliceAccount->addContact(bobAccount->getUsername());
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return !convId.empty(); }));
-
     // Now create alice2
     auto aliceArchive = std::filesystem::current_path().string() + "/alice.gz";
     aliceAccount->exportArchive(aliceArchive);
@@ -841,47 +691,28 @@ SyncHistoryTest::testSyncOneToOne()
     details[ConfProperties::ARCHIVE_PATH] = aliceArchive;
 
     alice2Id = Manager::instance().addAccount(details);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return alice2Ready && conversationReady; }));
-    libjami::unregisterSignalHandlers();
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return !alice2Data.conversationId.empty(); }));
 }
 
 void
 SyncHistoryTest::testConversationRequestRemoved()
 {
+    connectSignals();
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
+    auto uri = aliceAccount->getUsername();
 
     // Export alice
     auto aliceArchive = std::filesystem::current_path().string() + "/alice.gz";
     aliceAccount->exportArchive(aliceArchive);
 
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
-    auto uri = aliceAccount->getUsername();
 
     // Start conversation for Alice
     auto convId = libjami::startConversation(bobId);
 
     // Check that alice receives the request
-    std::mutex mtx;
-    std::unique_lock<std::mutex> lk {mtx};
-    std::condition_variable cv;
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    auto requestReceived = false;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
-            [&](const std::string& accountId,
-                const std::string& conversationId,
-                std::map<std::string, std::string> /*metadatas*/) {
-                if (accountId == aliceId && conversationId == convId) {
-                    requestReceived = true;
-                    cv.notify_one();
-                }
-            }));
-    libjami::registerSignalHandlers(confHandlers);
-
     libjami::addConversationMember(bobId, convId, uri);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return requestReceived; }));
-    libjami::unregisterSignalHandlers();
-    confHandlers.clear();
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return aliceData.requestReceived; }));
 
     // Now create alice2
     std::map<std::string, std::string> details = libjami::getAccountTemplate("RING");
@@ -894,43 +725,17 @@ SyncHistoryTest::testConversationRequestRemoved()
     details[ConfProperties::ARCHIVE_PATH] = aliceArchive;
     alice2Id = Manager::instance().addAccount(details);
 
-    requestReceived = false;
-    bool requestDeclined = false, requestDeclined2 = false;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
-            [&](const std::string& accountId,
-                const std::string& conversationId,
-                std::map<std::string, std::string> /*metadatas*/) {
-                if (accountId == alice2Id && conversationId == convId) {
-                    requestReceived = true;
-                    cv.notify_one();
-                }
-            }));
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestDeclined>(
-            [&](const std::string& accountId, const std::string& conversationId) {
-                if (conversationId != convId)
-                    return;
-                if (accountId == aliceId)
-                    requestDeclined = true;
-                if (accountId == alice2Id)
-                    requestDeclined2 = true;
-                cv.notify_one();
-            }));
-    libjami::registerSignalHandlers(confHandlers);
-
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return requestReceived; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return aliceData.requestReceived; }));
     // Now decline trust request, this should trigger ConversationRequestDeclined both sides for Alice
     libjami::declineConversationRequest(aliceId, convId);
 
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return requestDeclined && requestDeclined2; }));
-
-    libjami::unregisterSignalHandlers();
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return aliceData.requestRemoved && alice2Data.requestRemoved; }));
 }
 
 void
 SyncHistoryTest::testProfileReceivedMultiDevice()
 {
+    connectSignals();
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto aliceUri = aliceAccount->getUsername();
@@ -946,9 +751,9 @@ VERSION:2.1\n\
 FN:TITLE\n\
 DESCRIPTION:DESC\n\
 END:VCARD";
-    auto alicePath = fileutils::get_data_dir() / aliceAccount->getAccountID()
+    auto alicePath = fileutils::get_data_dir() / aliceId
                      / "profile.vcf";
-    auto bobPath = fileutils::get_data_dir() / bobAccount->getAccountID()
+    auto bobPath = fileutils::get_data_dir() / bobId
                    / "profile.vcf";
     // Save VCard
     auto p = std::filesystem::path(alicePath);
@@ -966,59 +771,15 @@ END:VCARD";
         bobFile.close();
     }
 
-    std::mutex mtx;
-    std::unique_lock<std::mutex> lk {mtx};
-    std::condition_variable cv;
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    bool conversationReady = false, requestReceived = false, bobProfileReceived = false,
-         aliceProfileReceived = false, bobProfileReceivedAlice2 = false;
-    std::string convId = "";
-    std::string bobDest = aliceAccount->dataTransfer()->profilePath(bobUri);
-    confHandlers.insert(libjami::exportable_callback<libjami::ConfigurationSignal::IncomingTrustRequest>(
-        [&](const std::string& account_id,
-            const std::string& /*from*/,
-            const std::string& /*conversationId*/,
-            const std::vector<uint8_t>& /*payload*/,
-            time_t /*received*/) {
-            if (account_id == bobId)
-                requestReceived = true;
-            cv.notify_one();
-        }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& conversationId) {
-            if (accountId == aliceId) {
-                convId = conversationId;
-            } else if (accountId == bobId) {
-                conversationReady = true;
-            }
-            cv.notify_one();
-        }));
-    confHandlers.insert(libjami::exportable_callback<libjami::ConfigurationSignal::ProfileReceived>(
-        [&](const std::string& accountId, const std::string& peerId, const std::string& path) {
-            if (accountId == aliceId && peerId == bobUri) {
-                bobProfileReceived = true;
-                auto p = std::filesystem::path(bobDest);
-                dhtnet::fileutils::recursive_mkdir(p.parent_path());
-                std::rename(path.c_str(), bobDest.c_str());
-            } else if (accountId == bobId && peerId == aliceUri) {
-                aliceProfileReceived = true;
-            } else if (accountId == alice2Id && peerId == bobUri) {
-                bobProfileReceivedAlice2 = true;
-            } else if (accountId == alice2Id && peerId == aliceUri) {
-                aliceProfileReceived = true;
-            }
-            cv.notify_one();
-        }));
-    libjami::registerSignalHandlers(confHandlers);
     aliceAccount->addContact(bobUri);
     aliceAccount->sendTrustRequest(bobUri, {});
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
 
     CPPUNIT_ASSERT(bobAccount->acceptTrustRequest(aliceUri));
     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
-        return conversationReady && bobProfileReceived && aliceProfileReceived;
+        return !bobData.profilePath.empty() && !aliceData.profilePath.empty() && !bobData.conversationId.empty();
     }));
-    CPPUNIT_ASSERT(std::filesystem::is_regular_file(bobDest));
+    CPPUNIT_ASSERT(std::filesystem::is_regular_file(bobData.profilePath));
 
     // Now create alice2
     std::map<std::string, std::string> details = libjami::getAccountTemplate("RING");
@@ -1029,96 +790,44 @@ END:VCARD";
     details[ConfProperties::ARCHIVE_PASSWORD] = "";
     details[ConfProperties::ARCHIVE_PIN] = "";
     details[ConfProperties::ARCHIVE_PATH] = aliceArchive;
-    bobProfileReceived = false, aliceProfileReceived = false;
+    bobData.profilePath = {};
+    alice2Data.profilePath = {};
     alice2Id = Manager::instance().addAccount(details);
 
-    CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&] { return aliceProfileReceived && bobProfileReceivedAlice2; }));
-    libjami::unregisterSignalHandlers();
+    CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&] {
+        return alice2Data.deviceAnnounced && !bobData.profilePath.empty() && !alice2Data.profilePath.empty(); }));
 }
 
 void
 SyncHistoryTest::testLastInteractionAfterClone()
 {
+    connectSignals();
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto aliceUri = aliceAccount->getUsername();
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto bobUri = bobAccount->getUsername();
-    std::string convId;
-
-    std::mutex mtx;
-    std::unique_lock<std::mutex> lk {mtx};
-    std::condition_variable cv;
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    auto messageReceived = false;
-    std::string msgId = "";
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::MessageReceived>(
-        [&](const std::string&  accountId ,
-            const std::string& /* conversationId */,
-            std::map<std::string, std::string> message) {
-            if (accountId == bobId) {
-                messageReceived = true;
-                msgId = message["id"];
-                cv.notify_one();
-            }
-        }));
-    auto conversationReady = false;
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& conversationId) {
-            if (accountId == bobId || accountId == alice2Id) {
-                convId = conversationId;
-                conversationReady = true;
-            }
-            cv.notify_one();
-        }));
-    auto requestReceived = false;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
-            [&](const std::string& accountId,
-                const std::string& /*conversationId*/,
-                std::map<std::string, std::string> /*metadatas*/) {
-                if (accountId == bobId)
-                    requestReceived = true;
-                cv.notify_one();
-            }));
-    auto messageDisplayed = false;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConfigurationSignal::AccountMessageStatusChanged>(
-            [&](const std::string& accountId,
-                const std::string& /* conversationId */,
-                const std::string& /* username */,
-                const std::string& /* msgId */,
-                int status) {
-                if (bobId == accountId && status == 3)
-                    messageDisplayed = true;
-                cv.notify_one();
-            }));
-    libjami::registerSignalHandlers(confHandlers);
-    confHandlers.clear();
 
     aliceAccount->addContact(bobUri);
     aliceAccount->sendTrustRequest(bobUri, {});
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived; }));
 
+    auto aliceMsgSize = aliceData.messages.size();
     CPPUNIT_ASSERT(bobAccount->acceptTrustRequest(aliceUri));
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return conversationReady; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 1 == aliceData.messages.size(); }));
 
     // Start conversation
-    messageReceived = false;
-    libjami::sendMessage(bobId, convId, std::string("Message 1"), "");
-    CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return messageReceived; }));
-    messageReceived = false;
-    libjami::sendMessage(bobId, convId, std::string("Message 2"), "");
-    CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return messageReceived; }));
-    messageReceived = false;
-    libjami::sendMessage(bobId, convId, std::string("Message 3"), "");
-    CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return messageReceived; }));
-
-    messageDisplayed = false;
-    libjami::setMessageDisplayed(aliceId, "swarm:" + convId, msgId, 3);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return messageDisplayed; }));
+    libjami::sendMessage(bobId, aliceData.conversationId, std::string("Message 1"), "");
+    CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return aliceMsgSize + 2 == aliceData.messages.size(); }));
+    libjami::sendMessage(bobId, aliceData.conversationId, std::string("Message 2"), "");
+    CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return aliceMsgSize + 3 == aliceData.messages.size(); }));
+    libjami::sendMessage(bobId, aliceData.conversationId, std::string("Message 3"), "");
+    CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return aliceMsgSize + 4 == aliceData.messages.size(); }));
+
+    auto msgId = aliceData.messages.rbegin()->id;
+    libjami::setMessageDisplayed(aliceId, "swarm:" + aliceData.conversationId, msgId, 3);
+    CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return aliceData.sent; }));
 
     // Now create alice2
-    conversationReady = false;
     auto aliceArchive = std::filesystem::current_path().string() + "/alice.gz";
     aliceAccount->exportArchive(aliceArchive);
     std::map<std::string, std::string> details = libjami::getAccountTemplate("RING");
@@ -1132,9 +841,9 @@ SyncHistoryTest::testLastInteractionAfterClone()
     alice2Id = Manager::instance().addAccount(details);
 
     // Check if conversation is ready
-    CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&]() { return conversationReady; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&]() { return !alice2Data.conversationId.empty(); }));
     // Check that last displayed is synched
-    auto membersInfos = libjami::getConversationMembers(alice2Id, convId);
+    auto membersInfos = libjami::getConversationMembers(alice2Id, alice2Data.conversationId);
     CPPUNIT_ASSERT(std::find_if(membersInfos.begin(),
                                 membersInfos.end(),
                                 [&](auto infos) {
@@ -1147,65 +856,11 @@ SyncHistoryTest::testLastInteractionAfterClone()
 void
 SyncHistoryTest::testLastInteractionAfterSomeMessages()
 {
+    connectSignals();
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
     auto aliceUri = aliceAccount->getUsername();
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
     auto bobUri = bobAccount->getUsername();
-    std::string convId;
-
-    std::mutex mtx;
-    std::unique_lock<std::mutex> lk {mtx};
-    std::condition_variable cv;
-    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
-    auto messageReceived = false;
-    std::string msgId = "";
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::MessageReceived>(
-        [&](const std::string& /* accountId */,
-            const std::string& /* conversationId */,
-            std::map<std::string, std::string> message) {
-            messageReceived = true;
-            msgId = message["id"];
-            cv.notify_one();
-        }));
-    auto conversationReady = false, conversationAlice2Ready = false;
-    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
-        [&](const std::string& accountId, const std::string& conversationId) {
-            if (accountId == bobId) {
-                convId = conversationId;
-                conversationReady = true;
-            } else if (accountId == alice2Id) {
-                conversationAlice2Ready = true;
-            }
-            cv.notify_one();
-        }));
-    auto requestReceived = false;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConversationSignal::ConversationRequestReceived>(
-            [&](const std::string& accountId,
-                const std::string& /*conversationId*/,
-                std::map<std::string, std::string> /*metadatas*/) {
-                if (accountId == bobId)
-                    requestReceived = true;
-                cv.notify_one();
-            }));
-    auto messageDisplayed = false, messageDisplayedAlice2 = false;
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::ConfigurationSignal::AccountMessageStatusChanged>(
-            [&](const std::string& accountId,
-                const std::string& /* conversationId */,
-                const std::string& /* username */,
-                const std::string& msgId,
-                int status) {
-                if (status == 3) {
-                    if (accountId == aliceId)
-                        messageDisplayed = true;
-                    else if (accountId == alice2Id)
-                        messageDisplayedAlice2 = true;
-                }
-                cv.notify_one();
-            }));
-    libjami::registerSignalHandlers(confHandlers);
-    confHandlers.clear();
 
     // Creates alice2
     auto aliceArchive = std::filesystem::current_path().string() + "/alice.gz";
@@ -1222,30 +877,26 @@ SyncHistoryTest::testLastInteractionAfterSomeMessages()
 
     aliceAccount->addContact(bobUri);
     aliceAccount->sendTrustRequest(bobUri, {});
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return requestReceived; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData.requestReceived && !alice2Data.conversationId.empty(); }));
 
+    auto aliceMsgSize = aliceData.messages.size();
     CPPUNIT_ASSERT(bobAccount->acceptTrustRequest(aliceUri));
     CPPUNIT_ASSERT(
-        cv.wait_for(lk, 30s, [&]() { return conversationReady && conversationAlice2Ready; }));
+        cv.wait_for(lk, 30s, [&]() { return aliceMsgSize + 1 == aliceData.messages.size(); }));
 
     // Start conversation
-    messageReceived = false;
-    libjami::sendMessage(bobId, convId, std::string("Message 1"), "");
-    CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return messageReceived; }));
-    messageReceived = false;
-    libjami::sendMessage(bobId, convId, std::string("Message 2"), "");
-    CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return messageReceived; }));
-    messageReceived = false;
-    libjami::sendMessage(bobId, convId, std::string("Message 3"), "");
-    CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return messageReceived; }));
-
-    messageDisplayed = false;
-    messageDisplayedAlice2 = false;
-    auto displayedId = msgId;
-    libjami::setMessageDisplayed(aliceId, "swarm:" + convId, displayedId, 3);
-    CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return messageDisplayed && messageDisplayedAlice2; }));
-
-    auto membersInfos = libjami::getConversationMembers(alice2Id, convId);
+    libjami::sendMessage(bobId, aliceData.conversationId, std::string("Message 1"), "");
+    CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return aliceMsgSize + 2 == aliceData.messages.size(); }));
+    libjami::sendMessage(bobId, aliceData.conversationId, std::string("Message 2"), "");
+    CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return aliceMsgSize + 3 == aliceData.messages.size(); }));
+    libjami::sendMessage(bobId, aliceData.conversationId, std::string("Message 3"), "");
+    CPPUNIT_ASSERT(cv.wait_for(lk, 10s, [&] { return aliceMsgSize + 4 == aliceData.messages.size(); }));
+
+    auto displayedId = aliceData.messages.rbegin()->id;
+    libjami::setMessageDisplayed(aliceId, "swarm:" + aliceData.conversationId, displayedId, 3);
+    CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return aliceData.sent && alice2Data.sent; }));
+
+    auto membersInfos = libjami::getConversationMembers(alice2Id, alice2Data.conversationId);
     CPPUNIT_ASSERT(std::find_if(membersInfos.begin(),
                                 membersInfos.end(),
                                 [&](auto infos) {