From 8468f15927ec7c83a5fca671bac1a4112883b8c9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Blin?=
 <sebastien.blin@savoirfairelinux.com>
Date: Tue, 27 Jun 2023 11:45:43 -0400
Subject: [PATCH] conversation: move logic of message management in daemon

This heavily changes the API for the client. The goal here is
to move the logic to construct the history to show in the daemon
and not the client. This has several advantages:

1. Logic is common across every platforms, so bugs should not be
platform-specific
2. Client got less logic
3. Signal are simplified, if an edition comes, "MessageUpdated"
will be triggered instead MessageReceived.
4. Some tests are added for linearizing the history.
5. Search on edition is fixed.

Tests got heavily re-written, but the content didn't change (2 tests
are added, the rest is simplification).

GitLab: #831
Change-Id: Ie7c81077067e9e49db1dd396829c9225c0512c16
---
 CMakeLists.txt                                |    2 +-
 .../cx.ring.Ring.ConfigurationManager.xml     |  169 +-
 bin/dbus/dbusconfigurationmanager.hpp         |   37 +
 bin/jni/conversation.i                        |   22 +
 bin/jni/jni_interface.i                       |    5 +
 bin/nodejs/callback.h                         |    6 +
 bin/nodejs/conversation.i                     |   22 +
 bin/nodejs/nodejs_interface.i                 |    5 +
 configure.ac                                  |    2 +-
 meson.build                                   |    2 +-
 src/client/conversation_interface.cpp         |   20 +
 src/client/ring_signal.cpp                    |    5 +
 src/jami/conversation_interface.h             |   60 +
 src/jamidht/conversation.cpp                  |  406 ++-
 src/jamidht/conversation.h                    |   17 +-
 src/jamidht/conversation_module.cpp           |   38 +
 src/jamidht/conversation_module.h             |    8 +
 src/jamidht/conversationrepository.cpp        |   86 +-
 src/jamidht/conversationrepository.h          |   19 +-
 test/unitTest/conversation/call.cpp           |  112 +-
 test/unitTest/conversation/conversation.cpp   | 2779 ++++-------------
 .../conversation/conversationMembersEvent.cpp | 2113 ++++---------
 .../conversation/conversationRequest.cpp      | 1353 +++-----
 test/unitTest/syncHistory/syncHistory.cpp     |  973 ++----
 24 files changed, 2779 insertions(+), 5482 deletions(-)

diff --git a/CMakeLists.txt b/CMakeLists.txt
index f536e45a94..d980f58567 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 c1643d2d84..e7f7aa8973 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 8fddb461bf..638d08fad8 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 30c10efdee..fdecb07e2e 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 6b549324d9..9e54cda758 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 7b2a4d69b2..154d6d77d6 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 803fcd3b22..6673a8c4e3 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 13a3d911cf..42a73c39ea 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 f94bf70f77..5c7467037e 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 73c16b7745..97ae791a12 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 661832b96f..0fb86ab66a 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 5576d32b3d..9d86696ca9 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 aed802231e..26d932c775 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 629c115d83..0d72a0e5be 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 3d883ad5c8..55c4b44c75 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 5372378c19..e2712371cf 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 0feaad8963..f7e3cba941 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 8812960296..f05b7bc2ed 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 60e585286e..de96ebd8dc 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 c25b2ce299..dd92c8fe85 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 a092886c7c..4fbf617afd 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 e3730f103c..8efab0997e 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 646ddfc7a3..4bb753fd87 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 7c2d52469c..9d58e83888 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) {
-- 
GitLab