diff --git a/bin/dbus/cx.ring.Ring.ConfigurationManager.xml b/bin/dbus/cx.ring.Ring.ConfigurationManager.xml
index 29c26eb662b0a9d0077709bfe04cdb046dd23df7..71b4bcd8e40a10483e39502f3ec092ca3f5f9c00 100644
--- a/bin/dbus/cx.ring.Ring.ConfigurationManager.xml
+++ b/bin/dbus/cx.ring.Ring.ConfigurationManager.xml
@@ -597,6 +597,26 @@
            </arg>
        </signal>
 
+       <signal name="needsHost" tp:name-for-bindings="needsHost">
+           <tp:added version="13.7.0"/>
+           <tp:docstring>
+               Notify client that a conversation needs a host for calls
+           </tp:docstring>
+           <arg type="s" name="accountId"/>
+           <arg type="s" name="conversationId"/>
+       </signal>
+
+       <signal name="activeCallsChanged" tp:name-for-bindings="activeCallsChanged">
+           <tp:added version="13.7.0"/>
+           <tp:docstring>
+               Notify client that a conversation got new active calls
+           </tp:docstring>
+           <arg type="s" name="accountId"/>
+           <arg type="s" name="conversationId"/>
+           <annotation name="org.qtproject.QtDBus.QtTypeName.Out2" value="VectorMapStringString"/>
+           <arg type="aa{ss}" name="activeCalls" direction="out"/>
+       </signal>
+
        <signal name="profileReceived" tp:name-for-bindings="profileReceived">
            <tp:added version="9.2.0"/>
            <tp:docstring>
@@ -1691,6 +1711,17 @@
            <arg type="s" name="accountId" direction="in"/>
        </method>
 
+       <method name="getActiveCalls" tp:name-for-bindings="getActiveCalls">
+           <tp:added version="13.7.0"/>
+           <tp:docstring>
+               Get the active call list per conversation
+           </tp:docstring>
+           <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="VectorMapStringString"/>
+           <arg type="aa{ss}" name="activeCalls" direction="out"/>
+           <arg type="s" name="accountId" direction="in"/>
+           <arg type="s" name="conversationId" direction="in"/>
+       </method>
+
        <method name="getConversationRequests" tp:name-for-bindings="getConversationRequests">
            <tp:added version="10.0.0"/>
            <tp:docstring>
diff --git a/bin/dbus/dbusclient.cpp b/bin/dbus/dbusclient.cpp
index 22179f059f5b7cff82ea9c6d2e84cf9945f4c490..b087a0e3eacecf31923aa55b551d7e7747179f4f 100644
--- a/bin/dbus/dbusclient.cpp
+++ b/bin/dbus/dbusclient.cpp
@@ -227,6 +227,8 @@ DBusClient::initLibrary(int flags)
             bind(&DBusConfigurationManager::accountMessageStatusChanged, confM, _1, _2, _3, _4, _5)),
         exportable_callback<ConfigurationSignal::ProfileReceived>(
             bind(&DBusConfigurationManager::profileReceived, confM, _1, _2, _3)),
+        exportable_callback<ConfigurationSignal::ActiveCallsChanged>(
+            bind(&DBusConfigurationManager::activeCallsChanged, confM, _1, _2, _3)),
         exportable_callback<ConfigurationSignal::ComposingStatusChanged>(
             bind(&DBusConfigurationManager::composingStatusChanged, confM, _1, _2, _3, _4)),
         exportable_callback<ConfigurationSignal::IncomingTrustRequest>(
diff --git a/bin/dbus/dbusconfigurationmanager.cpp b/bin/dbus/dbusconfigurationmanager.cpp
index 7e72fb4cd1c0274cdebd576c66a2347580be2e1e..7896840266b059c1c56967982fa64179275dfb06 100644
--- a/bin/dbus/dbusconfigurationmanager.cpp
+++ b/bin/dbus/dbusconfigurationmanager.cpp
@@ -303,7 +303,8 @@ DBusConfigurationManager::setAudioPlugin(const std::string& audioPlugin)
 }
 
 auto
-DBusConfigurationManager::getAudioOutputDeviceList() -> decltype(libjami::getAudioOutputDeviceList())
+DBusConfigurationManager::getAudioOutputDeviceList()
+    -> decltype(libjami::getAudioOutputDeviceList())
 {
     return libjami::getAudioOutputDeviceList();
 }
@@ -547,7 +548,11 @@ DBusConfigurationManager::validateCertificatePath(const std::string& accountId,
     -> decltype(libjami::validateCertificatePath(
         accountId, certificate, privateKey, privateKeyPass, caList))
 {
-    return libjami::validateCertificatePath(accountId, certificate, privateKey, privateKeyPass, caList);
+    return libjami::validateCertificatePath(accountId,
+                                            certificate,
+                                            privateKey,
+                                            privateKeyPass,
+                                            caList);
 }
 
 auto
@@ -721,7 +726,8 @@ DBusConfigurationManager::setVolume(const std::string& device, const double& val
 }
 
 auto
-DBusConfigurationManager::getVolume(const std::string& device) -> decltype(libjami::getVolume(device))
+DBusConfigurationManager::getVolume(const std::string& device)
+    -> decltype(libjami::getVolume(device))
 {
     return libjami::getVolume(device);
 }
@@ -858,6 +864,13 @@ DBusConfigurationManager::getConversations(const std::string& accountId)
     return libjami::getConversations(accountId);
 }
 
+std::vector<std::map<std::string, std::string>>
+DBusConfigurationManager::getActiveCalls(const std::string& accountId,
+                                         const std::string& conversationId)
+{
+    return libjami::getActiveCalls(accountId, conversationId);
+}
+
 std::vector<std::map<std::string, std::string>>
 DBusConfigurationManager::getConversationRequests(const std::string& accountId)
 {
@@ -967,14 +980,14 @@ DBusConfigurationManager::searchConversation(const std::string& accountId,
                                              const uint32_t& maxResult)
 {
     return libjami::searchConversation(accountId,
-                                     conversationId,
-                                     author,
-                                     lastId,
-                                     regexSearch,
-                                     type,
-                                     after,
-                                     before,
-                                     maxResult);
+                                       conversationId,
+                                       author,
+                                       lastId,
+                                       regexSearch,
+                                       type,
+                                       after,
+                                       before,
+                                       maxResult);
 }
 
 bool
diff --git a/bin/dbus/dbusconfigurationmanager.h b/bin/dbus/dbusconfigurationmanager.h
index 413a3dac8c7d138527a676fa43dab9b7e2fa9b67..c176849c8ad7410d7592570eb3441eee6dba0fab 100644
--- a/bin/dbus/dbusconfigurationmanager.h
+++ b/bin/dbus/dbusconfigurationmanager.h
@@ -53,8 +53,8 @@
 using RingDBusMessage = DBus::Struct<std::string, std::map<std::string, std::string>, uint64_t>;
 
 class LIBJAMI_PUBLIC DBusConfigurationManager : public cx::ring::Ring::ConfigurationManager_adaptor,
-                                              public DBus::IntrospectableAdaptor,
-                                              public DBus::ObjectAdaptor
+                                                public DBus::IntrospectableAdaptor,
+                                                public DBus::ObjectAdaptor
 {
 public:
     using RingDBusDataTransferInfo = DBus::Struct<std::string,
@@ -258,6 +258,8 @@ public:
     void declineConversationRequest(const std::string& accountId, const std::string& conversationId);
     bool removeConversation(const std::string& accountId, const std::string& conversationId);
     std::vector<std::string> getConversations(const std::string& accountId);
+    std::vector<std::map<std::string, std::string>> getActiveCalls(
+        const std::string& accountId, const std::string& conversationId);
     std::vector<std::map<std::string, std::string>> getConversationRequests(
         const std::string& accountId);
     void updateConversationInfos(const std::string& accountId,
diff --git a/bin/jni/configurationmanager.i b/bin/jni/configurationmanager.i
index 3714a1ca2b98deb5c09b5a0623db60103aa1de7e..be861049ff0cfed5ee1ee9cac29a2bc8e8bceabc 100644
--- a/bin/jni/configurationmanager.i
+++ b/bin/jni/configurationmanager.i
@@ -35,6 +35,8 @@ public:
     virtual void volatileAccountDetailsChanged(const std::string& account_id, const std::map<std::string, std::string>& details){}
     virtual void incomingAccountMessage(const std::string& /*account_id*/, const std::string& /*from*/, const std::string& /*message_id*/, const std::map<std::string, std::string>& /*payload*/){}
     virtual void accountMessageStatusChanged(const std::string& /*account_id*/, const std::string& /*conversationId*/, const std::string& /*peer*/, const std::string& /*message_id*/, int /*state*/){}
+    virtual void needsHost(const std::string& /*account_id*/, const std::string& /*conversationId*/){}
+    virtual void activeCallsChanged(const std::string& /*account_id*/, const std::string& /*conversationId*/, const std::vector<std::map<std::string, std::string>>& /*activeCalls*/ ){}
     virtual void profileReceived(const std::string& /*account_id*/, const std::string& /*from*/, const std::string& /*path*/){}
     virtual void composingStatusChanged(const std::string& /*account_id*/, const std::string& /*convId*/, const std::string& /*from*/, int /*state*/){}
     virtual void knownDevicesChanged(const std::string& /*account_id*/, const std::map<std::string, std::string>& /*devices*/){}
@@ -120,6 +122,7 @@ std::map<std::string, std::string> getKnownRingDevices(const std::string& accoun
 bool revokeDevice(const std::string& accountID, const std::string& password, const std::string& deviceID);
 
 void setActiveCodecList(const std::string& accountID, const std::vector<unsigned>& list);
+std::vector<std::map<std::string, std::string>> getActiveCalls(const std::string& accountId, const std::string& convId);
 
 std::vector<std::string> getAudioPluginList();
 void setAudioPlugin(const std::string& audioPlugin);
@@ -248,6 +251,8 @@ public:
     virtual void volatileAccountDetailsChanged(const std::string& account_id, const std::map<std::string, std::string>& details){}
     virtual void incomingAccountMessage(const std::string& /*account_id*/, const std::string& /*from*/, const std::string& /*message_id*/, const std::map<std::string, std::string>& /*payload*/){}
     virtual void accountMessageStatusChanged(const std::string& /*account_id*/, const std::string& /*conversationId*/, const std::string& /*peer*/, const std::string& /*message_id*/, int /*state*/){}
+    virtual void needsHost(const std::string& /*account_id*/, const std::string& /*conversationId*/){}
+    virtual void activeCallsChanged(const std::string& /*account_id*/, const std::string& /*conversationId*/, const std::vector<std::map<std::string, std::string>>& /*activeCalls*/ ){}
     virtual void composingStatusChanged(const std::string& /*account_id*/, const std::string& /*convId*/, const std::string& /*from*/, int /*state*/){}
     virtual void knownDevicesChanged(const std::string& /*account_id*/, const std::map<std::string, std::string>& /*devices*/){}
     virtual void exportOnRingEnded(const std::string& /*account_id*/, int /*state*/, const std::string& /*pin*/){}
diff --git a/bin/jni/conversation.i b/bin/jni/conversation.i
index 5d2c5384141c7e96fdb60308518be8fc0777f6d5..ae7b5501dcb7ca22ad889384c960039c7e3217dc 100644
--- a/bin/jni/conversation.i
+++ b/bin/jni/conversation.i
@@ -49,6 +49,7 @@ namespace libjami {
   void declineConversationRequest(const std::string& accountId, const std::string& conversationId);
   bool removeConversation(const std::string& accountId, const std::string& conversationId);
   std::vector<std::string> getConversations(const std::string& accountId);
+  std::vector<std::map<std::string, std::string>> getActiveCalls(const std::string& accountId, const std::string& conversationId);
   std::vector<std::map<std::string, std::string>> getConversationRequests(const std::string& accountId);
   void updateConversationInfos(const std::string& accountId, const std::string& conversationId, const std::map<std::string, std::string>& infos);
   std::map<std::string, std::string> conversationInfos(const std::string& accountId, const std::string& conversationId);
diff --git a/bin/jni/jni_interface.i b/bin/jni/jni_interface.i
index e9f4d399dfa3b0cdfd73e8e13bf60884d5798aec..8f2159d888d49d1d9ab1faa54d07ae81db37d910 100644
--- a/bin/jni/jni_interface.i
+++ b/bin/jni/jni_interface.i
@@ -273,6 +273,8 @@ void init(ConfigurationCallback* confM, Callback* callM, PresenceCallback* presM
         exportable_callback<ConfigurationSignal::Error>(bind(&ConfigurationCallback::errorAlert, confM, _1)),
         exportable_callback<ConfigurationSignal::IncomingAccountMessage>(bind(&ConfigurationCallback::incomingAccountMessage, confM, _1, _2, _3, _4 )),
         exportable_callback<ConfigurationSignal::AccountMessageStatusChanged>(bind(&ConfigurationCallback::accountMessageStatusChanged, confM, _1, _2, _3, _4, _5 )),
+        exportable_callback<ConfigurationSignal::NeedsHost>(bind(&ConfigurationCallback::needsHost, confM, _1, _2 )),
+        exportable_callback<ConfigurationSignal::ActiveCallsChanged>(bind(&ConfigurationCallback::activeCallsChanged, confM, _1, _2, _3 )),
         exportable_callback<ConfigurationSignal::ProfileReceived>(bind(&ConfigurationCallback::profileReceived, confM, _1, _2, _3 )),
         exportable_callback<ConfigurationSignal::ComposingStatusChanged>(bind(&ConfigurationCallback::composingStatusChanged, confM, _1, _2, _3, _4 )),
         exportable_callback<ConfigurationSignal::IncomingTrustRequest>(bind(&ConfigurationCallback::incomingTrustRequest, confM, _1, _2, _3, _4, _5 )),
diff --git a/bin/nodejs/callback.h b/bin/nodejs/callback.h
index 749d887c70f7764a9960c3bc8c55c34f9d352efc..7b2a4d69b2a75e1c79522a45a1d5428ed012f041 100644
--- a/bin/nodejs/callback.h
+++ b/bin/nodejs/callback.h
@@ -16,6 +16,8 @@ Persistent<Function> composingStatusChangedCb;
 Persistent<Function> volatileDetailsChangedCb;
 Persistent<Function> incomingAccountMessageCb;
 Persistent<Function> accountMessageStatusChangedCb;
+Persistent<Function> needsHostCb;
+Persistent<Function> activeCallsChangedCb;
 Persistent<Function> incomingTrustRequestCb;
 Persistent<Function> contactAddedCb;
 Persistent<Function> contactRemovedCb;
@@ -67,6 +69,10 @@ getPresistentCb(std::string_view signal)
         return &incomingAccountMessageCb;
     else if (signal == "AccountMessageStatusChanged")
         return &accountMessageStatusChangedCb;
+    else if (signal == "NeedsHost")
+        return &needsHostCb;
+    else if (signal == "ActiveCallsChanged")
+        return &activeCallsChangedCb;
     else if (signal == "IncomingTrustRequest")
         return &incomingTrustRequestCb;
     else if (signal == "ContactAdded")
@@ -417,6 +423,41 @@ accountMessageStatusChanged(const std::string& account_id,
     uv_async_send(&signalAsync);
 }
 
+void
+needsHost(const std::string& account_id, const std::string& conversationId)
+{
+    std::lock_guard<std::mutex> lock(pendingSignalsLock);
+    pendingSignals.emplace([account_id, conversationId]() {
+        Local<Function> func = Local<Function>::New(Isolate::GetCurrent(), needsHostCb);
+        if (!func.IsEmpty()) {
+            Local<Value> callback_args[] = {V8_STRING_NEW_LOCAL(account_id),
+                                            V8_STRING_NEW_LOCAL(conversationId)};
+            func->Call(SWIGV8_CURRENT_CONTEXT(), SWIGV8_NULL(), 2, callback_args);
+        }
+    });
+
+    uv_async_send(&signalAsync);
+}
+
+void
+activeCallsChanged(const std::string& account_id,
+                   const std::string& conversationId,
+                   const std::vector<std::map<std::string, std::string>>& activeCalls)
+{
+    std::lock_guard<std::mutex> lock(pendingSignalsLock);
+    pendingSignals.emplace([account_id, conversationId, activeCalls]() {
+        Local<Function> func = Local<Function>::New(Isolate::GetCurrent(), activeCallsChangedCb);
+        if (!func.IsEmpty()) {
+            Local<Value> callback_args[] = {V8_STRING_NEW_LOCAL(account_id),
+                                            V8_STRING_NEW_LOCAL(conversationId),
+                                            stringMapVecToJsMapArray(activeCalls)};
+            func->Call(SWIGV8_CURRENT_CONTEXT(), SWIGV8_NULL(), 3, callback_args);
+        }
+    });
+
+    uv_async_send(&signalAsync);
+}
+
 void
 incomingAccountMessage(const std::string& accountId,
                        const std::string& messageId,
@@ -852,8 +893,7 @@ logMessage(const std::string& message)
 {
     std::lock_guard<std::mutex> lock(pendingSignalsLock);
     pendingSignals.emplace([message]() {
-        Local<Function> func = Local<Function>::New(Isolate::GetCurrent(),
-                                                    messageSendCb);
+        Local<Function> func = Local<Function>::New(Isolate::GetCurrent(), messageSendCb);
         if (!func.IsEmpty()) {
             SWIGV8_VALUE callback_args[] = {V8_STRING_NEW_LOCAL(message)};
             func->Call(SWIGV8_CURRENT_CONTEXT(), SWIGV8_NULL(), 1, callback_args);
diff --git a/bin/nodejs/configurationmanager.i b/bin/nodejs/configurationmanager.i
index dfecde2b9c1008b75cd4646a4b19149e87805be3..6cdc41f4cc120c6c2ae426512ee30b7b7e7fa992 100644
--- a/bin/nodejs/configurationmanager.i
+++ b/bin/nodejs/configurationmanager.i
@@ -34,6 +34,8 @@ public:
     virtual void volatileAccountDetailsChanged(const std::string& account_id, const std::map<std::string, std::string>& details){}
     virtual void incomingAccountMessage(const std::string& /*account_id*/, const std::string& /*from*/, const std::map<std::string, std::string>& /*payload*/){}
     virtual void accountMessageStatusChanged(const std::string& /*account_id*/, const std::string& /*conversationId*/, const std::string& /*peer*/, const std::string& /*message_id*/, int /*state*/){}
+    virtual void needsHost(const std::string& /*account_id*/, const std::string& /*conversationId*/){}
+    virtual void activeCallsChanged(const std::string& /*account_id*/, const std::string& /*conversationId*/, const std::vector<std::map<std::string, std::string>>& /*activeCalls*/ ){}
     virtual void profileReceived(const std::string& /*account_id*/, const std::string& /*from*/, const std::string& /*path*/){}
     virtual void composingStatusChanged(const std::string& /*account_id*/, const std::string& /*convId*/, const std::string& /*from*/, int /*state*/){}
     virtual void knownDevicesChanged(const std::string& /*account_id*/, const std::map<std::string, std::string>& /*devices*/){}
@@ -103,6 +105,7 @@ bool searchUser(const std::string& account, const std::string& query);
 std::vector<unsigned> getCodecList();
 std::vector<std::string> getSupportedTlsMethod();
 std::vector<std::string> getSupportedCiphers(const std::string& accountID);
+std::vector<std::map<std::string, std::string>> getActiveCalls(const std::string& accountId, const std::string& convId);
 std::map<std::string, std::string> getCodecDetails(const std::string& accountID, const unsigned& codecId);
 bool setCodecDetails(const std::string& accountID, const unsigned& codecId, const std::map<std::string, std::string>& details);
 std::vector<unsigned> getActiveCodecList(const std::string& accountID);
@@ -233,6 +236,8 @@ public:
     virtual void volatileAccountDetailsChanged(const std::string& account_id, const std::map<std::string, std::string>& details){}
     virtual void incomingAccountMessage(const std::string& /*account_id*/, const std::string& /*from*/, const std::map<std::string, std::string>& /*payload*/){}
     virtual void accountMessageStatusChanged(const std::string& /*account_id*/, const std::string& /*conversationId*/, const std::string& /*peer*/, const std::string& /*message_id*/, int /*state*/){}
+    virtual void needsHost(const std::string& /*account_id*/, const std::string& /*conversationId*/){}
+    virtual void activeCallsChanged(const std::string& /*account_id*/, const std::string& /*conversationId*/, const std::vector<std::map<std::string, std::string>>& /*activeCalls*/ ){}
     virtual void profileReceived(const std::string& /*account_id*/, const std::string& /*from*/, const std::string& /*path*/){}
     virtual void composingStatusChanged(const std::string& /*account_id*/, const std::string& /*convId*/, const std::string& /*from*/, int /*state*/){}
     virtual void knownDevicesChanged(const std::string& /*account_id*/, const std::map<std::string, std::string>& /*devices*/){}
diff --git a/bin/nodejs/conversation.i b/bin/nodejs/conversation.i
index fc99c55a17439f511eecc92442b5ac8600fb9d78..0de5d6168f179ff82df07741efec0cd609c05946 100644
--- a/bin/nodejs/conversation.i
+++ b/bin/nodejs/conversation.i
@@ -49,6 +49,7 @@ namespace libjami {
   void declineConversationRequest(const std::string& accountId, const std::string& conversationId);
   bool removeConversation(const std::string& accountId, const std::string& conversationId);
   std::vector<std::string> getConversations(const std::string& accountId);
+  std::vector<std::map<std::string, std::string>> getActiveCalls(const std::string& accountId, const std::string& conversationId);
   std::vector<std::map<std::string, std::string>> getConversationRequests(const std::string& accountId);
   void updateConversationInfos(const std::string& accountId, const std::string& conversationId, const std::map<std::string, std::string>& infos);
   std::map<std::string, std::string> conversationInfos(const std::string& accountId, const std::string& conversationId);
diff --git a/bin/nodejs/nodejs_interface.i b/bin/nodejs/nodejs_interface.i
index 1acb741cd1d08d7bbadfe3ca739ac09043cc9ea9..8adf698695e445d0092b818e9d4494f1142eaa32 100644
--- a/bin/nodejs/nodejs_interface.i
+++ b/bin/nodejs/nodejs_interface.i
@@ -142,6 +142,8 @@ void init(const SWIGV8_VALUE& funcMap){
         exportable_callback<ConfigurationSignal::IncomingAccountMessage>(bind(&incomingAccountMessage, _1, _2, _3, _4 )),
         exportable_callback<ConfigurationSignal::AccountMessageStatusChanged>(bind(&accountMessageStatusChanged, _1, _2, _3, _4, _5 )),
         exportable_callback<ConfigurationSignal::MessageSend>(bind(&logMessage, _1 )),
+        exportable_callback<ConfigurationSignal::NeedsHost>(bind(&ConfigurationCallback::needsHost, _1, _2 )),
+        exportable_callback<ConfigurationSignal::ActiveCallsChanged>(bind(&ConfigurationCallback::activeCallsChanged, _1, _2, _3 )),
         //exportable_callback<ConfigurationSignal::ProfileReceived>(bind(&profileReceived, _1, _2, _3, _4 )),
         //exportable_callback<ConfigurationSignal::IncomingTrustRequest>(bind(&incomingTrustRequest, _1, _2, _3, _4, _5 )),
     };
diff --git a/configure.ac b/configure.ac
index 8a38e43429ad7ccfac7338a22c16df7ea0e44176..19b263e97a409aab46c30d7325f69f5f8c23798a 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.6.0],[jami@gnu.org],[jami])
+AC_INIT([Jami Daemon],[13.7.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 211acff63081ca08b3cdafedb536babfbc12de36..a93b4052556b28823fb7b5e14b2cc0d20790c213 100644
--- a/meson.build
+++ b/meson.build
@@ -1,5 +1,5 @@
 project('jami-daemon', ['c', 'cpp'],
-        version: '13.6.0',
+        version: '13.7.0',
         license: 'GPL3+',
         default_options: ['cpp_std=gnu++17', 'buildtype=debugoptimized'],
         meson_version:'>= 0.56'
diff --git a/src/call.cpp b/src/call.cpp
index 328c2af1f464e3ce057d481e67cc7ae810f0d6d7..e07f3f27046c202a6ffabafd02ff3ec143899a2d 100644
--- a/src/call.cpp
+++ b/src/call.cpp
@@ -117,13 +117,17 @@ Call::Call(const std::shared_ptr<Account>& account,
                 std::chrono::seconds(timeout));
         }
 
-        if (!isSubcall() && getCallType() == CallType::OUTGOING) {
+        if (!isSubcall()) {
             if (cnx_state == ConnectionState::CONNECTED && duration_start_ == time_point::min())
                 duration_start_ = clock::now();
             else if (cnx_state == ConnectionState::DISCONNECTED && call_state == CallState::OVER) {
                 if (auto jamiAccount = std::dynamic_pointer_cast<JamiAccount>(getAccount().lock())) {
-                    jamiAccount->convModule()->addCallHistoryMessage(getPeerNumber(),
-                                                                     getCallDuration().count());
+                    // TODO: This will be removed when 1:1 swarm will have a conference.
+                    // For now, only commit for 1:1 calls
+                    if (toUsername().find('/') == std::string::npos) {
+                        jamiAccount->convModule()->addCallHistoryMessage(getPeerNumber(),
+                                                                         getCallDuration().count());
+                    }
                     monitor();
                 }
             }
@@ -275,7 +279,10 @@ Call::setState(CallState call_state, ConnectionState cnx_state, signed code)
                      new_client_state.c_str(),
                      code);
             lock.unlock();
-            emitSignal<libjami::CallSignal::StateChange>(getAccountId(), id_, new_client_state, code);
+            emitSignal<libjami::CallSignal::StateChange>(getAccountId(),
+                                                         id_,
+                                                         new_client_state,
+                                                         code);
         }
     }
 
diff --git a/src/call.h b/src/call.h
index ac423d60b69aea9367267fcb4a54473df3cd4ea7..72c715394af92f910477b6cbe853873a310ce2e7 100644
--- a/src/call.h
+++ b/src/call.h
@@ -170,6 +170,18 @@ public:
      */
     void setPeerDisplayName(const std::string& name) { peerDisplayName_ = name; }
 
+    /**
+     * Get "To" from the invite
+     * @note Used to make the difference between incoming calls for accounts and for conversations
+     * @return the "To" that was present in the invite
+     */
+    const std::string& toUsername() const { return toUsername_; }
+    /**
+     * Updated by sipvoiplink, corresponds to the "To" in the invite
+     * @param username      "To"
+     */
+    void toUsername(const std::string& username) { toUsername_ = username; }
+
     /**
      * Get the peer display name (caller in ingoing)
      * not protected by mutex (when created)
@@ -537,6 +549,7 @@ protected:
 
     /// Supported conference protocol version
     int peerConfProtocol_ {0};
+    std::string toUsername_ {};
 };
 
 // Helpers
diff --git a/src/client/conversation_interface.cpp b/src/client/conversation_interface.cpp
index d46952c8b153b1aadb2d8513815013305bc63803..f67f7f7b00d9caf088dfb7aadb4fd22d574ab79b 100644
--- a/src/client/conversation_interface.cpp
+++ b/src/client/conversation_interface.cpp
@@ -78,6 +78,15 @@ getConversations(const std::string& accountId)
     return {};
 }
 
+std::vector<std::map<std::string, std::string>>
+getActiveCalls(const std::string& accountId, const std::string& conversationId)
+{
+    if (auto acc = jami::Manager::instance().getAccount<jami::JamiAccount>(accountId))
+        if (auto convModule = acc->convModule())
+            return convModule->getActiveCalls(conversationId);
+    return {};
+}
+
 std::vector<std::map<std::string, std::string>>
 getConversationRequests(const std::string& accountId)
 {
diff --git a/src/client/ring_signal.cpp b/src/client/ring_signal.cpp
index 6906887ebeeff45e042695f4adc0dede7eaa8df5..277e25405838ae7fb03213e25a0f05deca3ad7ba 100644
--- a/src/client/ring_signal.cpp
+++ b/src/client/ring_signal.cpp
@@ -66,6 +66,8 @@ getSignalHandlers()
         exported_callback<libjami::ConfigurationSignal::IncomingAccountMessage>(),
         exported_callback<libjami::ConfigurationSignal::ComposingStatusChanged>(),
         exported_callback<libjami::ConfigurationSignal::AccountMessageStatusChanged>(),
+        exported_callback<libjami::ConfigurationSignal::NeedsHost>(),
+        exported_callback<libjami::ConfigurationSignal::ActiveCallsChanged>(),
         exported_callback<libjami::ConfigurationSignal::ProfileReceived>(),
         exported_callback<libjami::ConfigurationSignal::IncomingTrustRequest>(),
         exported_callback<libjami::ConfigurationSignal::ContactAdded>(),
diff --git a/src/conference.cpp b/src/conference.cpp
index ffab65089f6084ceed3677309e6cf19eeaa2103c..d7b1db1a1809a677544538a021b7d0607b58e7ad 100644
--- a/src/conference.cpp
+++ b/src/conference.cpp
@@ -54,8 +54,10 @@ using namespace std::literals;
 
 namespace jami {
 
-Conference::Conference(const std::shared_ptr<Account>& account, bool attachHost)
-    : id_(Manager::instance().callFactory.getNewCallID())
+Conference::Conference(const std::shared_ptr<Account>& account,
+                       const std::string& confId,
+                       bool attachHost)
+    : id_(confId.empty() ? Manager::instance().callFactory.getNewCallID() : confId)
     , account_(account)
 #ifdef ENABLE_VIDEO
     , videoEnabled_(account->isVideoEnabled())
@@ -87,6 +89,7 @@ Conference::Conference(const std::shared_ptr<Account>& account, bool attachHost)
 
     JAMI_INFO("Create new conference %s", id_.c_str());
     setLocalHostDefaultMediaSource();
+    duration_start_ = clock::now();
 
 #ifdef ENABLE_VIDEO
     auto itVideo = std::find_if(hostSources_.begin(), hostSources_.end(), [&](auto attr) {
@@ -281,11 +284,11 @@ Conference::~Conference()
 
         // Continue the recording for the call if the conference was recorded
         if (isRecording()) {
-            JAMI_DBG("Stop recording for conf %s", getConfId().c_str());
+            JAMI_DEBUG("Stop recording for conf {:s}", getConfId());
             toggleRecording();
             if (not call->isRecording()) {
-                JAMI_DBG("Conference was recorded, start recording for conf %s",
-                         call->getCallId().c_str());
+                JAMI_DEBUG("Conference was recorded, start recording for conf {:s}",
+                           call->getCallId());
                 call->toggleRecording();
             }
         }
@@ -314,6 +317,8 @@ Conference::~Conference()
         confAVStreams.clear();
     }
 #endif // ENABLE_PLUGIN
+    if (shutdownCb_)
+        shutdownCb_(getDuration().count());
     jami_tracepoint(conference_end, id_.c_str());
 }
 
@@ -326,10 +331,10 @@ Conference::getState() const
 void
 Conference::setState(State state)
 {
-    JAMI_DBG("[conf %s] Set state to [%s] (was [%s])",
-             id_.c_str(),
-             getStateStr(state),
-             getStateStr());
+    JAMI_DEBUG("[conf {:s}] Set state to [{:s}] (was [{:s}])",
+               id_,
+               getStateStr(state),
+               getStateStr());
 
     confState_ = state;
 }
@@ -345,9 +350,7 @@ Conference::setLocalHostDefaultMediaSource()
             = {MediaType::MEDIA_AUDIO, false, false, true, {}, sip_utils::DEFAULT_AUDIO_STREAMID};
     }
 
-    JAMI_DBG("[conf %s] Setting local host audio source to [%s]",
-             id_.c_str(),
-             audioAttr.toString().c_str());
+    JAMI_DEBUG("[conf {:s}] Setting local host audio source to [{:s}]", id_, audioAttr.toString());
     hostSources_.emplace_back(audioAttr);
 
 #ifdef ENABLE_VIDEO
@@ -363,9 +366,9 @@ Conference::setLocalHostDefaultMediaSource()
                    Manager::instance().getVideoManager().videoDeviceMonitor.getMRLForDefaultDevice(),
                    sip_utils::DEFAULT_VIDEO_STREAMID};
         }
-        JAMI_DBG("[conf %s] Setting local host video source to [%s]",
-                 id_.c_str(),
-                 videoAttr.toString().c_str());
+        JAMI_DEBUG("[conf {:s}] Setting local host video source to [{:s}]",
+                   id_,
+                   videoAttr.toString());
         hostSources_.emplace_back(videoAttr);
     }
 #endif
@@ -516,9 +519,9 @@ Conference::takeOverMediaSourceControl(const std::string& callId)
         if (iter == mediaList.end()) {
             // Nothing to do if the call does not have a stream with
             // the requested media.
-            JAMI_DBG("[Call: %s] Does not have an active [%s] media source",
-                     callId.c_str(),
-                     MediaAttribute::mediaTypeToString(mediaType));
+            JAMI_DEBUG("[Call: {:s}] Does not have an active [{:s}] media source",
+                       callId,
+                       MediaAttribute::mediaTypeToString(mediaType));
             continue;
         }
 
@@ -568,14 +571,12 @@ Conference::requestMediaChange(const std::vector<libjami::MediaMap>& mediaList)
         return false;
     }
 
-    JAMI_DBG("[conf %s] Request media change", getConfId().c_str());
+    JAMI_DEBUG("[conf {:s}] Request media change", getConfId());
 
     auto mediaAttrList = MediaAttribute::buildMediaAttributesList(mediaList, false);
 
     for (auto const& mediaAttr : mediaAttrList) {
-        JAMI_DBG("[conf %s] New requested media: %s",
-                 getConfId().c_str(),
-                 mediaAttr.toString(true).c_str());
+        JAMI_DEBUG("[conf {:s}] New requested media: {:s}", getConfId(), mediaAttr.toString(true));
     }
 
     std::vector<std::string> newVideoInputs;
@@ -616,7 +617,7 @@ void
 Conference::handleMediaChangeRequest(const std::shared_ptr<Call>& call,
                                      const std::vector<libjami::MediaMap>& remoteMediaList)
 {
-    JAMI_DBG("Conf [%s] Answer to media change request", getConfId().c_str());
+    JAMI_DEBUG("Conf [{:s}] Answer to media change request", getConfId());
     auto currentMediaList = hostSources_;
 
 #ifdef ENABLE_VIDEO
@@ -664,7 +665,7 @@ Conference::handleMediaChangeRequest(const std::shared_ptr<Call>& call,
 void
 Conference::addParticipant(const std::string& participant_id)
 {
-    JAMI_DBG("Adding call %s to conference %s", participant_id.c_str(), id_.c_str());
+    JAMI_DEBUG("Adding call {:s} to conference {:s}", participant_id, id_);
 
     jami_tracepoint(conference_add_participant, id_.c_str(), participant_id.c_str());
 
@@ -674,19 +675,15 @@ Conference::addParticipant(const std::string& participant_id)
             return;
     }
 
-    // Check if participant was muted before conference
     if (auto call = getCall(participant_id)) {
-        if (call->isPeerMuted()) {
+        // Check if participant was muted before conference
+        if (call->isPeerMuted())
             participantsMuted_.emplace(call->getCallId());
-        }
 
         // NOTE:
         // When a call joins a conference, the media source of the call
         // will be set to the output of the conference mixer.
         takeOverMediaSourceControl(participant_id);
-    }
-
-    if (auto call = getCall(participant_id)) {
         auto w = call->getAccount();
         auto account = w.lock();
         if (account) {
@@ -708,9 +705,7 @@ Conference::addParticipant(const std::string& participant_id)
             if (account->isAllModerators())
                 moderators_.emplace(getRemoteId(call));
         }
-    }
 #ifdef ENABLE_VIDEO
-    if (auto call = getCall(participant_id)) {
         // In conference, if a participant joins with an audio only
         // call, it must be listed in the audioonlylist.
         auto mediaList = call->getMediaAttributeList();
@@ -722,17 +717,17 @@ Conference::addParticipant(const std::string& participant_id)
         call->enterConference(shared_from_this());
         // Continue the recording for the conference if one participant was recording
         if (call->isRecording()) {
-            JAMI_DBG("Stop recording for call %s", call->getCallId().c_str());
+            JAMI_DEBUG("Stop recording for call {:s}", call->getCallId());
             call->toggleRecording();
             if (not this->isRecording()) {
-                JAMI_DBG("One participant was recording, start recording for conference %s",
-                         getConfId().c_str());
+                JAMI_DEBUG("One participant was recording, start recording for conference {:s}",
+                           getConfId());
                 this->toggleRecording();
             }
         }
+#endif // ENABLE_VIDEO
     } else
         JAMI_ERR("no call associate to participant %s", participant_id.c_str());
-#endif // ENABLE_VIDEO
 #ifdef ENABLE_PLUGIN
     createConfAVStreams();
 #endif
@@ -847,7 +842,8 @@ Conference::sendConferenceInfos()
 
     // Inform client that layout has changed
     jami::emitSignal<libjami::CallSignal::OnConferenceInfosUpdated>(id_,
-                                                                  confInfo.toVectorMapStringString());
+                                                                    confInfo
+                                                                        .toVectorMapStringString());
 }
 
 #ifdef ENABLE_VIDEO
@@ -869,6 +865,7 @@ Conference::createSinks(const ConfInfo& infos)
 void
 Conference::removeParticipant(const std::string& participant_id)
 {
+    JAMI_DEBUG("Remove call {:s} in conference {:s}", participant_id, id_);
     {
         std::lock_guard<std::mutex> lk(participantsMtx_);
         if (!participants_.erase(participant_id))
@@ -955,9 +952,11 @@ Conference::detachLocalParticipant()
             "Invalid conference state in detach participant: current \"%s\" - expected \"%s\"",
             getStateStr(),
             "ACTIVE_ATTACHED");
+        return;
     }
 
     setLocalHostDefaultMediaSource();
+    setState(State::ACTIVE_DETACHED);
 }
 
 void
@@ -1058,7 +1057,7 @@ void
 Conference::switchInput(const std::string& input)
 {
 #ifdef ENABLE_VIDEO
-    JAMI_DBG("[Conf:%s] Setting video input to %s", id_.c_str(), input.c_str());
+    JAMI_DEBUG("[Conf:{:s}] Setting video input to {:s}", id_, input);
     std::vector<MediaAttribute> newSources;
     auto firstVideo = true;
     // Rewrite hostSources (remove all except one video input)
@@ -1232,11 +1231,11 @@ Conference::setHandRaised(const std::string& deviceId, const bool& state)
                     callDeviceId = transport->deviceId();
                 if (deviceId == callDeviceId) {
                     if (state and not isPeerRequiringAttention) {
-                        JAMI_DBG("Raise %s hand", deviceId.c_str());
+                        JAMI_DEBUG("Raise {:s} hand", deviceId);
                         handsRaised_.emplace(deviceId);
                         updateHandsRaised();
                     } else if (not state and isPeerRequiringAttention) {
-                        JAMI_DBG("Remove %s raised hand", deviceId.c_str());
+                        JAMI_DEBUG("Remove {:s} raised hand", deviceId);
                         handsRaised_.erase(deviceId);
                         updateHandsRaised();
                     }
@@ -1301,11 +1300,11 @@ Conference::setModerator(const std::string& participant_id, const bool& state)
             auto isPeerModerator = isModerator(participant_id);
             if (participant_id == getRemoteId(call)) {
                 if (state and not isPeerModerator) {
-                    JAMI_DBG("Add %s as moderator", participant_id.c_str());
+                    JAMI_DEBUG("Add {:s} as moderator", participant_id);
                     moderators_.emplace(participant_id);
                     updateModerators();
                 } else if (not state and isPeerModerator) {
-                    JAMI_DBG("Remove %s as moderator", participant_id.c_str());
+                    JAMI_DEBUG("Remove {:s} as moderator", participant_id);
                     moderators_.erase(participant_id);
                     updateModerators();
                 }
@@ -1417,12 +1416,12 @@ Conference::muteCall(const std::string& callId, bool state)
 {
     auto isPartMuted = isMuted(callId);
     if (state and not isPartMuted) {
-        JAMI_DBG("Mute participant %.*s", (int) callId.size(), callId.data());
+        JAMI_DEBUG("Mute participant {:s}", callId);
         participantsMuted_.emplace(callId);
         unbindParticipant(callId);
         updateMuted();
     } else if (not state and isPartMuted) {
-        JAMI_DBG("Unmute participant %.*s", (int) callId.size(), callId.data());
+        JAMI_DEBUG("Unmute participant {:s}", callId);
         participantsMuted_.erase(callId);
         bindParticipant(callId);
         updateMuted();
@@ -1596,7 +1595,8 @@ Conference::muteLocalHost(bool is_muted, const std::string& mediaType)
 {
     if (mediaType.compare(libjami::Media::Details::MEDIA_TYPE_AUDIO) == 0) {
         if (is_muted == isMediaSourceMuted(MediaType::MEDIA_AUDIO)) {
-            JAMI_DBG("Local audio source already in [%s] state", is_muted ? "muted" : "un-muted");
+            JAMI_DEBUG("Local audio source already in [{:s}] state",
+                       is_muted ? "muted" : "un-muted");
             return;
         }
 
@@ -1620,7 +1620,8 @@ Conference::muteLocalHost(bool is_muted, const std::string& mediaType)
         }
 
         if (is_muted == isMediaSourceMuted(MediaType::MEDIA_VIDEO)) {
-            JAMI_DBG("Local video source already in [%s] state", is_muted ? "muted" : "un-muted");
+            JAMI_DEBUG("Local video source already in [{:s}] state",
+                       is_muted ? "muted" : "un-muted");
             return;
         }
         setLocalHostMuteState(MediaType::MEDIA_VIDEO, is_muted);
diff --git a/src/conference.h b/src/conference.h
index defb1f540affde3c3e80a85f9e90a9beda8c73f0..7212daedbccdfca9ec0c2ff1cd590c45664ffef3 100644
--- a/src/conference.h
+++ b/src/conference.h
@@ -24,6 +24,7 @@
 #include "config.h"
 #endif
 
+#include <chrono>
 #include <set>
 #include <string>
 #include <memory>
@@ -187,6 +188,7 @@ struct ConfInfo : public std::vector<ParticipantInfo>
 };
 
 using ParticipantSet = std::set<std::string>;
+using clock = std::chrono::steady_clock;
 
 class Conference : public Recordable, public std::enable_shared_from_this<Conference>
 {
@@ -196,7 +198,9 @@ public:
     /**
      * Constructor for this class, increment static counter
      */
-    explicit Conference(const std::shared_ptr<Account>&, bool attachHost = true);
+    explicit Conference(const std::shared_ptr<Account>&,
+                        const std::string& confId = "",
+                        bool attachHost = true);
 
     /**
      * Destructor for this class, decrement static counter
@@ -222,6 +226,11 @@ public:
      */
     void setState(State state);
 
+    /**
+     * Set a callback that will be called when the conference will be destroyed
+     */
+    void onShutdown(std::function<void(int)> cb) { shutdownCb_ = std::move(cb); }
+
     /**
      * Return a string description of the conference state
      */
@@ -399,6 +408,17 @@ public:
     void stopRecording() override;
     bool startRecording(const std::string& path) override;
 
+    /**
+     * @return Conference duration in milliseconds
+     */
+    std::chrono::milliseconds getDuration() const
+    {
+        return duration_start_ == clock::time_point::min()
+                   ? std::chrono::milliseconds::zero()
+                   : std::chrono::duration_cast<std::chrono::milliseconds>(clock::now()
+                                                                           - duration_start_);
+    }
+
 private:
     std::weak_ptr<Conference> weak()
     {
@@ -513,6 +533,9 @@ private:
 
     ConfProtocolParser parser_;
     std::string getRemoteId(const std::shared_ptr<jami::Call>& call) const;
+
+    std::function<void(int)> shutdownCb_;
+    clock::time_point duration_start_;
 };
 
 } // namespace jami
diff --git a/src/jami/configurationmanager_interface.h b/src/jami/configurationmanager_interface.h
index d8a3ef0d16922d09159c28b5975e7fd2bf66573d..4ad507ba7bbc8e405d9b2eaafbb7a4a7d64ce860 100644
--- a/src/jami/configurationmanager_interface.h
+++ b/src/jami/configurationmanager_interface.h
@@ -57,36 +57,36 @@ LIBJAMI_PUBLIC std::map<std::string, std::string> getAccountDetails(const std::s
 LIBJAMI_PUBLIC std::map<std::string, std::string> getVolatileAccountDetails(
     const std::string& accountID);
 LIBJAMI_PUBLIC void setAccountDetails(const std::string& accountID,
-                                    const std::map<std::string, std::string>& details);
+                                      const std::map<std::string, std::string>& details);
 LIBJAMI_PUBLIC void setAccountActive(const std::string& accountID,
-                                   bool active,
-                                   bool shutdownConnections = false);
+                                     bool active,
+                                     bool shutdownConnections = false);
 LIBJAMI_PUBLIC std::map<std::string, std::string> getAccountTemplate(const std::string& accountType);
 LIBJAMI_PUBLIC std::string addAccount(const std::map<std::string, std::string>& details,
-                                    const std::string& accountID = {});
+                                      const std::string& accountID = {});
 LIBJAMI_PUBLIC void monitor(bool continuous);
 LIBJAMI_PUBLIC bool exportOnRing(const std::string& accountID, const std::string& password);
 LIBJAMI_PUBLIC bool exportToFile(const std::string& accountID,
-                               const std::string& destinationPath,
-                               const std::string& password = {});
+                                 const std::string& destinationPath,
+                                 const std::string& password = {});
 LIBJAMI_PUBLIC bool revokeDevice(const std::string& accountID,
-                               const std::string& password,
-                               const std::string& deviceID);
+                                 const std::string& password,
+                                 const std::string& deviceID);
 LIBJAMI_PUBLIC std::map<std::string, std::string> getKnownRingDevices(const std::string& accountID);
 LIBJAMI_PUBLIC bool changeAccountPassword(const std::string& accountID,
-                                        const std::string& password_old,
-                                        const std::string& password_new);
+                                          const std::string& password_old,
+                                          const std::string& password_new);
 LIBJAMI_PUBLIC bool isPasswordValid(const std::string& accountID, const std::string& password);
 
 LIBJAMI_PUBLIC bool lookupName(const std::string& account,
-                             const std::string& nameserver,
-                             const std::string& name);
+                               const std::string& nameserver,
+                               const std::string& name);
 LIBJAMI_PUBLIC bool lookupAddress(const std::string& account,
-                                const std::string& nameserver,
-                                const std::string& address);
+                                  const std::string& nameserver,
+                                  const std::string& address);
 LIBJAMI_PUBLIC bool registerName(const std::string& account,
-                               const std::string& password,
-                               const std::string& name);
+                                 const std::string& password,
+                                 const std::string& name);
 LIBJAMI_PUBLIC bool searchUser(const std::string& account, const std::string& query);
 
 LIBJAMI_PUBLIC void removeAccount(const std::string& accountID);
@@ -94,34 +94,34 @@ LIBJAMI_PUBLIC std::vector<std::string> getAccountList();
 LIBJAMI_PUBLIC void sendRegister(const std::string& accountID, bool enable);
 LIBJAMI_PUBLIC void registerAllAccounts(void);
 LIBJAMI_PUBLIC uint64_t sendAccountTextMessage(const std::string& accountID,
-                                             const std::string& to,
-                                             const std::map<std::string, std::string>& payloads);
+                                               const std::string& to,
+                                               const std::map<std::string, std::string>& payloads);
 LIBJAMI_PUBLIC bool cancelMessage(const std::string& accountID, uint64_t message);
 LIBJAMI_PUBLIC std::vector<Message> getLastMessages(const std::string& accountID,
-                                                  const uint64_t& base_timestamp);
+                                                    const uint64_t& base_timestamp);
 LIBJAMI_PUBLIC std::map<std::string, std::string> getNearbyPeers(const std::string& accountID);
 LIBJAMI_PUBLIC int getMessageStatus(uint64_t id);
 LIBJAMI_PUBLIC int getMessageStatus(const std::string& accountID, uint64_t id);
 LIBJAMI_PUBLIC void setIsComposing(const std::string& accountID,
-                                 const std::string& conversationUri,
-                                 bool isWriting);
+                                   const std::string& conversationUri,
+                                   bool isWriting);
 LIBJAMI_PUBLIC bool setMessageDisplayed(const std::string& accountID,
-                                      const std::string& conversationUri,
-                                      const std::string& messageId,
-                                      int status);
+                                        const std::string& conversationUri,
+                                        const std::string& messageId,
+                                        int status);
 
 LIBJAMI_PUBLIC std::vector<unsigned> getCodecList();
 LIBJAMI_PUBLIC std::vector<std::string> getSupportedTlsMethod();
 LIBJAMI_PUBLIC std::vector<std::string> getSupportedCiphers(const std::string& accountID);
 LIBJAMI_PUBLIC std::map<std::string, std::string> getCodecDetails(const std::string& accountID,
-                                                                const unsigned& codecId);
+                                                                  const unsigned& codecId);
 LIBJAMI_PUBLIC bool setCodecDetails(const std::string& accountID,
-                                  const unsigned& codecId,
-                                  const std::map<std::string, std::string>& details);
+                                    const unsigned& codecId,
+                                    const std::map<std::string, std::string>& details);
 LIBJAMI_PUBLIC std::vector<unsigned> getActiveCodecList(const std::string& accountID);
 
 LIBJAMI_PUBLIC void setActiveCodecList(const std::string& accountID,
-                                     const std::vector<unsigned>& list);
+                                       const std::vector<unsigned>& list);
 
 LIBJAMI_PUBLIC std::vector<std::string> getAudioPluginList();
 LIBJAMI_PUBLIC void setAudioPlugin(const std::string& audioPlugin);
@@ -175,7 +175,7 @@ LIBJAMI_PUBLIC void setAccountsOrder(const std::string& order);
 LIBJAMI_PUBLIC std::vector<std::map<std::string, std::string>> getCredentials(
     const std::string& accountID);
 LIBJAMI_PUBLIC void setCredentials(const std::string& accountID,
-                                 const std::vector<std::map<std::string, std::string>>& details);
+                                   const std::vector<std::map<std::string, std::string>>& details);
 
 LIBJAMI_PUBLIC std::string getAddrFromInterfaceName(const std::string& iface);
 
@@ -188,8 +188,8 @@ LIBJAMI_PUBLIC double getVolume(const std::string& device);
 /*
  * Security
  */
-LIBJAMI_PUBLIC std::map<std::string, std::string> validateCertificate(const std::string& accountId,
-                                                                    const std::string& certificate);
+LIBJAMI_PUBLIC std::map<std::string, std::string> validateCertificate(
+    const std::string& accountId, const std::string& certificate);
 LIBJAMI_PUBLIC std::map<std::string, std::string> validateCertificatePath(
     const std::string& accountId,
     const std::string& certificatePath,
@@ -197,7 +197,8 @@ LIBJAMI_PUBLIC std::map<std::string, std::string> validateCertificatePath(
     const std::string& privateKeyPassword,
     const std::string& caList);
 
-LIBJAMI_PUBLIC std::map<std::string, std::string> getCertificateDetails(const std::string& certificate);
+LIBJAMI_PUBLIC std::map<std::string, std::string> getCertificateDetails(
+    const std::string& certificate);
 LIBJAMI_PUBLIC std::map<std::string, std::string> getCertificateDetailsPath(
     const std::string& certificatePath,
     const std::string& privateKey,
@@ -206,7 +207,7 @@ LIBJAMI_PUBLIC std::map<std::string, std::string> getCertificateDetailsPath(
 LIBJAMI_PUBLIC std::vector<std::string> getPinnedCertificates();
 
 LIBJAMI_PUBLIC std::vector<std::string> pinCertificate(const std::vector<uint8_t>& certificate,
-                                                     bool local);
+                                                       bool local);
 LIBJAMI_PUBLIC bool unpinCertificate(const std::string& certId);
 
 LIBJAMI_PUBLIC void pinCertificatePath(const std::string& path);
@@ -214,10 +215,10 @@ LIBJAMI_PUBLIC unsigned unpinCertificatePath(const std::string& path);
 
 LIBJAMI_PUBLIC bool pinRemoteCertificate(const std::string& accountId, const std::string& certId);
 LIBJAMI_PUBLIC bool setCertificateStatus(const std::string& account,
-                                       const std::string& certId,
-                                       const std::string& status);
+                                         const std::string& certId,
+                                         const std::string& status);
 LIBJAMI_PUBLIC std::vector<std::string> getCertificatesByStatus(const std::string& account,
-                                                              const std::string& status);
+                                                                const std::string& status);
 
 /* contact requests */
 LIBJAMI_PUBLIC std::vector<std::map<std::string, std::string>> getTrustRequests(
@@ -225,15 +226,15 @@ LIBJAMI_PUBLIC std::vector<std::map<std::string, std::string>> getTrustRequests(
 LIBJAMI_PUBLIC bool acceptTrustRequest(const std::string& accountId, const std::string& from);
 LIBJAMI_PUBLIC bool discardTrustRequest(const std::string& accountId, const std::string& from);
 LIBJAMI_PUBLIC void sendTrustRequest(const std::string& accountId,
-                                   const std::string& to,
-                                   const std::vector<uint8_t>& payload = {});
+                                     const std::string& to,
+                                     const std::vector<uint8_t>& payload = {});
 
 /* Contacts */
 
 LIBJAMI_PUBLIC void addContact(const std::string& accountId, const std::string& uri);
 LIBJAMI_PUBLIC void removeContact(const std::string& accountId, const std::string& uri, bool ban);
 LIBJAMI_PUBLIC std::map<std::string, std::string> getContactDetails(const std::string& accountId,
-                                                                  const std::string& uri);
+                                                                    const std::string& uri);
 LIBJAMI_PUBLIC std::vector<std::map<std::string, std::string>> getContacts(
     const std::string& accountId);
 
@@ -261,7 +262,7 @@ LIBJAMI_PUBLIC void setPushNotificationTopic(const std::string& topic);
  * To be called by clients with relevant data when a push notification is received.
  */
 LIBJAMI_PUBLIC void pushNotificationReceived(const std::string& from,
-                                           const std::map<std::string, std::string>& data);
+                                             const std::map<std::string, std::string>& data);
 
 /**
  * Returns whether or not the audio meter is enabled for ring buffer @id.
@@ -281,8 +282,8 @@ LIBJAMI_PUBLIC void setAudioMeterState(const std::string& id, bool state);
  * Add/remove default moderator for conferences
  */
 LIBJAMI_PUBLIC void setDefaultModerator(const std::string& accountID,
-                                      const std::string& peerURI,
-                                      bool state);
+                                        const std::string& peerURI,
+                                        bool state);
 
 /**
  * Get default moderators for an account
@@ -386,6 +387,19 @@ struct LIBJAMI_PUBLIC ConfigurationSignal
                              const std::string& /*message_id*/,
                              int /*state*/);
     };
+    struct LIBJAMI_PUBLIC NeedsHost
+    {
+        constexpr static const char* name = "NeedsHost";
+        using cb_type = void(const std::string& /*account_id*/,
+                             const std::string& /*conversation_id*/);
+    };
+    struct LIBJAMI_PUBLIC ActiveCallsChanged
+    {
+        constexpr static const char* name = "ActiveCallsChanged";
+        using cb_type = void(const std::string& /*account_id*/,
+                             const std::string& /*conversation_id*/,
+                             const std::vector<std::map<std::string, std::string>>& /*activeCalls*/);
+    };
     struct LIBJAMI_PUBLIC ProfileReceived
     {
         constexpr static const char* name = "ProfileReceived";
diff --git a/src/jami/conversation_interface.h b/src/jami/conversation_interface.h
index 011af5d64e16b15ba877a84eb66aad4a618bd657..0a9901f1a43887fc2b7de0857e5ae2572e246e73 100644
--- a/src/jami/conversation_interface.h
+++ b/src/jami/conversation_interface.h
@@ -34,65 +34,69 @@ namespace libjami {
 // Conversation management
 LIBJAMI_PUBLIC std::string startConversation(const std::string& accountId);
 LIBJAMI_PUBLIC void acceptConversationRequest(const std::string& accountId,
-                                            const std::string& conversationId);
+                                              const std::string& conversationId);
 LIBJAMI_PUBLIC void declineConversationRequest(const std::string& accountId,
-                                             const std::string& conversationId);
+                                               const std::string& conversationId);
 LIBJAMI_PUBLIC bool removeConversation(const std::string& accountId,
-                                     const std::string& conversationId);
+                                       const std::string& conversationId);
 LIBJAMI_PUBLIC std::vector<std::string> getConversations(const std::string& accountId);
 LIBJAMI_PUBLIC std::vector<std::map<std::string, std::string>> getConversationRequests(
     const std::string& accountId);
 
+// Calls
+LIBJAMI_PUBLIC std::vector<std::map<std::string, std::string>> getActiveCalls(
+    const std::string& accountId, const std::string& conversationId);
+
 // Conversation's infos management
 LIBJAMI_PUBLIC void updateConversationInfos(const std::string& accountId,
-                                          const std::string& conversationId,
-                                          const std::map<std::string, std::string>& infos);
-LIBJAMI_PUBLIC std::map<std::string, std::string> conversationInfos(const std::string& accountId,
-                                                                  const std::string& conversationId);
+                                            const std::string& conversationId,
+                                            const std::map<std::string, std::string>& infos);
+LIBJAMI_PUBLIC std::map<std::string, std::string> conversationInfos(
+    const std::string& accountId, const std::string& conversationId);
 LIBJAMI_PUBLIC void setConversationPreferences(const std::string& accountId,
-                                             const std::string& conversationId,
-                                             const std::map<std::string, std::string>& prefs);
+                                               const std::string& conversationId,
+                                               const std::map<std::string, std::string>& prefs);
 LIBJAMI_PUBLIC std::map<std::string, std::string> getConversationPreferences(
     const std::string& accountId, const std::string& conversationId);
 
 // Member management
 LIBJAMI_PUBLIC void addConversationMember(const std::string& accountId,
-                                        const std::string& conversationId,
-                                        const std::string& contactUri);
+                                          const std::string& conversationId,
+                                          const std::string& contactUri);
 LIBJAMI_PUBLIC void removeConversationMember(const std::string& accountId,
-                                           const std::string& conversationId,
-                                           const std::string& contactUri);
+                                             const std::string& conversationId,
+                                             const std::string& contactUri);
 LIBJAMI_PUBLIC std::vector<std::map<std::string, std::string>> getConversationMembers(
     const std::string& accountId, const std::string& conversationId);
 
 // Message send/load
 LIBJAMI_PUBLIC void sendMessage(const std::string& accountId,
-                              const std::string& conversationId,
-                              const std::string& message,
-                              const std::string& replyTo,
-                              const int32_t& flag = 0);
+                                const std::string& conversationId,
+                                const std::string& message,
+                                const std::string& replyTo,
+                                const int32_t& flag = 0);
 LIBJAMI_PUBLIC uint32_t loadConversationMessages(const std::string& accountId,
-                                               const std::string& conversationId,
-                                               const std::string& fromMessage,
-                                               size_t n);
+                                                 const std::string& conversationId,
+                                                 const std::string& fromMessage,
+                                                 size_t n);
 LIBJAMI_PUBLIC uint32_t loadConversationUntil(const std::string& accountId,
-                                            const std::string& conversationId,
-                                            const std::string& fromMessage,
-                                            const std::string& toMessage);
+                                              const std::string& conversationId,
+                                              const std::string& fromMessage,
+                                              const std::string& toMessage);
 LIBJAMI_PUBLIC uint32_t countInteractions(const std::string& accountId,
-                                        const std::string& conversationId,
-                                        const std::string& toId,
-                                        const std::string& fromId,
-                                        const std::string& authorUri);
+                                          const std::string& conversationId,
+                                          const std::string& toId,
+                                          const std::string& fromId,
+                                          const std::string& authorUri);
 LIBJAMI_PUBLIC uint32_t searchConversation(const std::string& accountId,
-                                         const std::string& conversationId,
-                                         const std::string& author,
-                                         const std::string& lastId,
-                                         const std::string& regexSearch,
-                                         const std::string& type,
-                                         const int64_t& after,
-                                         const int64_t& before,
-                                         const uint32_t& maxResult);
+                                           const std::string& conversationId,
+                                           const std::string& author,
+                                           const std::string& lastId,
+                                           const std::string& regexSearch,
+                                           const std::string& type,
+                                           const int64_t& after,
+                                           const int64_t& before,
+                                           const uint32_t& maxResult);
 
 struct LIBJAMI_PUBLIC ConversationSignal
 {
diff --git a/src/jamidht/conversation.cpp b/src/jamidht/conversation.cpp
index 2c67d01ce245ab397f753f0968717eda6c98e613..d6c43c91b89977295ca1a0deab0300186b4b8778 100644
--- a/src/jamidht/conversation.cpp
+++ b/src/jamidht/conversation.cpp
@@ -28,7 +28,6 @@
 #include <json/json.h>
 #include <string_view>
 #include <opendht/thread_pool.h>
-#include <tuple>
 
 #ifdef ENABLE_PLUGIN
 #include "manager.h"
@@ -154,6 +153,12 @@ public:
             throw std::logic_error("Couldn't clone repository");
         }
         init();
+        // To get current active calls from previous commit, we need to read the history
+        auto convCommits = loadMessages({});
+        std::reverse(std::begin(convCommits), std::end(convCommits));
+        for (const auto& c : convCommits) {
+            updateActiveCalls(c);
+        }
     }
 
     void init()
@@ -171,17 +176,31 @@ public:
                                  + ConversationMapKeys::LAST_DISPLAYED;
             preferencesPath_ = conversationDataPath_ + DIR_SEPARATOR_STR
                                + ConversationMapKeys::PREFERENCES;
+            activeCallsPath_ = conversationDataPath_ + DIR_SEPARATOR_STR
+                               + ConversationMapKeys::ACTIVE_CALLS;
+            hostedCallsPath_ = conversationDataPath_ + DIR_SEPARATOR_STR
+                               + ConversationMapKeys::HOSTED_CALLS;
             loadFetched();
             loadSending();
             loadLastDisplayed();
         }
     }
 
+    /**
+     * If, for whatever reason, the daemon is stopped while hosting a conference,
+     * we need to announce the end of this call when restarting.
+     * To avoid to keep active calls forever.
+     */
+    std::vector<std::string> announceEndedCalls();
+
     ~Impl() = default;
 
+    std::vector<std::string> refreshActiveCalls();
     bool isAdmin() const;
     std::string repoPath() const;
 
+    // Protect against parallel commits in the repo
+    // As the index can add files to the commit we want.
     std::mutex writeMtx_ {};
     void announce(const std::string& commitId) const
     {
@@ -204,6 +223,100 @@ public:
         announce(repository_->convCommitToMap(convcommits));
     }
 
+    /**
+     * Update activeCalls_ via announced commits (in load or via new commits)
+     * @param commit        Commit to check
+     * @param eraseOnly     If we want to ignore added commits
+     * @note eraseOnly is used by loadMessages. This is a fail-safe, this SHOULD NOT happen
+     */
+    void updateActiveCalls(const std::map<std::string, std::string>& commit,
+                           bool eraseOnly = false) const
+    {
+        if (!repository_)
+            return;
+        if (commit.at("type") == "member") {
+            // In this case, we need to check if we are not removing a hosting member or device
+            std::lock_guard<std::mutex> lk(activeCallsMtx_);
+            auto it = activeCalls_.begin();
+            auto updateActives = false;
+            while (it != activeCalls_.end()) {
+                if (it->at("uri") == commit.at("uri") || it->at("device") == commit.at("uri")) {
+                    JAMI_DEBUG("Removing {:s} from the active calls, because {:s} left",
+                               it->at("id"),
+                               commit.at("uri"));
+                    it = activeCalls_.erase(it);
+                    updateActives = true;
+                } else {
+                    ++it;
+                }
+            }
+            if (updateActives) {
+                saveActiveCalls();
+                emitSignal<libjami::ConfigurationSignal::ActiveCallsChanged>(accountId_,
+                                                                             repository_->id(),
+                                                                             activeCalls_);
+            }
+            return;
+        }
+        // Else, it's a call information
+        if (commit.find("confId") != commit.end() && commit.find("uri") != commit.end()
+            && commit.find("device") != commit.end()) {
+            auto convId = repository_->id();
+            auto confId = commit.at("confId");
+            auto uri = commit.at("uri");
+            auto device = commit.at("device");
+            if (commit.find("duration") == commit.end()) {
+                if (!eraseOnly) {
+                    JAMI_DEBUG(
+                        "swarm:{:s} new current call detected: {:s} on device {:s}, account {:s}",
+                        convId,
+                        confId,
+                        device,
+                        uri);
+                    std::lock_guard<std::mutex> lk(activeCallsMtx_);
+                    std::map<std::string, std::string> activeCall;
+                    activeCall["id"] = confId;
+                    activeCall["uri"] = uri;
+                    activeCall["device"] = device;
+                    activeCalls_.emplace_back(activeCall);
+                    saveActiveCalls();
+                    emitSignal<libjami::ConfigurationSignal::ActiveCallsChanged>(accountId_,
+                                                                                 repository_->id(),
+                                                                                 activeCalls_);
+                }
+            } else {
+                std::lock_guard<std::mutex> lk(activeCallsMtx_);
+                auto itActive = std::find_if(activeCalls_.begin(),
+                                             activeCalls_.end(),
+                                             [&](auto value) {
+                                                 return value["id"] == confId && value["uri"] == uri
+                                                        && value["device"] == device;
+                                             });
+                if (itActive != activeCalls_.end()) {
+                    activeCalls_.erase(itActive);
+                    if (eraseOnly) {
+                        JAMI_WARNING("previous swarm:{:s} call finished detected: {:s} on device "
+                                     "{:s}, account {:s}",
+                                     convId,
+                                     confId,
+                                     device,
+                                     uri);
+                    } else {
+                        JAMI_DEBUG("swarm:{:s} call finished: {:s} on device {:s}, account {:s}",
+                                   convId,
+                                   confId,
+                                   device,
+                                   uri);
+                    }
+                }
+                saveActiveCalls();
+                emitSignal<libjami::ConfigurationSignal::ActiveCallsChanged>(accountId_,
+                                                                             repository_->id(),
+                                                                             activeCalls_);
+            }
+        }
+    }
+
     void announce(const std::vector<std::map<std::string, std::string>>& commits) const
     {
         auto shared = account_.lock();
@@ -231,14 +344,18 @@ public:
                             action = 3;
                         else if (actionStr == "unban")
                             action = 4;
+                        if (actionStr == "ban" || actionStr == "remove") {
+                            // In this case, a potential host was removed during a call.
+                            updateActiveCalls(c);
+                        }
                         if (action != -1) {
                             announceMember = true;
-                            emitSignal<libjami::ConversationSignal::ConversationMemberEvent>(accountId_,
-                                                                                           convId,
-                                                                                           uri,
-                                                                                           action);
+                            emitSignal<libjami::ConversationSignal::ConversationMemberEvent>(
+                                accountId_, convId, uri, action);
                         }
                     }
+                } else if (c.at("type") == "application/call-history+json") {
+                    updateActiveCalls(c);
                 }
 #ifdef ENABLE_PLUGIN
                 auto& pluginChatManager
@@ -348,6 +465,46 @@ public:
         msgpack::pack(file, lastDisplayed_);
     }
 
+    void loadActiveCalls() const
+    {
+        try {
+            // read file
+            auto file = fileutils::loadFile(activeCallsPath_);
+            // load values
+            msgpack::object_handle oh = msgpack::unpack((const char*) file.data(), file.size());
+            std::lock_guard<std::mutex> lk {activeCallsMtx_};
+            oh.get().convert(activeCalls_);
+        } catch (const std::exception& e) {
+            return;
+        }
+    }
+
+    void saveActiveCalls() const
+    {
+        std::ofstream file(activeCallsPath_, std::ios::trunc | std::ios::binary);
+        msgpack::pack(file, activeCalls_);
+    }
+
+    void loadHostedCalls() const
+    {
+        try {
+            // read file
+            auto file = fileutils::loadFile(hostedCallsPath_);
+            // load values
+            msgpack::object_handle oh = msgpack::unpack((const char*) file.data(), file.size());
+            std::lock_guard<std::mutex> lk {hostedCallsMtx_};
+            oh.get().convert(hostedCalls_);
+        } catch (const std::exception& e) {
+            return;
+        }
+    }
+
+    void saveHostedCalls() const
+    {
+        std::ofstream file(hostedCallsPath_, std::ios::trunc | std::ios::binary);
+        msgpack::pack(file, hostedCalls_);
+    }
+
     void voteUnban(const std::string& contactUri, const std::string& type, const OnDoneCb& cb);
 
     std::string bannedType(const std::string& uri) const
@@ -374,6 +531,7 @@ public:
     void pull();
     std::vector<std::map<std::string, std::string>> mergeHistory(const std::string& uri);
 
+    // Avoid multiple fetch/merges at the same time.
     std::mutex pullcbsMtx_ {};
     std::set<std::string> fetchingRemotes_ {}; // store current remote in fetch
     std::deque<std::tuple<std::string, std::string, OnPullCb>> pullcbs_ {};
@@ -392,6 +550,15 @@ public:
     mutable std::mutex lastDisplayedMtx_ {}; // for lastDisplayed_
     mutable std::map<std::string, std::string> lastDisplayed_ {};
     std::function<void(const std::string&, const std::string&)> lastDisplayedUpdatedCb_ {};
+
+    // Manage hosted calls on this device
+    std::string hostedCallsPath_ {};
+    mutable std::mutex hostedCallsMtx_ {};
+    mutable std::map<std::string, uint64_t /* start time */> hostedCalls_ {};
+    // Manage active calls for this conversation (can be hosted by other devices)
+    std::string activeCallsPath_ {};
+    mutable std::mutex activeCallsMtx_ {};
+    mutable std::vector<std::map<std::string, std::string>> activeCalls_ {};
 };
 
 bool
@@ -409,6 +576,65 @@ Conversation::Impl::isAdmin() const
     return fileutils::isFile(fileutils::getFullPath(adminsPath, uri + ".crt"));
 }
 
+std::vector<std::string>
+Conversation::Impl::refreshActiveCalls()
+{
+    loadActiveCalls();
+    loadHostedCalls();
+    return announceEndedCalls();
+}
+
+std::vector<std::string>
+Conversation::Impl::announceEndedCalls()
+{
+    auto shared = account_.lock();
+    // Handle current calls
+    std::vector<std::string> commits {};
+    std::unique_lock<std::mutex> lk(writeMtx_);
+    std::unique_lock<std::mutex> lkA(activeCallsMtx_);
+    for (const auto& hostedCall : hostedCalls_) {
+        // In this case, this means that we left
+        // the conference while still hosting it, so activeCalls
+        // will not be correctly updated
+        // We don't need to send notifications there, as peers will sync with presence
+        Json::Value value;
+        auto uri = shared->getUsername();
+        auto device = std::string(shared->currentDeviceId());
+        value["uri"] = uri;
+        value["device"] = device;
+        value["confId"] = hostedCall.first;
+        value["type"] = "application/call-history+json";
+        auto now = std::chrono::system_clock::now();
+        auto nowConverted = std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch())
+                                .count();
+        value["duration"] = std::to_string((nowConverted - hostedCall.second) * 1000);
+        Json::StreamWriterBuilder wbuilder;
+        auto itActive = std::find_if(activeCalls_.begin(),
+                                     activeCalls_.end(),
+                                     [confId = hostedCall.first, uri, device](auto value) {
+                                         return value.at("id") == confId && value.at("uri") == uri
+                                                && value.at("device") == device;
+                                     });
+        if (itActive != activeCalls_.end())
+            activeCalls_.erase(itActive);
+        wbuilder["commentStyle"] = "None";
+        wbuilder["indentation"] = "";
+        auto commit = repository_->commitMessage(Json::writeString(wbuilder, value));
+        commits.emplace_back(commit);
+
+        JAMI_DEBUG("Removing hosted conference... {:s}", hostedCall.first);
+    }
+    hostedCalls_.clear();
+    saveActiveCalls();
+    saveHostedCalls();
+    lkA.unlock();
+    lk.unlock();
+    if (!commits.empty()) {
+        announce(commits);
+    }
+    return commits;
+}
+
 std::string
 Conversation::Impl::repoPath() const
 {
@@ -842,7 +1068,7 @@ Conversation::Impl::mergeHistory(const std::string& uri)
             newCommits.emplace_back(*commit);
     }
 
-    JAMI_DBG("Successfully merge history with %s", uri.c_str());
+    JAMI_DEBUG("Successfully merge history with {:s}", uri);
     auto result = repository_->convCommitToMap(newCommits);
     for (const auto& commit : result) {
         auto it = commit.find("type");
@@ -1092,8 +1318,8 @@ Conversation::updatePreferences(const std::map<std::string, std::string>& map)
     std::ofstream file(filePath, std::ios::trunc | std::ios::binary);
     msgpack::pack(file, prefs);
     emitSignal<libjami::ConversationSignal::ConversationPreferencesUpdated>(pimpl_->accountId_,
-                                                                          id(),
-                                                                          std::move(prefs));
+                                                                            id(),
+                                                                            std::move(prefs));
 }
 
 std::map<std::string, std::string>
@@ -1155,20 +1381,21 @@ Conversation::onFileChannelRequest(const std::string& member,
         if (fileutils::isSymLink(path)) {
             fileutils::remove(path, true);
         }
-        JAMI_DBG("[Account %s] %s asked for non existing file %s in %s",
-                 pimpl_->accountId_.c_str(),
-                 member.c_str(),
-                 fileId.c_str(),
-                 id().c_str());
+        JAMI_DEBUG("[Account {:s}] {:s} asked for non existing file {:s} in {:s}",
+                   pimpl_->accountId_,
+                   member,
+                   fileId,
+                   id());
         return false;
     }
     // Check that our file is correct before sending
     if (verifyShaSum && commit->at("sha3sum") != fileutils::sha3File(path)) {
-        JAMI_DBG("[Account %s] %s asked for file %s in %s, but our version is not complete",
-                 pimpl_->accountId_.c_str(),
-                 member.c_str(),
-                 fileId.c_str(),
-                 id().c_str());
+        JAMI_DEBUG(
+            "[Account {:s}] {:s} asked for file {:s} in {:s}, but our version is not complete",
+            pimpl_->accountId_,
+            member,
+            fileId,
+            id());
         return false;
     }
     return true;
@@ -1329,6 +1556,12 @@ Conversation::updateLastDisplayed(const std::string& lastDisplayed)
         updateLastDisplayed();
 }
 
+std::vector<std::string>
+Conversation::refreshActiveCalls()
+{
+    return pimpl_->refreshActiveCalls();
+}
+
 void
 Conversation::onLastDisplayedUpdated(
     std::function<void(const std::string&, const std::string&)>&& lastDisplayedUpdatedCb)
@@ -1366,9 +1599,9 @@ Conversation::search(uint32_t req,
             auto commits = sthis->pimpl_->repository_->search(filter);
             if (commits.size() > 0)
                 emitSignal<libjami::ConversationSignal::MessagesFound>(req,
-                                                                     acc->getAccountID(),
-                                                                     sthis->id(),
-                                                                     std::move(commits));
+                                                                       acc->getAccountID(),
+                                                                       sthis->id(),
+                                                                       std::move(commits));
             // If we're the latest thread, inform client that the search is finished
             if ((*flag)-- == 1 /* decrement return the old value */) {
                 emitSignal<libjami::ConversationSignal::MessagesFound>(
@@ -1381,4 +1614,63 @@ Conversation::search(uint32_t req,
     });
 }
 
+void
+Conversation::hostConference(Json::Value&& message, OnDoneCb&& cb)
+{
+    if (!message.isMember("confId")) {
+        JAMI_ERR() << "Malformed commit";
+        return;
+    }
+
+    auto now = std::chrono::system_clock::now();
+    auto nowSecs = std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch()).count();
+    {
+        std::lock_guard<std::mutex> lk(pimpl_->hostedCallsMtx_);
+        pimpl_->hostedCalls_[message["confId"].asString()] = nowSecs;
+        pimpl_->saveHostedCalls();
+    }
+
+    sendMessage(std::move(message), "", {}, std::move(cb));
+}
+
+bool
+Conversation::isHosting(const std::string& confId) const
+{
+    auto shared = pimpl_->account_.lock();
+    if (!shared)
+        return false;
+    auto info = infos();
+    if (info["rdvDevice"] == shared->currentDeviceId() && info["rdvHost"] == shared->getUsername())
+        return true; // We are the current device Host
+    std::lock_guard<std::mutex> lk(pimpl_->hostedCallsMtx_);
+    return pimpl_->hostedCalls_.find(confId) != pimpl_->hostedCalls_.end();
+}
+
+void
+Conversation::removeActiveConference(Json::Value&& message, OnDoneCb&& cb)
+{
+    if (!message.isMember("confId")) {
+        JAMI_ERR() << "Malformed commit";
+        return;
+    }
+
+    auto erased = false;
+    {
+        std::lock_guard<std::mutex> lk(pimpl_->hostedCallsMtx_);
+        erased = pimpl_->hostedCalls_.erase(message["confId"].asString());
+    }
+    if (erased) {
+        pimpl_->saveHostedCalls();
+        sendMessage(std::move(message), "", {}, std::move(cb));
+    } else
+        cb(false, "");
+}
+
+std::vector<std::map<std::string, std::string>>
+Conversation::currentCalls() const
+{
+    std::lock_guard<std::mutex> lk(pimpl_->activeCallsMtx_);
+    return pimpl_->activeCalls_;
+}
+
 } // namespace jami
diff --git a/src/jamidht/conversation.h b/src/jamidht/conversation.h
index 29b0ee0e93fd9067249d80112d4cff6b988b2fa0..d5114d232a295554815f8a6935cc93d7ab1af0ae 100644
--- a/src/jamidht/conversation.h
+++ b/src/jamidht/conversation.h
@@ -26,6 +26,7 @@
 #include <memory>
 #include <json/json.h>
 #include <msgpack.hpp>
+#include <set>
 
 #include "jamidht/conversationrepository.h"
 #include "jami/datatransfer_interface.h"
@@ -41,6 +42,8 @@ static constexpr const char* ERASED = "erased";
 static constexpr const char* MEMBERS = "members";
 static constexpr const char* LAST_DISPLAYED = "lastDisplayed";
 static constexpr const char* PREFERENCES = "preferences";
+static constexpr const char* ACTIVE_CALLS = "activeCalls";
+static constexpr const char* HOSTED_CALLS = "hostedCalls";
 static constexpr const char* CACHED = "cached";
 static constexpr const char* RECEIVED = "received";
 static constexpr const char* DECLINED = "declined";
@@ -122,6 +125,14 @@ public:
                  const std::string& conversationId);
     ~Conversation();
 
+    /**
+     * Refresh active calls.
+     * @note: If the host crash during a call, when initializing, we need to update
+     * and commit all the crashed calls
+     * @return  Commits added
+     */
+    std::vector<std::string> refreshActiveCalls();
+
     /**
      * Add a callback to update upper layers
      * @note to call after the construction (and before ConversationReady)
@@ -385,6 +396,33 @@ public:
     void search(uint32_t req,
                 const Filter& filter,
                 const std::shared_ptr<std::atomic_int>& flag) const;
+    /**
+     * Host a conference in the conversation
+     * @note the message must have "confId"
+     * @note Update hostedCalls_ and commit in the conversation
+     * @param message       message to commit
+     * @param cb            callback triggered when committed
+     */
+    void hostConference(Json::Value&& message, OnDoneCb&& cb = {});
+    /**
+     * Announce the end of a call
+     * @note the message must have "confId"
+     * @note called when conference is finished
+     * @param message       message to commit
+     * @param cb            callback triggered when committed
+     */
+    void removeActiveConference(Json::Value&& message, OnDoneCb&& cb = {});
+    /**
+     * Check if we're currently hosting this conference
+     * @param confId
+     * @return true if hosting
+     */
+    bool isHosting(const std::string& confId) const;
+    /**
+     * Return current detected calls
+     * @return a vector of map with the following keys: "id", "uri", "device"
+     */
+    std::vector<std::map<std::string, std::string>> currentCalls() const;
 
 private:
     std::shared_ptr<Conversation> shared()
diff --git a/src/jamidht/conversation_module.cpp b/src/jamidht/conversation_module.cpp
index 610014e210d7acc36d8532d2ce8b457830b4fe90..14611bbca971fd88540df5e082000a0a019eb9e0 100644
--- a/src/jamidht/conversation_module.cpp
+++ b/src/jamidht/conversation_module.cpp
@@ -25,11 +25,13 @@
 #include <opendht/thread_pool.h>
 
 #include "account_const.h"
+#include "call.h"
 #include "client/ring_signal.h"
 #include "fileutils.h"
 #include "jamidht/account_manager.h"
 #include "jamidht/jamiaccount.h"
 #include "manager.h"
+#include "sip/sipcall.h"
 #include "vcard.h"
 
 namespace jami {
@@ -987,6 +989,13 @@ ConversationModule::loadConversations()
                 info.lastDisplayed = conv->infos()[ConversationMapKeys::LAST_DISPLAYED];
                 addConvInfo(info);
             }
+            auto commits = conv->refreshActiveCalls();
+            if (!commits.empty()) {
+                // Note: here, this means that some calls were actives while the
+                // daemon finished (can be a crash).
+                // Notify other in the conversation that the call is finished
+                pimpl_->sendMessageNotification(*conv, *commits.rbegin(), true);
+            }
             pimpl_->conversations_.emplace(repository, std::move(conv));
         } catch (const std::logic_error& e) {
             JAMI_WARN("[Account %s] Conversations not loaded : %s",
@@ -1017,7 +1026,7 @@ ConversationModule::loadConversations()
                              pimpl_->accountId_,
                              info.id);
                 emitSignal<libjami::ConversationSignal::ConversationRemoved>(pimpl_->accountId_,
-                                                                           info.id);
+                                                                             info.id);
                 itInfo = pimpl_->convInfos_.erase(itInfo);
                 continue;
             }
@@ -1120,10 +1129,10 @@ ConversationModule::onTrustRequest(const std::string& uri,
         return;
     }
     emitSignal<libjami::ConfigurationSignal::IncomingTrustRequest>(pimpl_->accountId_,
-                                                                 conversationId,
-                                                                 uri,
-                                                                 payload,
-                                                                 received);
+                                                                   conversationId,
+                                                                   uri,
+                                                                   payload,
+                                                                   received);
     ConversationRequest req;
     req.from = uri;
     req.conversationId = conversationId;
@@ -1133,8 +1142,8 @@ ConversationModule::onTrustRequest(const std::string& uri,
     auto reqMap = req.toMap();
     pimpl_->addConversationRequest(conversationId, std::move(req));
     emitSignal<libjami::ConversationSignal::ConversationRequestReceived>(pimpl_->accountId_,
-                                                                       conversationId,
-                                                                       reqMap);
+                                                                         conversationId,
+                                                                         reqMap);
 }
 
 void
@@ -1161,8 +1170,8 @@ ConversationModule::onConversationRequest(const std::string& from, const Json::V
     // the same conversation request. Will sync when the conversation will be added
 
     emitSignal<libjami::ConversationSignal::ConversationRequestReceived>(pimpl_->accountId_,
-                                                                       convId,
-                                                                       reqMap);
+                                                                         convId,
+                                                                         reqMap);
 }
 
 void
@@ -1215,7 +1224,7 @@ ConversationModule::declineConversationRequest(const std::string& conversationId
         pimpl_->saveConvRequests();
     }
     emitSignal<libjami::ConversationSignal::ConversationRequestDeclined>(pimpl_->accountId_,
-                                                                       conversationId);
+                                                                         conversationId);
     pimpl_->needsSyncingCb_({});
 }
 
@@ -1402,9 +1411,9 @@ ConversationModule::loadConversationMessages(const std::string& conversationId,
         conversation->second->loadMessages(
             [accountId = pimpl_->accountId_, conversationId, id](auto&& messages) {
                 emitSignal<libjami::ConversationSignal::ConversationLoaded>(id,
-                                                                          accountId,
-                                                                          conversationId,
-                                                                          messages);
+                                                                            accountId,
+                                                                            conversationId,
+                                                                            messages);
             },
             options);
         return id;
@@ -1429,9 +1438,9 @@ ConversationModule::loadConversationUntil(const std::string& conversationId,
         conversation->second->loadMessages(
             [accountId = pimpl_->accountId_, conversationId, id](auto&& messages) {
                 emitSignal<libjami::ConversationSignal::ConversationLoaded>(id,
-                                                                          accountId,
-                                                                          conversationId,
-                                                                          messages);
+                                                                            accountId,
+                                                                            conversationId,
+                                                                            messages);
             },
             options);
         return id;
@@ -1538,7 +1547,7 @@ ConversationModule::onSyncData(const SyncMsg& msg,
                 auto itConv = pimpl_->conversations_.find(convId);
                 if (itConv != pimpl_->conversations_.end() && !itConv->second->isRemoving()) {
                     emitSignal<libjami::ConversationSignal::ConversationRemoved>(pimpl_->accountId_,
-                                                                               convId);
+                                                                                 convId);
                     itConv->second->setRemovingFlag();
                 }
             }
@@ -1575,7 +1584,7 @@ ConversationModule::onSyncData(const SyncMsg& msg,
                       convId.c_str(),
                       deviceId.c_str());
             emitSignal<libjami::ConversationSignal::ConversationRequestDeclined>(pimpl_->accountId_,
-                                                                               convId);
+                                                                                 convId);
             continue;
         }
 
@@ -1585,8 +1594,8 @@ ConversationModule::onSyncData(const SyncMsg& msg,
                   deviceId.c_str());
 
         emitSignal<libjami::ConversationSignal::ConversationRequestReceived>(pimpl_->accountId_,
-                                                                           convId,
-                                                                           req.toMap());
+                                                                             convId,
+                                                                             req.toMap());
     }
 
     // Updates preferences for conversations
@@ -1897,8 +1906,8 @@ ConversationModule::removeContact(const std::string& uri, bool banned)
         auto it = pimpl_->conversationsRequests_.begin();
         while (it != pimpl_->conversationsRequests_.end()) {
             if (it->second.from == uri) {
-                emitSignal<libjami::ConversationSignal::ConversationRequestDeclined>(pimpl_->accountId_,
-                                                                                   it->first);
+                emitSignal<libjami::ConversationSignal::ConversationRequestDeclined>(
+                    pimpl_->accountId_, it->first);
                 update = true;
                 it = pimpl_->conversationsRequests_.erase(it);
             } else {
@@ -1989,6 +1998,245 @@ ConversationModule::initReplay(const std::string& oldConvId, const std::string&
     }
 }
 
+bool
+ConversationModule::isHosting(const std::string& conversationId, const std::string& confId) const
+{
+    std::lock_guard<std::mutex> lk(pimpl_->conversationsMtx_);
+    if (conversationId.empty()) {
+        return std::find_if(pimpl_->conversations_.cbegin(),
+                            pimpl_->conversations_.cend(),
+                            [&](const auto& conv) { return conv.second->isHosting(confId); })
+               != pimpl_->conversations_.cend();
+    } else {
+        auto conversation = pimpl_->conversations_.find(conversationId);
+        if (conversation != pimpl_->conversations_.end() && conversation->second) {
+            return conversation->second->isHosting(confId);
+        }
+    }
+    return false;
+}
+
+std::vector<std::map<std::string, std::string>>
+ConversationModule::getActiveCalls(const std::string& conversationId) const
+{
+    std::unique_lock<std::mutex> lk(pimpl_->conversationsMtx_);
+    auto conversation = pimpl_->conversations_.find(conversationId);
+    if (conversation == pimpl_->conversations_.end() || !conversation->second) {
+        JAMI_ERR("Conversation %s not found", conversationId.c_str());
+        return {};
+    }
+    return conversation->second->currentCalls();
+}
+
+void
+ConversationModule::call(const std::string& url,
+                         const std::shared_ptr<SIPCall>& call,
+                         std::function<void(const std::string&, const DeviceId&)>&& cb)
+{
+    std::string conversationId = "", confId = "", uri = "", deviceId = "";
+    if (url.find('/') == std::string::npos) {
+        conversationId = url;
+    } else {
+        auto parameters = jami::split_string(url, '/');
+        if (parameters.size() != 4) {
+            JAMI_ERR("Incorrect url %s", url.c_str());
+            return;
+        }
+        conversationId = parameters[0];
+        JAMI_ERR("@@@ %s", conversationId.c_str());
+        uri = parameters[1];
+        JAMI_ERR("@@@ %s", uri.c_str());
+        deviceId = parameters[2];
+        JAMI_ERR("@@@ %s", deviceId.c_str());
+        confId = parameters[3];
+        JAMI_ERR("@@@ %s", confId.c_str());
+    }
+
+    std::string callUri;
+    auto sendCall = [&]() {
+        call->setState(Call::ConnectionState::TRYING);
+        call->setPeerNumber(callUri);
+        call->setPeerUri("rdv:" + callUri);
+        call->addStateListener([w = pimpl_->weak(), conversationId](Call::CallState call_state,
+                                                                    Call::ConnectionState cnx_state,
+                                                                    int) {
+            if (cnx_state == Call::ConnectionState::DISCONNECTED
+                && call_state == Call::CallState::MERROR) {
+                auto shared = w.lock();
+                if (!shared)
+                    return false;
+                if (auto acc = shared->account_.lock())
+                    emitSignal<libjami::ConfigurationSignal::NeedsHost>(acc->getAccountID(),
+                                                                        conversationId);
+                return true;
+            }
+            return true;
+        });
+        cb(callUri, DeviceId(deviceId));
+    };
+
+    std::unique_lock<std::mutex> lk(pimpl_->conversationsMtx_);
+    auto conversation = pimpl_->conversations_.find(conversationId);
+    if (conversation == pimpl_->conversations_.end() || !conversation->second) {
+        JAMI_ERR("Conversation %s not found", conversationId.c_str());
+        return;
+    }
+
+    // Check if we want to join a specific conference
+    // So, if confId is specified or if there is some activeCalls
+    // or if we are the default host.
+    auto& conv = conversation->second;
+    auto activeCalls = conv->currentCalls();
+    auto infos = conv->infos();
+    auto itRdvAccount = infos.find("rdvAccount");
+    auto itRdvDevice = infos.find("rdvDevice");
+    auto sendCallRequest = false;
+    if (confId != "") {
+        sendCallRequest = true;
+        confId = confId == "0" ? Manager::instance().callFactory.getNewCallID() : confId;
+        JAMI_DBG("Calling self, join conference");
+    } else if (!activeCalls.empty()) {
+        // Else, we try to join active calls
+        sendCallRequest = true;
+        auto& ac = *activeCalls.rbegin();
+        confId = ac.at("id");
+        uri = ac.at("uri");
+        deviceId = ac.at("device");
+        JAMI_DBG("Calling last active call: %s", callUri.c_str());
+    } else if (itRdvAccount != infos.end() && itRdvDevice != infos.end()) {
+        // Else, creates "to" (accountId/deviceId/conversationId/confId) and ask remote host
+        sendCallRequest = true;
+        uri = itRdvAccount->second;
+        deviceId = itRdvDevice->second;
+        confId = call->getCallId();
+        JAMI_DBG("Remote host detected. Calling %s on device %s", uri.c_str(), deviceId.c_str());
+    }
+
+    if (sendCallRequest) {
+        callUri = fmt::format("{}/{}/{}/{}", conversationId, uri, deviceId, confId);
+        if (uri == pimpl_->username_ && deviceId == pimpl_->deviceId_) {
+            // In this case, we're probably hosting the conference.
+            call->setState(Call::ConnectionState::CONNECTED);
+            // In this case, the call is the only one in the conference
+            // and there is no peer, so media succeeded and are shown to
+            // the client.
+            call->reportMediaNegotiationStatus();
+            lk.unlock();
+            hostConference(conversationId, confId, call->getCallId());
+            return;
+        }
+        JAMI_DBG("Calling: %s", callUri.c_str());
+        sendCall();
+        return;
+    }
+
+    // Else, we are the host.
+    confId = Manager::instance().callFactory.getNewCallID();
+    call->setState(Call::ConnectionState::CONNECTED);
+    // In this case, the call is the only one in the conference
+    // and there is no peer, so media succeeded and are shown to
+    // the client.
+    call->reportMediaNegotiationStatus();
+    lk.unlock();
+    hostConference(conversationId, confId, call->getCallId());
+}
+
+void
+ConversationModule::hostConference(const std::string& conversationId,
+                                   const std::string& confId,
+                                   const std::string& callId)
+{
+    auto acc = pimpl_->account_.lock();
+    if (!acc)
+        return;
+    std::shared_ptr<Call> call;
+    call = acc->getCall(callId);
+    if (!call) {
+        JAMI_WARN("No call with id %s found", callId.c_str());
+        return;
+    }
+    auto conf = acc->getConference(confId);
+    auto createConf = !conf;
+    if (createConf) {
+        conf = std::make_shared<Conference>(acc, confId);
+        acc->attach(conf);
+    }
+    conf->addParticipant(callId);
+
+    if (createConf) {
+        emitSignal<libjami::CallSignal::ConferenceCreated>(acc->getAccountID(), confId);
+    } else {
+        conf->attachLocalParticipant();
+        conf->reportMediaNegotiationStatus();
+        emitSignal<libjami::CallSignal::ConferenceChanged>(acc->getAccountID(),
+                                                           conf->getConfId(),
+                                                           conf->getStateStr());
+        return;
+    }
+
+    std::unique_lock<std::mutex> lk(pimpl_->conversationsMtx_);
+    auto conversation = pimpl_->conversations_.find(conversationId);
+    if (conversation == pimpl_->conversations_.end() || !conversation->second) {
+        JAMI_ERR("Conversation %s not found", conversationId.c_str());
+        return;
+    }
+    auto& conv = conversation->second;
+    // Add commit to conversation
+    Json::Value value;
+    value["uri"] = pimpl_->username_;
+    value["device"] = pimpl_->deviceId_;
+    value["confId"] = confId;
+    value["type"] = "application/call-history+json";
+    conv->hostConference(std::move(value),
+                         std::move([w = pimpl_->weak(),
+                                    conversationId](bool ok, const std::string& commitId) {
+                             if (ok) {
+                                 if (auto shared = w.lock())
+                                     shared->sendMessageNotification(conversationId, commitId, true);
+                             } else {
+                                 JAMI_ERR("Failed to send message to conversation %s",
+                                          conversationId.c_str());
+                             }
+                         }));
+
+    // When conf finished = remove host & commit
+    // Master call, so when it's stopped, the conference will be stopped (as we use the hold state
+    // for detaching the call)
+    conf->onShutdown(
+        [w = pimpl_->weak(), accountUri = pimpl_->username_, confId, conversationId, call](
+            int duration) {
+            auto shared = w.lock();
+            if (shared) {
+                Json::Value value;
+                value["uri"] = accountUri;
+                value["device"] = shared->deviceId_;
+                value["confId"] = confId;
+                value["type"] = "application/call-history+json";
+                value["duration"] = std::to_string(duration);
+
+                std::unique_lock<std::mutex> lk(shared->conversationsMtx_);
+                auto conversation = shared->conversations_.find(conversationId);
+                if (conversation == shared->conversations_.end() || !conversation->second) {
+                    JAMI_ERR("Conversation %s not found", conversationId.c_str());
+                    return true;
+                }
+                auto& conv = conversation->second;
+                conv->removeActiveConference(
+                    std::move(value), [w, conversationId](bool ok, const std::string& commitId) {
+                        if (ok) {
+                            if (auto shared = w.lock()) {
+                                shared->sendMessageNotification(conversationId, commitId, true);
+                            }
+                        } else {
+                            JAMI_ERR("Failed to send message to conversation %s",
+                                     conversationId.c_str());
+                        }
+                    });
+            }
+            return true;
+        });
+}
+
 std::map<std::string, ConvInfo>
 ConversationModule::convInfos(const std::string& accountId)
 {
diff --git a/src/jamidht/conversation_module.h b/src/jamidht/conversation_module.h
index 7dee281aaf2b7785abb69cb6434e9070da5b6d97..c45f8a57c0e132fc25cca710da496fd570f434dd 100644
--- a/src/jamidht/conversation_module.h
+++ b/src/jamidht/conversation_module.h
@@ -21,6 +21,7 @@
 #pragma once
 
 #include "scheduled_executor.h"
+#include "jamidht/account_manager.h"
 #include "jamidht/conversation.h"
 #include "jamidht/conversationrepository.h"
 #include "jamidht/jami_contact.h"
@@ -30,6 +31,8 @@
 
 namespace jami {
 
+class SIPCall;
+
 struct SyncMsg
 {
     jami::DeviceSync ds;
@@ -42,7 +45,8 @@ struct SyncMsg
 };
 
 using ChannelCb = std::function<bool(const std::shared_ptr<ChannelSocket>&)>;
-using NeedSocketCb = std::function<void(const std::string&, const std::string&, ChannelCb&&, const std::string&)>;
+using NeedSocketCb
+    = std::function<void(const std::string&, const std::string&, ChannelCb&&, const std::string&)>;
 using SengMsgCb
     = std::function<uint64_t(const std::string&, std::map<std::string, std::string>, uint64_t)>;
 using NeedsSyncingCb = std::function<void(std::shared_ptr<SyncMsg>&&)>;
@@ -365,6 +369,32 @@ public:
      */
     bool removeConversation(const std::string& conversationId);
     void initReplay(const std::string& oldConvId, const std::string& newConvId);
+    /**
+     * Check if we're hosting a specific conference
+     * @param conversationId (empty to search all conv)
+     * @param confId
+     * @return true if hosting this conference
+     */
+    bool isHosting(const std::string& conversationId, const std::string& confId) const;
+    /**
+     * Return active calls
+     * @param convId        Which conversation to choose
+     * @return {{"id":id}, {"uri":uri}, {"device":device}}
+     */
+    std::vector<std::map<std::string, std::string>> getActiveCalls(
+        const std::string& conversationId) const;
+    /**
+     * Call the conversation
+     * @param url       Url to call (swarm:conversation or swarm:conv/account/device/conf to join)
+     * @param call      Call to use
+     * @param cb        Callback to pass which device to call (called in the same thread)
+     */
+    void call(const std::string& url,
+              const std::shared_ptr<SIPCall>& call,
+              std::function<void(const std::string&, const DeviceId&)>&& cb);
+    void hostConference(const std::string& conversationId,
+                        const std::string& confId,
+                        const std::string& callId);
 
     // The following methods modify what is stored on the disk
     static void saveConvInfos(const std::string& accountId,
diff --git a/src/jamidht/conversationrepository.cpp b/src/jamidht/conversationrepository.cpp
index b36f46f0d7d5b4fc71bf8705116b445576d80664..da0579f68517f09f514c1ee016f7826175cd0fbf 100644
--- a/src/jamidht/conversationrepository.cpp
+++ b/src/jamidht/conversationrepository.cpp
@@ -1749,9 +1749,9 @@ ConversationRepository::Impl::mode() const
     if (lastMsg.size() == 0) {
         if (auto shared = account_.lock()) {
             emitSignal<libjami::ConversationSignal::OnConversationError>(shared->getAccountID(),
-                                                                       id_,
-                                                                       EINVALIDMODE,
-                                                                       "No initial commit");
+                                                                         id_,
+                                                                         EINVALIDMODE,
+                                                                         "No initial commit");
         }
         throw std::logic_error("Can't retrieve first commit");
     }
@@ -1764,18 +1764,18 @@ ConversationRepository::Impl::mode() const
     if (!reader->parse(commitMsg.data(), commitMsg.data() + commitMsg.size(), &root, &err)) {
         if (auto shared = account_.lock()) {
             emitSignal<libjami::ConversationSignal::OnConversationError>(shared->getAccountID(),
-                                                                       id_,
-                                                                       EINVALIDMODE,
-                                                                       "No initial commit");
+                                                                         id_,
+                                                                         EINVALIDMODE,
+                                                                         "No initial commit");
         }
         throw std::logic_error("Can't retrieve first commit");
     }
     if (!root.isMember("mode")) {
         if (auto shared = account_.lock()) {
             emitSignal<libjami::ConversationSignal::OnConversationError>(shared->getAccountID(),
-                                                                       id_,
-                                                                       EINVALIDMODE,
-                                                                       "No mode detected");
+                                                                         id_,
+                                                                         EINVALIDMODE,
+                                                                         "No mode detected");
         }
         throw std::logic_error("No mode detected for initial commit");
     }
@@ -1797,9 +1797,9 @@ ConversationRepository::Impl::mode() const
     default:
         if (auto shared = account_.lock()) {
             emitSignal<libjami::ConversationSignal::OnConversationError>(shared->getAccountID(),
-                                                                       id_,
-                                                                       EINVALIDMODE,
-                                                                       "Incorrect mode detected");
+                                                                         id_,
+                                                                         EINVALIDMODE,
+                                                                         "Incorrect mode detected");
         }
         throw std::logic_error("Incorrect mode detected");
     }
@@ -2715,10 +2715,8 @@ ConversationRepository::Impl::validCommits(
                     validUserAtCommit.c_str(),
                     commit.commit_msg.c_str());
                 if (auto shared = account_.lock()) {
-                    emitSignal<libjami::ConversationSignal::OnConversationError>(shared->getAccountID(),
-                                                                               id_,
-                                                                               EVALIDFETCH,
-                                                                               "Malformed commit");
+                    emitSignal<libjami::ConversationSignal::OnConversationError>(
+                        shared->getAccountID(), id_, EVALIDFETCH, "Malformed commit");
                 }
                 return false;
             }
@@ -2730,10 +2728,8 @@ ConversationRepository::Impl::validCommits(
                           "that your contact is not doing unwanted stuff.",
                           validUserAtCommit.c_str());
                 if (auto shared = account_.lock()) {
-                    emitSignal<libjami::ConversationSignal::OnConversationError>(shared->getAccountID(),
-                                                                               id_,
-                                                                               EVALIDFETCH,
-                                                                               "Malformed commit");
+                    emitSignal<libjami::ConversationSignal::OnConversationError>(
+                        shared->getAccountID(), id_, EVALIDFETCH, "Malformed commit");
                 }
                 return false;
             }
@@ -3248,8 +3244,8 @@ ConversationRepository::leave()
     auto uri = details[libjami::Account::ConfProperties::USERNAME];
     auto name = details[libjami::Account::ConfProperties::DISPLAYNAME];
     if (name.empty())
-        name = account
-                   ->getVolatileAccountDetails()[libjami::Account::VolatileProperties::REGISTERED_NAME];
+        name = account->getVolatileAccountDetails()
+                   [libjami::Account::VolatileProperties::REGISTERED_NAME];
     if (name.empty())
         name = deviceId;
 
@@ -3706,35 +3702,38 @@ ConversationRepository::updateInfos(const std::map<std::string, std::string>& pr
         JAMI_ERR("Could not write data to %s", profilePath.c_str());
         return {};
     }
+
+    auto addKey = [&](auto property, auto key) {
+        auto it = infosMap.find(key);
+        if (it != infosMap.end()) {
+            file << property;
+            file << ":";
+            file << it->second;
+            file << vCard::Delimiter::END_LINE_TOKEN;
+        }
+    };
+
     file << vCard::Delimiter::BEGIN_TOKEN;
     file << vCard::Delimiter::END_LINE_TOKEN;
     file << vCard::Property::VCARD_VERSION;
     file << ":2.1";
     file << vCard::Delimiter::END_LINE_TOKEN;
-    auto titleIt = infosMap.find("title");
-    if (titleIt != infosMap.end()) {
-        file << vCard::Property::FORMATTED_NAME;
-        file << ":";
-        file << titleIt->second;
-        file << vCard::Delimiter::END_LINE_TOKEN;
-    }
-    auto descriptionIt = infosMap.find("description");
-    if (descriptionIt != infosMap.end()) {
-        file << vCard::Property::DESCRIPTION;
-        file << ":";
-        file << descriptionIt->second;
-        file << vCard::Delimiter::END_LINE_TOKEN;
-    }
+    addKey(vCard::Property::FORMATTED_NAME, vCard::Value::TITLE);
+    addKey(vCard::Property::DESCRIPTION, vCard::Value::DESCRIPTION);
     file << vCard::Property::PHOTO;
     file << vCard::Delimiter::SEPARATOR_TOKEN;
     file << vCard::Property::BASE64;
-    auto avatarIt = infosMap.find("avatar");
+    auto avatarIt = infosMap.find(vCard::Value::AVATAR);
     if (avatarIt != infosMap.end()) {
         // TODO type=png? store another way?
         file << ":";
         file << avatarIt->second;
     }
     file << vCard::Delimiter::END_LINE_TOKEN;
+    addKey(vCard::Property::RDV_ACCOUNT, vCard::Value::RDV_ACCOUNT);
+    file << vCard::Delimiter::END_LINE_TOKEN;
+    addKey(vCard::Property::RDV_DEVICE, vCard::Value::RDV_DEVICE);
+    file << vCard::Delimiter::END_LINE_TOKEN;
     file << vCard::Delimiter::END_TOKEN;
     file.close();
 
@@ -3780,6 +3779,10 @@ ConversationRepository::infosFromVCard(std::map<std::string, std::string>&& deta
             result["description"] = std::move(v);
         } else if (k.find(vCard::Property::PHOTO) == 0) {
             result["avatar"] = std::move(v);
+        } else if (k.find(vCard::Property::RDV_ACCOUNT) == 0) {
+            result["rdvAccount"] = std::move(v);
+        } else if (k.find(vCard::Property::RDV_DEVICE) == 0) {
+            result["rdvDevice"] = std::move(v);
         }
     }
     return result;
diff --git a/src/jamidht/jamiaccount.cpp b/src/jamidht/jamiaccount.cpp
index f617bd91266cf7e1fd8252d4ac62fefc4b3505e4..8ef6922fdbf08a01ac7095005cdfbcce643ea666 100644
--- a/src/jamidht/jamiaccount.cpp
+++ b/src/jamidht/jamiaccount.cpp
@@ -365,7 +365,7 @@ JamiAccount::newIncomingCall(const std::string& from,
                     auto call = Manager::instance().callFactory.newSipCall(shared(),
                                                                            Call::CallType::INCOMING,
                                                                            mediaList);
-                    call->setPeerUri(RING_URI_PREFIX + from);
+                    call->setPeerUri(JAMI_URI_PREFIX + from);
                     call->setPeerNumber(from);
 
                     call->setSipTransport(sipTransp, getContactHeader(sipTransp));
@@ -383,9 +383,6 @@ JamiAccount::newIncomingCall(const std::string& from,
 std::shared_ptr<Call>
 JamiAccount::newOutgoingCall(std::string_view toUrl, const std::vector<libjami::MediaMap>& mediaList)
 {
-    auto suffix = stripPrefix(toUrl);
-    JAMI_DBG() << *this << "Calling peer " << suffix;
-
     auto& manager = Manager::instance();
     std::shared_ptr<SIPCall> call;
 
@@ -403,7 +400,8 @@ JamiAccount::newOutgoingCall(std::string_view toUrl, const std::vector<libjami::
     if (not call)
         return {};
 
-    getIceOptions([call, w = weak(), uri = std::string(toUrl)](auto&& opts) {
+    auto uri = Uri(toUrl);
+    getIceOptions([call, w = weak(), uri = std::move(uri)](auto&& opts) {
         if (call->isIceEnabled()) {
             if (not call->createIceMediaTransport(false)
                 or not call->initIceMediaTransport(true, std::forward<IceTransportOptions>(opts))) {
@@ -413,23 +411,28 @@ JamiAccount::newOutgoingCall(std::string_view toUrl, const std::vector<libjami::
         auto shared = w.lock();
         if (!shared)
             return;
-        shared->newOutgoingCallHelper(call, uri);
+        JAMI_DBG() << "New outgoing call with " << uri.toString();
+        call->setPeerNumber(uri.authority());
+        call->setPeerUri(uri.toString());
+
+        if (uri.scheme() == Uri::Scheme::SWARM || uri.scheme() == Uri::Scheme::RENDEZVOUS)
+            shared->newSwarmOutgoingCallHelper(call, uri);
+        else
+            shared->newOutgoingCallHelper(call, uri);
     });
 
     return call;
 }
 
 void
-JamiAccount::newOutgoingCallHelper(const std::shared_ptr<SIPCall>& call, std::string_view toUri)
+JamiAccount::newOutgoingCallHelper(const std::shared_ptr<SIPCall>& call, const Uri& uri)
 {
-    auto suffix = stripPrefix(toUri);
-    JAMI_DBG() << *this << "Calling DHT peer " << suffix;
-
+    JAMI_DBG() << this << "Calling peer " << uri.authority();
     try {
-        const std::string uri {parseJamiUri(suffix)};
-        startOutgoingCall(call, uri);
+        startOutgoingCall(call, uri.authority());
     } catch (...) {
 #if HAVE_RINGNS
+        auto suffix = stripPrefix(uri.toString());
         NameDirectory::lookupUri(suffix,
                                  config().nameServer,
                                  [wthis_ = weak(), call](const std::string& result,
@@ -443,8 +446,7 @@ JamiAccount::newOutgoingCallHelper(const std::shared_ptr<SIPCall>& call, std::st
                                          }
                                          if (auto sthis = wthis_.lock()) {
                                              try {
-                                                 const std::string toUri {parseJamiUri(result)};
-                                                 sthis->startOutgoingCall(call, toUri);
+                                                 sthis->startOutgoingCall(call, result);
                                              } catch (...) {
                                                  call->onFailure(ENOENT);
                                              }
@@ -459,6 +461,103 @@ JamiAccount::newOutgoingCallHelper(const std::shared_ptr<SIPCall>& call, std::st
     }
 }
 
+void
+JamiAccount::newSwarmOutgoingCallHelper(const std::shared_ptr<SIPCall>& call, const Uri& uri)
+{
+    JAMI_DBG("[Account %s] Calling conversation %s",
+             getAccountID().c_str(),
+             uri.authority().c_str());
+    convModule()->call(
+        uri.authority(),
+        call,
+        std::move([this, uri, call](const std::string& accountUri, const DeviceId& deviceId) {
+            std::unique_lock<std::mutex> lkSipConn(sipConnsMtx_);
+            for (auto& [key, value] : sipConns_) {
+                if (key.first != accountUri || key.second != deviceId)
+                    continue;
+                if (value.empty())
+                    continue;
+                auto& sipConn = value.back();
+
+                if (!sipConn.channel) {
+                    JAMI_WARN(
+                        "A SIP transport exists without Channel, this is a bug. Please report");
+                    continue;
+                }
+
+                auto transport = sipConn.transport;
+                if (!transport or !sipConn.channel)
+                    continue;
+                call->setState(Call::ConnectionState::PROGRESSING);
+
+                auto remoted_address = sipConn.channel->getRemoteAddress();
+                try {
+                    onConnectedOutgoingCall(call, uri.authority(), remoted_address);
+                    return;
+                } catch (const VoipLinkException&) {
+                    // In this case, the main scenario is that SIPStartCall failed because
+                    // the ICE is dead and the TLS session didn't send any packet on that dead
+                    // link (connectivity change, killed by the os, etc)
+                    // Here, we don't need to do anything, the TLS will fail and will delete
+                    // the cached transport
+                    continue;
+                }
+            }
+            lkSipConn.unlock();
+            {
+                std::lock_guard<std::mutex> lkP(pendingCallsMutex_);
+                pendingCalls_[deviceId].emplace_back(call);
+            }
+
+            // Else, ask for a channel (for future calls/text messages)
+            auto type = call->hasVideo() ? "videoCall" : "audioCall";
+            JAMI_WARN("[call %s] No channeled socket with this peer. Send request",
+                      call->getCallId().c_str());
+            requestSIPConnection(accountUri, deviceId, type, true, call);
+        }));
+}
+
+void
+JamiAccount::handleIncomingConversationCall(const std::string& callId,
+                                            const std::string& destination)
+{
+    auto split = jami::split_string(destination, '/');
+    if (split.size() != 4)
+        return;
+    auto conversationId = std::string(split[0]);
+    auto accountUri = std::string(split[1]);
+    auto deviceId = std::string(split[2]);
+    auto confId = std::string(split[3]);
+
+    if (getUsername() != accountUri || currentDeviceId() != deviceId)
+        return;
+
+    auto call = getCall(callId);
+    if (!call) {
+        JAMI_ERR("Call %s not found", callId.c_str());
+        return;
+    }
+    Manager::instance().answerCall(*call);
+
+    if (!convModule()->isHosting(conversationId, confId)) {
+        // Create conference and host it.
+        convModule()->hostConference(conversationId, confId, callId);
+        if (auto conf = getConference(confId))
+            conf->detachLocalParticipant();
+    } else {
+        auto conf = getConference(confId);
+        if (!conf) {
+            JAMI_ERR("Conference %s not found", confId.c_str());
+            return;
+        }
+
+        conf->addParticipant(callId);
+        emitSignal<libjami::CallSignal::ConferenceChanged>(getAccountID(),
+                                                           conf->getConfId(),
+                                                           conf->getStateStr());
+    }
+}
+
 std::shared_ptr<SIPCall>
 JamiAccount::createSubCall(const std::shared_ptr<SIPCall>& mainCall)
 {
@@ -473,11 +572,10 @@ JamiAccount::startOutgoingCall(const std::shared_ptr<SIPCall>& call, const std::
         call->onFailure(ENETDOWN);
         return;
     }
+
     // TODO: for now, we automatically trust all explicitly called peers
     setCertificateStatus(toUri, tls::TrustStore::PermissionStatus::ALLOWED);
 
-    call->setPeerNumber(toUri + "@ring.dht");
-    call->setPeerUri(JAMI_URI_PREFIX + toUri);
     call->setState(Call::ConnectionState::TRYING);
     std::weak_ptr<SIPCall> wCall = call;
 
@@ -488,7 +586,6 @@ JamiAccount::startOutgoingCall(const std::shared_ptr<SIPCall>& call, const std::
                                        if (response == NameDirectory::Response::found)
                                            if (auto call = wCall.lock()) {
                                                call->setPeerRegisteredName(result);
-                                               call->setPeerUri(JAMI_URI_PREFIX + result);
                                            }
                                    });
 #endif
@@ -522,7 +619,7 @@ JamiAccount::startOutgoingCall(const std::shared_ptr<SIPCall>& call, const std::
                 return;
 
             auto dev_call = createSubCall(call);
-            dev_call->setIPToIP(true);
+            dev_call->setPeerNumber(call->getPeerNumber());
             dev_call->setState(Call::ConnectionState::TRYING);
             call->addStateListener(
                 [w = weak(), deviceId](Call::CallState, Call::ConnectionState state, int) {
@@ -572,6 +669,7 @@ JamiAccount::startOutgoingCall(const std::shared_ptr<SIPCall>& call, const std::
                   call->getCallId().c_str());
 
         auto dev_call = createSubCall(call);
+        dev_call->setPeerNumber(call->getPeerNumber());
         dev_call->setSipTransport(transport, getContactHeader(transport));
         call->addSubCall(*dev_call);
         dev_call->setIceMedia(call->getIceMedia());
@@ -685,9 +783,6 @@ JamiAccount::onConnectedOutgoingCall(const std::shared_ptr<SIPCall>& call,
         return;
     }
 
-    call->setIPToIP(true);
-    call->setPeerNumber(to_id);
-
     // Note: pj_ice_strans_create can call onComplete in the same thread
     // This means that iceMutex_ in IceTransport can be locked when onInitDone is called
     // So, we need to run the call creation in the main thread
diff --git a/src/jamidht/jamiaccount.h b/src/jamidht/jamiaccount.h
index b6be21108592faf3a24d3db2b07406753aa9bb0a..f24e17e78f9574557223f8a893eb8cbcc91ec601 100644
--- a/src/jamidht/jamiaccount.h
+++ b/src/jamidht/jamiaccount.h
@@ -126,7 +126,8 @@ public:
 
     const std::string& getPath() const { return idPath_; }
 
-    const JamiAccountConfig& config() const {
+    const JamiAccountConfig& config() const
+    {
         return *static_cast<const JamiAccountConfig*>(&Account::config());
     }
 
@@ -146,7 +147,8 @@ public:
      */
     virtual std::map<std::string, std::string> getVolatileAccountDetails() const override;
 
-    std::unique_ptr<AccountConfig> buildConfig() const override {
+    std::unique_ptr<AccountConfig> buildConfig() const override
+    {
         return std::make_unique<JamiAccountConfig>(getAccountID(), idPath_);
     }
 
@@ -234,6 +236,10 @@ public:
 
     /**
      * Create outgoing SIPCall.
+     * @note Accepts several urls:
+     *          + jami:uri for calling someone
+     *          + swarm:id for calling a group (will host or join if an active call is detected)
+     *          + rdv:id/uri/device/confId to join a specific conference hosted on (uri, device)
      * @param[in] toUrl The address to call
      * @param[in] mediaList list of medias
      * @return A shared pointer on the created call.
@@ -420,10 +426,10 @@ public:
 
     void saveConfig() const override;
 
-    inline void editConfig(std::function<void(JamiAccountConfig& conf)>&& edit) {
-        Account::editConfig([&](AccountConfig& conf) {
-            edit(*static_cast<JamiAccountConfig*>(&conf));
-        });
+    inline void editConfig(std::function<void(JamiAccountConfig& conf)>&& edit)
+    {
+        Account::editConfig(
+            [&](AccountConfig& conf) { edit(*static_cast<JamiAccountConfig*>(&conf)); });
     }
 
     /**
@@ -521,8 +527,8 @@ public:
 
     // non-swarm version
     libjami::DataTransferId sendFile(const std::string& peer,
-                                   const std::string& path,
-                                   const InternalCompletionCb& icb = {});
+                                     const std::string& path,
+                                     const InternalCompletionCb& icb = {});
 
     void transferFile(const std::string& conversationId,
                       const std::string& path,
@@ -605,6 +611,13 @@ public:
 
     bool isValidAccountDevice(const dht::crypto::Certificate& cert) const;
 
+    /**
+     * Join incoming call to hosted conference
+     * @param callId        The call to join
+     * @param destination   conversation/uri/device/confId to join
+     */
+    void handleIncomingConversationCall(const std::string& callId, const std::string& destination);
+
 private:
     NON_COPYABLE(JamiAccount);
 
@@ -706,11 +719,10 @@ private:
 
     template<class... Args>
     std::shared_ptr<IceTransport> createIceTransport(const Args&... args);
-    void newOutgoingCallHelper(const std::shared_ptr<SIPCall>& call, std::string_view toUri);
+    void newOutgoingCallHelper(const std::shared_ptr<SIPCall>& call, const Uri& uri);
+    void newSwarmOutgoingCallHelper(const std::shared_ptr<SIPCall>& call, const Uri& uri);
     std::shared_ptr<SIPCall> createSubCall(const std::shared_ptr<SIPCall>& mainCall);
 
-    void updateContactHeader();
-
 #if HAVE_RINGNS
     std::string registeredName_;
 #endif
diff --git a/src/manager.cpp b/src/manager.cpp
index b82e9f51326f620239b46ef6d924fec065fcd75c..eaad12af7818ed9436bdb15ea8240b9fbd41aa16 100644
--- a/src/manager.cpp
+++ b/src/manager.cpp
@@ -561,9 +561,16 @@ Manager::ManagerPimpl::processRemainingParticipants(Conference& conf)
                 JAMI_ERR("No account detected");
                 return;
             }
+
+            // Stay in a conference if 1 participants for swarm and rendezvous
             if (account->isRendezVous())
                 return;
 
+            if (auto acc = std::dynamic_pointer_cast<JamiAccount>(account))
+                if (acc->convModule()->isHosting("", conf.getConfId()))
+                    return;
+
+            // Else go in 1:1
             if (current_callId != conf.getConfId())
                 base_.onHoldCall(account->getAccountID(), call->getCallId());
             else
@@ -1083,13 +1090,17 @@ Manager::answerCall(Call& call, const std::vector<libjami::MediaMap>& mediaList)
 
 // THREAD=Main
 bool
-Manager::hangupCall(const std::string&, const std::string& callId)
+Manager::hangupCall(const std::string& accountId, const std::string& callId)
 {
+    auto account = getAccount(accountId);
+    if (not account)
+        return false;
+    // store the current call id
     stopTone();
     pimpl_->removeWaitingCall(callId);
 
     /* We often get here when the call was hungup before being created */
-    auto call = getCallFromCallID(callId);
+    auto call = account->getCall(callId);
     if (not call) {
         JAMI_WARN("Could not hang up non-existant call %s", callId.c_str());
         return false;
@@ -1261,8 +1272,8 @@ Manager::holdConference(const std::string& accountId, const std::string& confId)
         if (auto conf = account->getConference(confId)) {
             conf->detachLocalParticipant();
             emitSignal<libjami::CallSignal::ConferenceChanged>(accountId,
-                                                             conf->getConfId(),
-                                                             conf->getStateStr());
+                                                               conf->getConfId(),
+                                                               conf->getStateStr());
             return true;
         }
     }
@@ -1285,8 +1296,8 @@ Manager::unHoldConference(const std::string& accountId, const std::string& confI
                 pimpl_->switchCall(confId);
                 conf->setState(Conference::State::ACTIVE_ATTACHED);
                 emitSignal<libjami::CallSignal::ConferenceChanged>(accountId,
-                                                                 conf->getConfId(),
-                                                                 conf->getStateStr());
+                                                                   conf->getConfId(),
+                                                                   conf->getStateStr());
                 return true;
             } else if (conf->getState() == Conference::State::ACTIVE_DETACHED) {
                 pimpl_->addMainParticipant(*conf);
@@ -1352,8 +1363,8 @@ Manager::ManagerPimpl::addMainParticipant(Conference& conf)
 {
     conf.attachLocalParticipant();
     emitSignal<libjami::CallSignal::ConferenceChanged>(conf.getAccountId(),
-                                                     conf.getConfId(),
-                                                     conf.getStateStr());
+                                                       conf.getConfId(),
+                                                       conf.getStateStr());
     switchCall(conf.getConfId());
 }
 
@@ -1446,8 +1457,8 @@ Manager::joinParticipant(const std::string& accountId,
         conf->detachLocalParticipant();
     }
     emitSignal<libjami::CallSignal::ConferenceChanged>(account->getAccountID(),
-                                                     conf->getConfId(),
-                                                     conf->getStateStr());
+                                                       conf->getConfId(),
+                                                       conf->getStateStr());
 
     return true;
 }
@@ -1503,8 +1514,8 @@ Manager::detachLocalParticipant(const std::shared_ptr<Conference>& conf)
     JAMI_INFO("Detach local participant from conference %s", conf->getConfId().c_str());
     conf->detachLocalParticipant();
     emitSignal<libjami::CallSignal::ConferenceChanged>(conf->getAccountId(),
-                                                     conf->getConfId(),
-                                                     conf->getStateStr());
+                                                       conf->getConfId(),
+                                                       conf->getStateStr());
     pimpl_->unsetCurrentCall();
     return true;
 }
@@ -1544,8 +1555,8 @@ Manager::removeParticipant(Call& call)
     removeAudio(call);
 
     emitSignal<libjami::CallSignal::ConferenceChanged>(conf->getAccountId(),
-                                                     conf->getConfId(),
-                                                     conf->getStateStr());
+                                                       conf->getConfId(),
+                                                       conf->getStateStr());
 
     pimpl_->processRemainingParticipants(*conf);
 }
@@ -1822,24 +1833,6 @@ Manager::incomingCall(const std::string& accountId, Call& call)
         return;
     }
 
-    // Report incoming call using "CallSignal::IncomingCallWithMedia" signal.
-    auto const& mediaList = MediaAttribute::mediaAttributesToMediaMaps(call.getMediaAttributeList());
-
-    if (mediaList.empty()) {
-        JAMI_WARN("Incoming call %s has an empty media list", call.getCallId().c_str());
-    }
-
-    JAMI_DEBUG("Incoming call {:s} on account {:s} with {:d} media",
-              call.getCallId(),
-              accountId,
-              mediaList.size());
-
-    // Report the call using new API.
-    emitSignal<libjami::CallSignal::IncomingCallWithMedia>(accountId,
-                                                         call.getCallId(),
-                                                         call.getPeerDisplayName() + " " + from,
-                                                         mediaList);
-
     // Process the call.
     pimpl_->processIncomingCall(accountId, call);
 }
@@ -1872,9 +1865,9 @@ Manager::incomingMessage(const std::string& accountId,
 
                 // in case of a conference we must notify client using conference id
                 emitSignal<libjami::CallSignal::IncomingMessage>(accountId,
-                                                               conf->getConfId(),
-                                                               from,
-                                                               messages);
+                                                                 conf->getConfId(),
+                                                                 from,
+                                                                 messages);
             } else {
                 JAMI_ERR("no conference associated to ID %s", callId.c_str());
             }
@@ -2475,6 +2468,32 @@ Manager::ManagerPimpl::processIncomingCall(const std::string& accountId, Call& i
         return;
     }
 
+    auto username = incomCall.toUsername();
+    if (username.find('/') != std::string::npos) {
+        // Avoid to do heavy stuff in SIPVoIPLink's transaction_request_cb
+        dht::ThreadPool::io().run([this, account, incomCallId, username]() {
+            if (auto jamiAccount = std::dynamic_pointer_cast<JamiAccount>(account))
+                jamiAccount->handleIncomingConversationCall(incomCallId, username);
+        });
+        return;
+    }
+
+    auto const& mediaList = MediaAttribute::mediaAttributesToMediaMaps(
+        incomCall.getMediaAttributeList());
+
+    if (mediaList.empty())
+        JAMI_WARN("Incoming call %s has an empty media list", incomCallId.c_str());
+
+    JAMI_INFO("Incoming call %s on account %s with %lu media",
+              incomCallId.c_str(),
+              accountId.c_str(),
+              mediaList.size());
+
+    emitSignal<libjami::CallSignal::IncomingCallWithMedia>(accountId,
+                                                           incomCallId,
+                                                           incomCall.getPeerNumber(),
+                                                           mediaList);
+
     if (not base_.hasCurrentCall()) {
         incomCall.setState(Call::ConnectionState::RINGING);
 #if !defined(RING_UWP) && !(defined(TARGET_OS_IOS) && TARGET_OS_IOS)
@@ -2509,17 +2528,17 @@ Manager::ManagerPimpl::processIncomingCall(const std::string& accountId, Call& i
             }
 
             // First call
-            auto conf = std::make_shared<Conference>(account, false);
+            auto conf = std::make_shared<Conference>(account, "", false);
             account->attach(conf);
             emitSignal<libjami::CallSignal::ConferenceCreated>(account->getAccountID(),
-                                                             conf->getConfId());
+                                                               conf->getConfId());
 
             // Bind calls according to their state
             bindCallToConference(*incomCall, *conf);
             conf->detachLocalParticipant();
             emitSignal<libjami::CallSignal::ConferenceChanged>(account->getAccountID(),
-                                                             conf->getConfId(),
-                                                             conf->getStateStr());
+                                                               conf->getConfId(),
+                                                               conf->getStateStr());
         });
     } else if (autoAnswer_ || account->isAutoAnswerEnabled()) {
         dht::ThreadPool::io().run(
@@ -2968,8 +2987,8 @@ Manager::setAccountActive(const std::string& accountID, bool active, bool shutdo
             }
         }
     }
-    emitSignal<libjami::ConfigurationSignal::VolatileDetailsChanged>(accountID,
-                                                                   acc->getVolatileAccountDetails());
+    emitSignal<libjami::ConfigurationSignal::VolatileDetailsChanged>(
+        accountID, acc->getVolatileAccountDetails());
 }
 
 std::shared_ptr<AudioLayer>
@@ -3173,9 +3192,8 @@ void
 Manager::enableLocalModerators(const std::string& accountID, bool isModEnabled)
 {
     if (auto acc = getAccount(accountID))
-        acc->editConfig([&](AccountConfig& config){
-            config.localModeratorsEnabled = isModEnabled;
-        });
+        acc->editConfig(
+            [&](AccountConfig& config) { config.localModeratorsEnabled = isModEnabled; });
 }
 
 bool
@@ -3193,9 +3211,7 @@ void
 Manager::setAllModerators(const std::string& accountID, bool allModerators)
 {
     if (auto acc = getAccount(accountID))
-        acc->editConfig([&](AccountConfig& config){
-            config.allModeratorsEnabled = allModerators;
-        });
+        acc->editConfig([&](AccountConfig& config) { config.allModeratorsEnabled = allModerators; });
 }
 
 bool
diff --git a/src/sip/sipaccountbase.cpp b/src/sip/sipaccountbase.cpp
index 57e29c959de0f818f573cdf0fa5595636b719c59..32c9aa56769d997d31f5569d78ee2861ad9b664a 100644
--- a/src/sip/sipaccountbase.cpp
+++ b/src/sip/sipaccountbase.cpp
@@ -93,8 +93,11 @@ SIPAccountBase::CreateClientDialogAndInvite(const pj_str_t* from,
         JAMI_DBG("No target provided, using 'to' as target");
     }
 
-    if (pjsip_dlg_create_uac(pjsip_ua_instance(), from, contact, to, target, dlg) != PJ_SUCCESS) {
-        JAMI_ERR("Unable to create SIP dialogs for user agent client when calling %s", to->ptr);
+    auto status = pjsip_dlg_create_uac(pjsip_ua_instance(), from, contact, to, target, dlg);
+    if (status != PJ_SUCCESS) {
+        JAMI_ERR("Unable to create SIP dialogs for user agent client when calling %s %d",
+                 to->ptr,
+                 status);
         return false;
     }
 
@@ -236,8 +239,8 @@ SIPAccountBase::getIceOptions() const noexcept
     IceTransportOptions opts;
     opts.upnpEnable = getUPnPActive();
 
-    //if (config().stunEnabled)
-    //    opts.stunServers.emplace_back(StunServerInfo().setUri(stunServer_));
+    // if (config().stunEnabled)
+    //     opts.stunServers.emplace_back(StunServerInfo().setUri(stunServer_));
     if (config().turnEnabled) {
         auto cached = false;
         std::lock_guard<std::mutex> lk(cachedTurnMutex_);
diff --git a/src/sip/sipcall.cpp b/src/sip/sipcall.cpp
index 85fb222a8fb0861d4c32974aa041aa65cd4f3eed..04a2e4ac74dedc7ae850fe5e152306ece18b0d6a 100644
--- a/src/sip/sipcall.cpp
+++ b/src/sip/sipcall.cpp
@@ -2589,9 +2589,8 @@ SIPCall::getMediaAttributeList() const
 {
     std::vector<MediaAttribute> mediaList;
     mediaList.reserve(rtpStreams_.size());
-    for (auto const& stream : rtpStreams_) {
+    for (auto const& stream : rtpStreams_)
         mediaList.emplace_back(*stream.mediaAttribute_);
-    }
     return mediaList;
 }
 
diff --git a/src/sip/sipcall.h b/src/sip/sipcall.h
index 0b9a6d8288aa52e5b8f404dcf2764dfb7ed60c9c..444212f8f22f1990c353e33e40fb1d63730fdd28 100644
--- a/src/sip/sipcall.h
+++ b/src/sip/sipcall.h
@@ -323,6 +323,10 @@ public:
     {
         return std::weak_ptr<SIPCall>(shared());
     }
+    /**
+     * Announce to the client that medias are successfully negotiated
+     */
+    void reportMediaNegotiationStatus();
 
 private:
     void generateMediaPorts();
@@ -391,7 +395,6 @@ private:
     void setupNegotiatedMedia();
     void startAllMedia();
     void stopAllMedia();
-    void reportMediaNegotiationStatus();
     void updateRemoteMedia();
 
     /**
diff --git a/src/sip/sipvoiplink.cpp b/src/sip/sipvoiplink.cpp
index 4afe4dc217a8383240983aa6fe836943518ee219..ccc3c7c8373499ad77c75fbaee5cda8ed5dbba3f 100644
--- a/src/sip/sipvoiplink.cpp
+++ b/src/sip/sipvoiplink.cpp
@@ -318,9 +318,9 @@ transaction_request_cb(pjsip_rx_data* rdata)
                     // urgent messages are optional
                     if (ret >= 2)
                         emitSignal<libjami::CallSignal::VoiceMailNotify>(account->getAccountID(),
-                                                                       newCount,
-                                                                       oldCount,
-                                                                       urgentCount);
+                                                                         newCount,
+                                                                         oldCount,
+                                                                         urgentCount);
                 }
             }
         } else if (request.find(sip_utils::SIP_METHODS::MESSAGE) != std::string_view::npos) {
@@ -393,7 +393,6 @@ transaction_request_cb(pjsip_rx_data* rdata)
     unsigned options = 0;
 
     if (pjsip_inv_verify_request(rdata, &options, NULL, NULL, endpt_, NULL) != PJ_SUCCESS) {
-
         JAMI_ERR("Couldn't verify INVITE request in secure dialog.");
         try_respond_stateless(endpt_, rdata, PJSIP_SC_METHOD_NOT_ALLOWED, NULL, NULL, NULL);
         return PJ_FALSE;
@@ -418,6 +417,8 @@ transaction_request_cb(pjsip_rx_data* rdata)
     }
 
     call->setPeerUaVersion(sip_utils::getPeerUserAgent(rdata));
+    // The username can be used to join specific calls in conversations
+    call->toUsername(std::string(toUsername));
 
     // FIXME : for now, use the same address family as the SIP transport
     auto family = pjsip_transport_type_get_af(
diff --git a/src/uri.cpp b/src/uri.cpp
index c851788af7d4b4824174744545b7679d2ef431d9..398bb00221955d12ad6658c657c373588efdebf0 100644
--- a/src/uri.cpp
+++ b/src/uri.cpp
@@ -39,6 +39,8 @@ Uri::Uri(const std::string_view& uri)
             scheme_ = Uri::Scheme::DATA_TRANSFER;
         else if (scheme_str == "git")
             scheme_ = Uri::Scheme::GIT;
+        else if (scheme_str == "rdv")
+            scheme_ = Uri::Scheme::RENDEZVOUS;
         else if (scheme_str == "sync")
             scheme_ = Uri::Scheme::SYNC;
         else
@@ -75,6 +77,8 @@ Uri::schemeToString() const
         return "sip";
     case Uri::Scheme::SWARM:
         return "swarm";
+    case Uri::Scheme::RENDEZVOUS:
+        return "rdv";
     case Uri::Scheme::GIT:
         return "git";
     case Uri::Scheme::SYNC:
diff --git a/src/uri.h b/src/uri.h
index 5cafcf21e29ddaacd759e696f85645c0a9378fbe..d4d42f00525fa4eb54700c35f8bd6c9da1161996 100644
--- a/src/uri.h
+++ b/src/uri.h
@@ -31,6 +31,7 @@ public:
         JAMI,          // Start with "jami:" and 45 ASCII chars OR 40 ASCII chars
         SIP,           // Start with "sip:"
         SWARM,         // Start with "swarm:" and 40 ASCII chars
+        RENDEZVOUS,    // Start wutg "rdv" and used for call in swarms
         GIT,           // Start with "git:"
         DATA_TRANSFER, // Start with "data-transfer://"
         SYNC,          // Start with "sync:"
diff --git a/src/vcard.h b/src/vcard.h
index 4a91a22111b79d8f408339d68a63dc45b47d6298..09d746c36db36de59438b1097e057831165aa5c0 100644
--- a/src/vcard.h
+++ b/src/vcard.h
@@ -64,14 +64,23 @@ struct Property
     constexpr static const char* TELEPHONE = "TEL";
     constexpr static const char* TIME_ZONE = "TZ";
     constexpr static const char* TITLE = "TITLE";
+    constexpr static const char* RDV_ACCOUNT = "RDV_ACCOUNT";
+    constexpr static const char* RDV_DEVICE = "RDV_DEVICE";
     constexpr static const char* URL = "URL";
     constexpr static const char* BASE64 = "ENCODING=BASE64";
     constexpr static const char* TYPE_PNG = "TYPE=PNG";
     constexpr static const char* TYPE_JPEG = "TYPE=JPEG";
     constexpr static const char* PHOTO_PNG = "PHOTO;ENCODING=BASE64;TYPE=PNG";
     constexpr static const char* PHOTO_JPEG = "PHOTO;ENCODING=BASE64;TYPE=JPEG";
+};
 
-    constexpr static const char* X_RINGACCOUNT = "X-RINGACCOUNTID";
+struct Value
+{
+    constexpr static const char* TITLE = "title";
+    constexpr static const char* DESCRIPTION = "description";
+    constexpr static const char* AVATAR = "avatar";
+    constexpr static const char* RDV_ACCOUNT = "rdvAccount";
+    constexpr static const char* RDV_DEVICE = "rdvDevice";
 };
 
 namespace utils {
diff --git a/test/unitTest/Makefile.am b/test/unitTest/Makefile.am
index f2b0fd99482716f308cc8df77b4b1fb248132945..98ed8813568e073f42500f55408ef7d727d4e943 100644
--- a/test/unitTest/Makefile.am
+++ b/test/unitTest/Makefile.am
@@ -168,6 +168,12 @@ ut_conversationRepository_SOURCES = conversationRepository/conversationRepositor
 check_PROGRAMS += ut_conversation
 ut_conversation_SOURCES = conversation/conversationcommon.cpp conversation/conversation.cpp common.cpp
 
+#
+# conversation_call
+#
+check_PROGRAMS += ut_conversation_call
+ut_conversation_call_SOURCES = conversation/conversationcommon.cpp conversation/call.cpp common.cpp
+
 #
 # media_negotiation
 #
diff --git a/test/unitTest/call/conference.cpp b/test/unitTest/call/conference.cpp
index c28f03ad3203b3a8535b61798e1547f3540ef12b..41b500b80bd32c6f13b9c15a7629a230fef05481 100644
--- a/test/unitTest/call/conference.cpp
+++ b/test/unitTest/call/conference.cpp
@@ -74,7 +74,8 @@ public:
     ConferenceTest()
     {
         // Init daemon
-        libjami::init(libjami::InitFlag(libjami::LIBJAMI_FLAG_DEBUG | libjami::LIBJAMI_FLAG_CONSOLE_LOG));
+        libjami::init(
+            libjami::InitFlag(libjami::LIBJAMI_FLAG_DEBUG | libjami::LIBJAMI_FLAG_CONSOLE_LOG));
         if (not Manager::instance().initialized)
             CPPUNIT_ASSERT(libjami::start("jami-sample.yml"));
     }
@@ -104,6 +105,7 @@ private:
     void testPropagateRecording();
     void testBrokenParticipantAudioAndVideo();
     void testBrokenParticipantAudioOnly();
+    void testRemoveConferenceInOneOne();
 
     CPPUNIT_TEST_SUITE(ConferenceTest);
     CPPUNIT_TEST(testGetConference);
@@ -126,6 +128,7 @@ private:
     CPPUNIT_TEST(testPropagateRecording);
     CPPUNIT_TEST(testBrokenParticipantAudioAndVideo);
     CPPUNIT_TEST(testBrokenParticipantAudioOnly);
+    CPPUNIT_TEST(testRemoveConferenceInOneOne);
     CPPUNIT_TEST_SUITE_END();
 
     // Common parts
@@ -204,11 +207,11 @@ ConferenceTest::registerSignalHandlers()
             }
             cv.notify_one();
         }));
-    confHandlers.insert(
-        libjami::exportable_callback<libjami::CallSignal::StateChange>([=](const std::string& accountId,
-                                                                       const std::string& callId,
-                                                                       const std::string& state,
-                                                                       signed) {
+    confHandlers.insert(libjami::exportable_callback<libjami::CallSignal::StateChange>(
+        [=](const std::string& accountId,
+            const std::string& callId,
+            const std::string& state,
+            signed) {
             if (accountId == aliceId) {
                 auto details = libjami::getCallDetails(aliceId, callId);
                 if (details["PEER_NUMBER"].find(bobUri) != std::string::npos)
@@ -469,16 +472,21 @@ ConferenceTest::testCreateParticipantsSinks()
     auto expectedNumberOfParticipants = 3;
 
     // Check participants number
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]{ return pInfos_.size() == expectedNumberOfParticipants; }));
+    CPPUNIT_ASSERT(
+        cv.wait_for(lk, 30s, [&] { return pInfos_.size() == expectedNumberOfParticipants; }));
 
     if (not jami::getVideoDeviceMonitor().getDeviceList().empty()) {
         JAMI_INFO() << "Check sinks if video device available.";
         for (auto& info : pInfos_) {
             auto uri = string_remove_suffix(info["uri"], '@');
             if (uri == bobUri) {
-                CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&]{ return Manager::instance().getSinkClient(info["sinkId"]) != nullptr; }));
+                CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&] {
+                    return Manager::instance().getSinkClient(info["sinkId"]) != nullptr;
+                }));
             } else if (uri == carlaUri) {
-                CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&]{ return Manager::instance().getSinkClient(info["sinkId"]) != nullptr; }));
+                CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&] {
+                    return Manager::instance().getSinkClient(info["sinkId"]) != nullptr;
+                }));
             }
         }
     } else {
@@ -486,14 +494,17 @@ ConferenceTest::testCreateParticipantsSinks()
         for (auto& info : pInfos_) {
             auto uri = string_remove_suffix(info["uri"], '@');
             if (uri == bobUri) {
-                CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&]{ return Manager::instance().getSinkClient(info["sinkId"]) == nullptr; }));
+                CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&] {
+                    return Manager::instance().getSinkClient(info["sinkId"]) == nullptr;
+                }));
             } else if (uri == carlaUri) {
-                CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&]{ return Manager::instance().getSinkClient(info["sinkId"]) == nullptr; }));
+                CPPUNIT_ASSERT(cv.wait_for(lk, 5s, [&] {
+                    return Manager::instance().getSinkClient(info["sinkId"]) == nullptr;
+                }));
             }
         }
     }
 
-
     hangupConference();
 
     libjami::unregisterSignalHandlers();
@@ -560,9 +571,9 @@ ConferenceTest::testActiveStatusAfterRemove()
     daviCall.reset();
 
     auto call2 = libjami::placeCallWithMedia(aliceId,
-                                           daviUri,
-                                           MediaAttribute::mediaAttributesToMediaMaps(
-                                               {defaultAudio}));
+                                             daviUri,
+                                             MediaAttribute::mediaAttributesToMediaMaps(
+                                                 {defaultAudio}));
     CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return !daviCall.callId.empty(); }));
     Manager::instance().answerCall(daviId, daviCall.callId);
     CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return daviCall.hostState == "CURRENT"; }));
@@ -793,17 +804,20 @@ ConferenceTest::testHostAddRmSecondVideo()
     // Alice adds new media
     pInfos_.clear();
     std::vector<std::map<std::string, std::string>> mediaList
-        = {{{libjami::Media::MediaAttributeKey::MEDIA_TYPE, libjami::Media::MediaAttributeValue::AUDIO},
+        = {{{libjami::Media::MediaAttributeKey::MEDIA_TYPE,
+             libjami::Media::MediaAttributeValue::AUDIO},
             {libjami::Media::MediaAttributeKey::ENABLED, "true"},
             {libjami::Media::MediaAttributeKey::MUTED, "false"},
             {libjami::Media::MediaAttributeKey::SOURCE, ""},
             {libjami::Media::MediaAttributeKey::LABEL, "audio_0"}},
-           {{libjami::Media::MediaAttributeKey::MEDIA_TYPE, libjami::Media::MediaAttributeValue::VIDEO},
+           {{libjami::Media::MediaAttributeKey::MEDIA_TYPE,
+             libjami::Media::MediaAttributeValue::VIDEO},
             {libjami::Media::MediaAttributeKey::ENABLED, "true"},
             {libjami::Media::MediaAttributeKey::MUTED, "false"},
             {libjami::Media::MediaAttributeKey::SOURCE, "bar"},
             {libjami::Media::MediaAttributeKey::LABEL, "video_0"}},
-           {{libjami::Media::MediaAttributeKey::MEDIA_TYPE, libjami::Media::MediaAttributeValue::VIDEO},
+           {{libjami::Media::MediaAttributeKey::MEDIA_TYPE,
+             libjami::Media::MediaAttributeValue::VIDEO},
             {libjami::Media::MediaAttributeKey::ENABLED, "true"},
             {libjami::Media::MediaAttributeKey::MUTED, "false"},
             {libjami::Media::MediaAttributeKey::SOURCE, "foo"},
@@ -855,17 +869,20 @@ ConferenceTest::testParticipantAddRmSecondVideo()
     // Bob adds new media
     pInfos_.clear();
     std::vector<std::map<std::string, std::string>> mediaList
-        = {{{libjami::Media::MediaAttributeKey::MEDIA_TYPE, libjami::Media::MediaAttributeValue::AUDIO},
+        = {{{libjami::Media::MediaAttributeKey::MEDIA_TYPE,
+             libjami::Media::MediaAttributeValue::AUDIO},
             {libjami::Media::MediaAttributeKey::ENABLED, "true"},
             {libjami::Media::MediaAttributeKey::MUTED, "false"},
             {libjami::Media::MediaAttributeKey::SOURCE, ""},
             {libjami::Media::MediaAttributeKey::LABEL, "audio_0"}},
-           {{libjami::Media::MediaAttributeKey::MEDIA_TYPE, libjami::Media::MediaAttributeValue::VIDEO},
+           {{libjami::Media::MediaAttributeKey::MEDIA_TYPE,
+             libjami::Media::MediaAttributeValue::VIDEO},
             {libjami::Media::MediaAttributeKey::ENABLED, "true"},
             {libjami::Media::MediaAttributeKey::MUTED, "false"},
             {libjami::Media::MediaAttributeKey::SOURCE, "bar"},
             {libjami::Media::MediaAttributeKey::LABEL, "video_0"}},
-           {{libjami::Media::MediaAttributeKey::MEDIA_TYPE, libjami::Media::MediaAttributeValue::VIDEO},
+           {{libjami::Media::MediaAttributeKey::MEDIA_TYPE,
+             libjami::Media::MediaAttributeValue::VIDEO},
             {libjami::Media::MediaAttributeKey::ENABLED, "true"},
             {libjami::Media::MediaAttributeKey::MUTED, "false"},
             {libjami::Media::MediaAttributeKey::SOURCE, "foo"},
@@ -927,10 +944,11 @@ ConferenceTest::testBrokenParticipantAudioAndVideo()
 
     // Start conference with four participants
     startConference(false, true);
-    auto expectedNumberOfParticipants = 4;
+    auto expectedNumberOfParticipants = 4u;
 
     // Check participants number
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]{ return pInfos_.size() == expectedNumberOfParticipants; }));
+    CPPUNIT_ASSERT(
+        cv.wait_for(lk, 30s, [&] { return pInfos_.size() == expectedNumberOfParticipants; }));
 
     // Crash participant
     auto daviAccount = Manager::instance().getAccount<JamiAccount>(daviId);
@@ -939,7 +957,8 @@ ConferenceTest::testBrokenParticipantAudioAndVideo()
 
     // Check participants number
     // It should have one less participant than in the conference start
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]{ return expectedNumberOfParticipants - 1 == pInfos_.size(); }));
+    CPPUNIT_ASSERT(
+        cv.wait_for(lk, 30s, [&] { return expectedNumberOfParticipants - 1 == pInfos_.size(); }));
 
     hangupConference();
 
@@ -953,10 +972,11 @@ ConferenceTest::testBrokenParticipantAudioOnly()
 
     // Start conference with four participants
     startConference(true, true);
-    auto expectedNumberOfParticipants = 4;
+    auto expectedNumberOfParticipants = 4u;
 
     // Check participants number
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]{ return pInfos_.size() == expectedNumberOfParticipants; }));
+    CPPUNIT_ASSERT(
+        cv.wait_for(lk, 30s, [&] { return pInfos_.size() == expectedNumberOfParticipants; }));
 
     // Crash participant
     auto daviAccount = Manager::instance().getAccount<JamiAccount>(daviId);
@@ -965,10 +985,24 @@ ConferenceTest::testBrokenParticipantAudioOnly()
 
     // Check participants number
     // It should have one less participant than in the conference start
-    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]{ return expectedNumberOfParticipants - 1 == pInfos_.size(); }));
+    CPPUNIT_ASSERT(
+        cv.wait_for(lk, 30s, [&] { return expectedNumberOfParticipants - 1 == pInfos_.size(); }));
 
     hangupConference();
+    libjami::unregisterSignalHandlers();
+}
 
+void
+ConferenceTest::testRemoveConferenceInOneOne()
+{
+    registerSignalHandlers();
+    startConference();
+    // Here it's 1:1 calls we merged, so we can close the conference
+    JAMI_INFO("Hangup Bob");
+    Manager::instance().hangupCall(bobId, bobCall.callId);
+    CPPUNIT_ASSERT(cv.wait_for(lk, 20s, [&] { return confId.empty() && bobCall.state == "OVER"; }));
+    Manager::instance().hangupCall(carlaId, carlaCall.callId);
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return carlaCall.state == "OVER"; }));
     libjami::unregisterSignalHandlers();
 }
 
diff --git a/test/unitTest/conversation/call.cpp b/test/unitTest/conversation/call.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..6596946eafe614b37fa81520ec953d4a14700caf
--- /dev/null
+++ b/test/unitTest/conversation/call.cpp
@@ -0,0 +1,832 @@
+/*
+ *  Copyright (C) 2022 Savoir-faire Linux Inc.
+ *  Author: Sébastien Blin <sebastien.blin@savoirfairelinux.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <cppunit/TestAssert.h>
+#include <cppunit/TestFixture.h>
+#include <cppunit/extensions/HelperMacros.h>
+
+#include <condition_variable>
+#include <filesystem>
+#include <string>
+
+#include "../../test_runner.h"
+#include "account_const.h"
+#include "common.h"
+#include "conversation/conversationcommon.h"
+#include "manager.h"
+
+using namespace std::literals::chrono_literals;
+
+namespace jami {
+namespace test {
+
+struct ConvData
+{
+    std::string id {};
+    bool requestReceived {false};
+    bool conferenceChanged {false};
+    bool conferenceRemoved {false};
+    std::string hostState {};
+    std::vector<std::map<std::string, std::string>> messages {};
+};
+
+class ConversationCallTest : public CppUnit::TestFixture
+{
+public:
+    ~ConversationCallTest() { libjami::fini(); }
+    static std::string name() { return "ConversationCallTest"; }
+    void setUp();
+    void tearDown();
+
+    std::string aliceId;
+    std::string bobId;
+    std::string bob2Id;
+    std::string carlaId;
+    ConvData aliceData_;
+    ConvData bobData_;
+    ConvData bob2Data_;
+    ConvData carlaData_;
+
+    std::mutex mtx;
+    std::unique_lock<std::mutex> lk {mtx};
+    std::condition_variable cv;
+
+private:
+    void connectSignals();
+    void enableCarla();
+
+    void testActiveCalls();
+    void testActiveCalls3Peers();
+    void testRejoinCall();
+    void testParticipantHangupConfNotRemoved();
+    void testJoinFinishedCall();
+    void testJoinFinishedCallForbidden();
+    void testUsePreference();
+    void testJoinWhileActiveCall();
+
+    CPPUNIT_TEST_SUITE(ConversationCallTest);
+    CPPUNIT_TEST(testActiveCalls);
+    CPPUNIT_TEST(testActiveCalls3Peers);
+    CPPUNIT_TEST(testRejoinCall);
+    CPPUNIT_TEST(testParticipantHangupConfNotRemoved);
+    CPPUNIT_TEST(testJoinFinishedCall);
+    CPPUNIT_TEST(testJoinFinishedCallForbidden);
+    CPPUNIT_TEST(testUsePreference);
+    CPPUNIT_TEST(testJoinWhileActiveCall);
+    CPPUNIT_TEST_SUITE_END();
+};
+
+CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(ConversationCallTest, ConversationCallTest::name());
+
+void
+ConversationCallTest::setUp()
+{
+    // Init daemon
+    libjami::init(
+        libjami::InitFlag(libjami::libjami_FLAG_DEBUG | libjami::libjami_FLAG_CONSOLE_LOG));
+    if (not Manager::instance().initialized)
+        CPPUNIT_ASSERT(libjami::start("jami-sample.yml"));
+
+    auto actors = load_actors("actors/alice-bob-carla.yml");
+    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
+ConversationCallTest::tearDown()
+{
+    auto bobArchive = std::filesystem::current_path().string() + "/bob.gz";
+    std::remove(bobArchive.c_str());
+
+    if (bob2Id.empty()) {
+        wait_for_removal_of({aliceId, bobId, carlaId});
+    } else {
+        wait_for_removal_of({aliceId, bobId, carlaId, bob2Id});
+    }
+}
+
+void
+ConversationCallTest::connectSignals()
+{
+    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
+    confHandlers.insert(libjami::exportable_callback<libjami::ConversationSignal::ConversationReady>(
+        [&](const std::string& accountId, const std::string& conversationId) {
+            if (accountId == aliceId)
+                aliceData_.id = conversationId;
+            else if (accountId == bobId)
+                bobData_.id = conversationId;
+            else if (accountId == bob2Id)
+                bob2Data_.id = conversationId;
+            else if (accountId == carlaId)
+                carlaData_.id = conversationId;
+            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 && aliceData_.id == conversationId)
+                aliceData_.messages.emplace_back(message);
+            if (accountId == bobId && bobData_.id == conversationId)
+                bobData_.messages.emplace_back(message);
+            if (accountId == bob2Id && bob2Data_.id == conversationId)
+                bob2Data_.messages.emplace_back(message);
+            if (accountId == carlaId && carlaData_.id == conversationId)
+                carlaData_.messages.emplace_back(message);
+            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;
+                if (accountId == bobId)
+                    bobData_.requestReceived = true;
+                if (accountId == bob2Id)
+                    bob2Data_.requestReceived = true;
+                if (accountId == carlaId)
+                    carlaData_.requestReceived = true;
+                cv.notify_one();
+            }));
+    confHandlers.insert(libjami::exportable_callback<libjami::CallSignal::ConferenceChanged>(
+        [&](const std::string& accountId, const std::string&, const std::string&) {
+            if (accountId == aliceId)
+                aliceData_.conferenceChanged = true;
+            cv.notify_one();
+        }));
+    confHandlers.insert(libjami::exportable_callback<libjami::CallSignal::ConferenceRemoved>(
+        [&](const std::string& accountId, const std::string&) {
+            if (accountId == aliceId)
+                aliceData_.conferenceRemoved = true;
+            cv.notify_one();
+        }));
+    confHandlers.insert(libjami::exportable_callback<libjami::CallSignal::StateChange>(
+        [&](const std::string& accountId,
+            const std::string& callId,
+            const std::string& state,
+            signed) {
+            if (accountId == aliceId) {
+                auto details = libjami::getCallDetails(aliceId, callId);
+                if (details.find("PEER_NUMBER") != details.end()) {
+                    auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
+                    auto bobUri = bobAccount->getUsername();
+                    auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
+                    auto carlaUri = carlaAccount->getUsername();
+
+                    if (details["PEER_NUMBER"].find(bobUri) != std::string::npos)
+                        bobData_.hostState = state;
+                    else if (details["PEER_NUMBER"].find(carlaUri) != std::string::npos)
+                        carlaData_.hostState = state;
+                }
+            }
+            cv.notify_one();
+        }));
+    libjami::registerSignalHandlers(confHandlers);
+}
+
+void
+ConversationCallTest::enableCarla()
+{
+    auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
+    // Enable carla
+    std::map<std::string, std::shared_ptr<libjami::CallbackWrapperBase>> confHandlers;
+    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();
+                }
+            }));
+    libjami::registerSignalHandlers(confHandlers);
+
+    Manager::instance().sendRegister(carlaId, true);
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&] { return carlaConnected; }));
+    confHandlers.clear();
+    libjami::unregisterSignalHandlers();
+}
+
+void
+ConversationCallTest::testActiveCalls()
+{
+    auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
+    auto bobUri = bobAccount->getUsername();
+    auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
+    auto carlaUri = carlaAccount->getUsername();
+    connectSignals();
+
+    // Start conversation
+    libjami::startConversation(aliceId);
+    cv.wait_for(lk, 30s, [&]() { return !aliceData_.id.empty(); });
+
+    // get active calls = 0
+    CPPUNIT_ASSERT(libjami::getActiveCalls(aliceId, aliceData_.id).size() == 0);
+
+    // start call
+    aliceData_.messages.clear();
+    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");
+
+    // get active calls = 1
+    CPPUNIT_ASSERT(libjami::getActiveCalls(aliceId, aliceData_.id).size() == 1);
+
+    // hangup
+    aliceData_.messages.clear();
+    Manager::instance().hangupCall(aliceId, callId);
+
+    // should get message
+    cv.wait_for(lk, 30s, [&]() { return !aliceData_.messages.empty(); });
+    CPPUNIT_ASSERT(aliceData_.messages[0].find("duration") != aliceData_.messages[0].end());
+
+    // get active calls = 0
+    CPPUNIT_ASSERT(libjami::getActiveCalls(aliceId, aliceData_.id).size() == 0);
+}
+
+void
+ConversationCallTest::testActiveCalls3Peers()
+{
+    enableCarla();
+    connectSignals();
+
+    auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
+    auto aliceUri = aliceAccount->getUsername();
+    auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
+    auto bobUri = bobAccount->getUsername();
+    auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
+    auto carlaUri = carlaAccount->getUsername();
+    // Start conversation
+    libjami::startConversation(aliceId);
+    cv.wait_for(lk, 30s, [&]() { return !aliceData_.id.empty(); });
+
+    libjami::addConversationMember(aliceId, aliceData_.id, bobUri);
+    libjami::addConversationMember(aliceId, aliceData_.id, carlaUri);
+    CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&]() {
+        return bobData_.requestReceived && carlaData_.requestReceived;
+    }));
+
+    aliceData_.messages.clear();
+    libjami::acceptConversationRequest(bobId, aliceData_.id);
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
+        return !bobData_.id.empty() && !aliceData_.messages.empty();
+    }));
+    aliceData_.messages.clear();
+    libjami::acceptConversationRequest(carlaId, aliceData_.id);
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
+        return !carlaData_.id.empty() && !aliceData_.messages.empty();
+    }));
+
+    // start call
+    aliceData_.messages.clear();
+    bobData_.messages.clear();
+    carlaData_.messages.clear();
+    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";
+    };
+    // should get message
+    cv.wait_for(lk, 30s, [&]() {
+        return lastCommitIsCall(aliceData_) && lastCommitIsCall(bobData_)
+               && lastCommitIsCall(carlaData_);
+    });
+
+    auto destination = fmt::format("rdv:{}/{}/{}/{}",
+                                   bobData_.id,
+                                   bobData_.messages.rbegin()->at("uri"),
+                                   bobData_.messages.rbegin()->at("device"),
+                                   bobData_.messages.rbegin()->at("confId"));
+
+    aliceData_.conferenceChanged = false;
+    libjami::placeCallWithMedia(bobId, destination, {});
+    cv.wait_for(lk, 30s, [&]() {
+        return aliceData_.conferenceChanged && bobData_.hostState == "CURRENT";
+    });
+    aliceData_.conferenceChanged = false;
+    libjami::placeCallWithMedia(carlaId, destination, {});
+    cv.wait_for(lk, 30s, [&]() {
+        return aliceData_.conferenceChanged && carlaData_.hostState == "CURRENT";
+    });
+
+    // get 3 participants
+    auto callList = libjami::getParticipantList(aliceId, bobData_.messages.rbegin()->at("confId"));
+    CPPUNIT_ASSERT(callList.size() == 3);
+
+    // get active calls = 1
+    CPPUNIT_ASSERT(libjami::getActiveCalls(bobId, bobData_.id).size() == 1);
+
+    // hangup
+    aliceData_.messages.clear();
+    bobData_.messages.clear();
+    carlaData_.messages.clear();
+    Manager::instance().hangupCall(aliceId, callId);
+
+    // should get message
+    cv.wait_for(lk, 30s, [&]() {
+        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());
+
+    // get active calls = 0
+    CPPUNIT_ASSERT(libjami::getActiveCalls(aliceId, aliceData_.id).size() == 0);
+}
+
+void
+ConversationCallTest::testRejoinCall()
+{
+    enableCarla();
+    connectSignals();
+
+    auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
+    auto aliceUri = aliceAccount->getUsername();
+    auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
+    auto bobUri = bobAccount->getUsername();
+    auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
+    auto carlaUri = carlaAccount->getUsername();
+    // Start conversation
+    libjami::startConversation(aliceId);
+    cv.wait_for(lk, 30s, [&]() { return !aliceData_.id.empty(); });
+
+    libjami::addConversationMember(aliceId, aliceData_.id, bobUri);
+    libjami::addConversationMember(aliceId, aliceData_.id, carlaUri);
+    CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&]() {
+        return bobData_.requestReceived && carlaData_.requestReceived;
+    }));
+
+    aliceData_.messages.clear();
+    libjami::acceptConversationRequest(bobId, aliceData_.id);
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
+        return !bobData_.id.empty() && !aliceData_.messages.empty();
+    }));
+    aliceData_.messages.clear();
+    libjami::acceptConversationRequest(carlaId, aliceData_.id);
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
+        return !carlaData_.id.empty() && !aliceData_.messages.empty();
+    }));
+
+    // start call
+    aliceData_.messages.clear();
+    bobData_.messages.clear();
+    carlaData_.messages.clear();
+    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";
+    };
+    // should get message
+    cv.wait_for(lk, 30s, [&]() {
+        return lastCommitIsCall(aliceData_) && lastCommitIsCall(bobData_)
+               && lastCommitIsCall(carlaData_);
+    });
+
+    auto confId = bobData_.messages.rbegin()->at("confId");
+    auto destination = fmt::format("rdv:{}/{}/{}/{}",
+                                   bobData_.id,
+                                   bobData_.messages.rbegin()->at("uri"),
+                                   bobData_.messages.rbegin()->at("device"),
+                                   bobData_.messages.rbegin()->at("confId"));
+
+    aliceData_.conferenceChanged = false;
+    auto bobCall = libjami::placeCallWithMedia(bobId, destination, {});
+    cv.wait_for(lk, 30s, [&]() {
+        return aliceData_.conferenceChanged && bobData_.hostState == "CURRENT";
+    });
+    aliceData_.conferenceChanged = false;
+    libjami::placeCallWithMedia(carlaId, destination, {});
+    cv.wait_for(lk, 30s, [&]() {
+        return aliceData_.conferenceChanged && carlaData_.hostState == "CURRENT";
+    });
+
+    CPPUNIT_ASSERT(libjami::getParticipantList(aliceId, confId).size() == 3);
+
+    // hangup 1 participant and rejoin
+    aliceData_.messages.clear();
+    bobData_.messages.clear();
+    aliceData_.conferenceChanged = false;
+    Manager::instance().hangupCall(bobId, bobCall);
+    cv.wait_for(lk, 30s, [&]() {
+        return aliceData_.conferenceChanged && bobData_.hostState == "OVER";
+    });
+
+    CPPUNIT_ASSERT(libjami::getParticipantList(aliceId, confId).size() == 2);
+
+    aliceData_.conferenceChanged = false;
+    libjami::placeCallWithMedia(bobId, destination, {});
+    cv.wait_for(lk, 30s, [&]() {
+        return aliceData_.conferenceChanged && bobData_.hostState == "CURRENT";
+    });
+
+    CPPUNIT_ASSERT(libjami::getParticipantList(aliceId, confId).size() == 3);
+    CPPUNIT_ASSERT(aliceData_.messages.empty());
+    CPPUNIT_ASSERT(bobData_.messages.empty());
+
+    // hangup
+    aliceData_.messages.clear();
+    bobData_.messages.clear();
+    carlaData_.messages.clear();
+    Manager::instance().hangupCall(aliceId, callId);
+
+    // should get message
+    cv.wait_for(lk, 30s, [&]() {
+        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());
+
+    // get active calls = 0
+    CPPUNIT_ASSERT(libjami::getActiveCalls(aliceId, aliceData_.id).size() == 0);
+}
+
+void
+ConversationCallTest::testParticipantHangupConfNotRemoved()
+{
+    connectSignals();
+
+    auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
+    auto aliceUri = aliceAccount->getUsername();
+    auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
+    auto bobUri = bobAccount->getUsername();
+    // Start conversation
+    libjami::startConversation(aliceId);
+    cv.wait_for(lk, 30s, [&]() { return !aliceData_.id.empty(); });
+
+    libjami::addConversationMember(aliceId, aliceData_.id, bobUri);
+    CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&]() { return bobData_.requestReceived; }));
+
+    aliceData_.messages.clear();
+    libjami::acceptConversationRequest(bobId, aliceData_.id);
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
+        return !bobData_.id.empty() && !aliceData_.messages.empty();
+    }));
+
+    // start call
+    aliceData_.messages.clear();
+    bobData_.messages.clear();
+    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";
+    };
+    // should get message
+    cv.wait_for(lk, 30s, [&]() {
+        return lastCommitIsCall(aliceData_) && lastCommitIsCall(bobData_);
+    });
+
+    auto destination = fmt::format("rdv:{}/{}/{}/{}",
+                                   bobData_.id,
+                                   bobData_.messages.rbegin()->at("uri"),
+                                   bobData_.messages.rbegin()->at("device"),
+                                   bobData_.messages.rbegin()->at("confId"));
+
+    aliceData_.conferenceChanged = false;
+    auto bobCallId = libjami::placeCallWithMedia(bobId, destination, {});
+    cv.wait_for(lk, 30s, [&]() {
+        return aliceData_.conferenceChanged && bobData_.hostState == "CURRENT";
+    });
+
+    // hangup bob MUST NOT stop the conference
+    aliceData_.messages.clear();
+    bobData_.messages.clear();
+    aliceData_.conferenceChanged = false;
+    Manager::instance().hangupCall(bobId, bobCallId);
+
+    CPPUNIT_ASSERT(!cv.wait_for(lk, 10s, [&]() { return aliceData_.conferenceRemoved; }));
+}
+
+void
+ConversationCallTest::testJoinFinishedCall()
+{
+    enableCarla();
+    connectSignals();
+    auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
+    auto aliceUri = aliceAccount->getUsername();
+    auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
+    auto bobUri = bobAccount->getUsername();
+    auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
+    auto carlaUri = carlaAccount->getUsername();
+    // Start conversation
+    libjami::startConversation(aliceId);
+    cv.wait_for(lk, 30s, [&]() { return !aliceData_.id.empty(); });
+    libjami::addConversationMember(aliceId, aliceData_.id, bobUri);
+    libjami::addConversationMember(aliceId, aliceData_.id, carlaUri);
+    CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&]() {
+        return bobData_.requestReceived && carlaData_.requestReceived;
+    }));
+    aliceData_.messages.clear();
+    libjami::acceptConversationRequest(bobId, aliceData_.id);
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
+        return !bobData_.id.empty() && !aliceData_.messages.empty();
+    }));
+    aliceData_.messages.clear();
+    libjami::acceptConversationRequest(carlaId, aliceData_.id);
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
+        return !carlaData_.id.empty() && !aliceData_.messages.empty();
+    }));
+    // start call
+    aliceData_.messages.clear();
+    bobData_.messages.clear();
+    carlaData_.messages.clear();
+    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";
+    };
+    // should get message
+    cv.wait_for(lk, 30s, [&]() {
+        return lastCommitIsCall(aliceData_) && lastCommitIsCall(bobData_)
+               && lastCommitIsCall(carlaData_);
+    });
+    auto confId = bobData_.messages.rbegin()->at("confId");
+    auto destination = fmt::format("rdv:{}/{}/{}/{}",
+                                   bobData_.id,
+                                   bobData_.messages.rbegin()->at("uri"),
+                                   bobData_.messages.rbegin()->at("device"),
+                                   bobData_.messages.rbegin()->at("confId"));
+    // hangup
+    aliceData_.messages.clear();
+    bobData_.messages.clear();
+    carlaData_.messages.clear();
+    Manager::instance().hangupCall(aliceId, callId);
+    // should get message
+    cv.wait_for(lk, 30s, [&]() {
+        return !aliceData_.messages.empty() && !bobData_.messages.empty()
+               && !carlaData_.messages.empty();
+    });
+    CPPUNIT_ASSERT(libjami::getActiveCalls(aliceId, aliceData_.id).size() == 0);
+    aliceData_.messages.clear();
+    bobData_.messages.clear();
+    carlaData_.messages.clear();
+    // If bob try to join the call, it will re-host a new conference
+    // and commit a new active call.
+    auto bobCall = libjami::placeCallWithMedia(bobId, destination, {});
+    // should get message
+    cv.wait_for(lk, 30s, [&]() {
+        return lastCommitIsCall(aliceData_) && lastCommitIsCall(bobData_)
+               && lastCommitIsCall(carlaData_) && bobData_.hostState == "CURRENT";
+    });
+    confId = bobData_.messages.rbegin()->at("confId");
+    CPPUNIT_ASSERT(libjami::getActiveCalls(aliceId, aliceData_.id).size() == 1);
+    // hangup
+    aliceData_.messages.clear();
+    bobData_.messages.clear();
+    carlaData_.messages.clear();
+    Manager::instance().hangupConference(aliceId, confId);
+    // should get message
+    cv.wait_for(lk, 30s, [&]() {
+        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());
+    // get active calls = 0
+    CPPUNIT_ASSERT(libjami::getActiveCalls(aliceId, aliceData_.id).size() == 0);
+}
+
+void
+ConversationCallTest::testJoinFinishedCallForbidden()
+{
+    enableCarla();
+    connectSignals();
+
+    auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
+    auto aliceUri = aliceAccount->getUsername();
+    auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
+    auto bobUri = bobAccount->getUsername();
+    auto carlaAccount = Manager::instance().getAccount<JamiAccount>(carlaId);
+    auto carlaUri = carlaAccount->getUsername();
+    // Start conversation
+    libjami::startConversation(aliceId);
+    cv.wait_for(lk, 30s, [&]() { return !aliceData_.id.empty(); });
+
+    // Do not host conference for others
+    libjami::setConversationPreferences(aliceId, aliceData_.id, {{"hostConference", "false"}});
+
+    libjami::addConversationMember(aliceId, aliceData_.id, bobUri);
+    libjami::addConversationMember(aliceId, aliceData_.id, carlaUri);
+    CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&]() {
+        return bobData_.requestReceived && carlaData_.requestReceived;
+    }));
+
+    aliceData_.messages.clear();
+    libjami::acceptConversationRequest(bobId, aliceData_.id);
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
+        return !bobData_.id.empty() && !aliceData_.messages.empty();
+    }));
+    aliceData_.messages.clear();
+    libjami::acceptConversationRequest(carlaId, aliceData_.id);
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
+        return !carlaData_.id.empty() && !aliceData_.messages.empty();
+    }));
+
+    // start call
+    aliceData_.messages.clear();
+    bobData_.messages.clear();
+    carlaData_.messages.clear();
+    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";
+    };
+    // should get message
+    cv.wait_for(lk, 30s, [&]() {
+        return lastCommitIsCall(aliceData_) && lastCommitIsCall(bobData_)
+               && lastCommitIsCall(carlaData_);
+    });
+
+    auto confId = bobData_.messages.rbegin()->at("confId");
+    auto destination = fmt::format("rdv:{}/{}/{}/{}",
+                                   bobData_.id,
+                                   bobData_.messages.rbegin()->at("uri"),
+                                   bobData_.messages.rbegin()->at("device"),
+                                   bobData_.messages.rbegin()->at("confId"));
+
+    // hangup
+    aliceData_.messages.clear();
+    bobData_.messages.clear();
+    carlaData_.messages.clear();
+    Manager::instance().hangupCall(aliceId, callId);
+
+    // should get message
+    cv.wait_for(lk, 30s, [&]() {
+        return !aliceData_.messages.empty() && !bobData_.messages.empty()
+               && !carlaData_.messages.empty();
+    });
+
+    CPPUNIT_ASSERT(libjami::getActiveCalls(aliceId, aliceData_.id).size() == 0);
+
+    aliceData_.messages.clear();
+    bobData_.messages.clear();
+    carlaData_.messages.clear();
+    // If bob try to join the call, it will re-host a new conference
+    // and commit a new active call.
+    auto bobCall = libjami::placeCallWithMedia(bobId, destination, {});
+    // should get message
+    cv.wait_for(lk, 30s, [&]() {
+        return lastCommitIsCall(aliceData_) && lastCommitIsCall(bobData_)
+               && lastCommitIsCall(carlaData_) && bobData_.hostState == "CURRENT";
+    });
+
+    confId = bobData_.messages.rbegin()->at("confId");
+
+    CPPUNIT_ASSERT(libjami::getActiveCalls(aliceId, aliceData_.id).size() == 1);
+
+    // hangup
+    aliceData_.messages.clear();
+    bobData_.messages.clear();
+    carlaData_.messages.clear();
+    Manager::instance().hangupConference(aliceId, confId);
+
+    // should get message
+    cv.wait_for(lk, 30s, [&]() {
+        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());
+
+    // get active calls = 0
+    CPPUNIT_ASSERT(libjami::getActiveCalls(aliceId, aliceData_.id).size() == 0);
+}
+
+void
+ConversationCallTest::testUsePreference()
+{
+    enableCarla();
+    connectSignals();
+    auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
+    auto aliceUri = aliceAccount->getUsername();
+    auto aliceDevice = std::string(aliceAccount->currentDeviceId());
+    auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
+    auto bobUri = bobAccount->getUsername();
+    // Start conversation
+    libjami::startConversation(aliceId);
+    cv.wait_for(lk, 30s, [&]() { return !aliceData_.id.empty(); });
+    libjami::addConversationMember(aliceId, aliceData_.id, bobUri);
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobData_.requestReceived; }));
+    aliceData_.messages.clear();
+    libjami::acceptConversationRequest(bobId, aliceData_.id);
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
+        return !bobData_.id.empty() && !aliceData_.messages.empty();
+    }));
+
+    // Update preferences
+    aliceData_.messages.clear();
+    bobData_.messages.clear();
+    auto lastCommitIsProfile = [&](const auto& data) {
+        return !data.messages.empty()
+               && data.messages.rbegin()->at("type") == "application/update-profile";
+    };
+    libjami::updateConversationInfos(aliceId,
+                                     aliceData_.id,
+                                     std::map<std::string, std::string> {
+                                         {"rdvAccount", aliceUri},
+                                         {"rdvDevice", aliceDevice},
+                                     });
+    // should get message
+    cv.wait_for(lk, 30s, [&]() {
+        return lastCommitIsProfile(aliceData_) && lastCommitIsProfile(bobData_);
+    });
+
+    // start call
+    aliceData_.messages.clear();
+    bobData_.messages.clear();
+    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";
+    };
+    // should get message
+    cv.wait_for(lk, 30s, [&]() {
+        return lastCommitIsCall(aliceData_) && lastCommitIsCall(bobData_);
+    });
+    auto confId = bobData_.messages.rbegin()->at("confId");
+
+    // Alice should be the host
+    CPPUNIT_ASSERT(aliceAccount->getConference(confId));
+    Manager::instance().hangupCall(bobId, callId);
+}
+
+void
+ConversationCallTest::testJoinWhileActiveCall()
+{
+    connectSignals();
+
+    auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceId);
+    auto aliceUri = aliceAccount->getUsername();
+    auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobId);
+    auto bobUri = bobAccount->getUsername();
+    // Start conversation
+    libjami::startConversation(aliceId);
+    cv.wait_for(lk, 30s, [&]() { return !aliceData_.id.empty(); });
+
+    // start call
+    aliceData_.messages.clear();
+    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";
+    };
+    // should get message
+    cv.wait_for(lk, 30s, [&]() { return lastCommitIsCall(aliceData_); });
+
+    libjami::addConversationMember(aliceId, aliceData_.id, bobUri);
+    CPPUNIT_ASSERT(cv.wait_for(lk, 60s, [&]() { return bobData_.requestReceived; }));
+
+    aliceData_.messages.clear();
+    libjami::acceptConversationRequest(bobId, aliceData_.id);
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() {
+        return !bobData_.id.empty() && !aliceData_.messages.empty();
+    }));
+
+    CPPUNIT_ASSERT(libjami::getActiveCalls(bobId, bobData_.id).size() == 1);
+
+    // hangup bob MUST NOT stop the conference
+    aliceData_.messages.clear();
+    bobData_.messages.clear();
+    aliceData_.conferenceChanged = false;
+    Manager::instance().hangupCall(bobId, bobCallId);
+
+    CPPUNIT_ASSERT(!cv.wait_for(lk, 10s, [&]() { return aliceData_.conferenceRemoved; }));
+}
+
+} // namespace test
+} // namespace jami
+
+RING_TEST_RUNNER(jami::test::ConversationCallTest::name())
diff --git a/test/unitTest/conversation/conversationMembersEvent.cpp b/test/unitTest/conversation/conversationMembersEvent.cpp
index c65acdc49791a29f10ffd427cbee58dcc5725595..42da0aa0baa2306232284145d2647a76af59c2de 100644
--- a/test/unitTest/conversation/conversationMembersEvent.cpp
+++ b/test/unitTest/conversation/conversationMembersEvent.cpp
@@ -86,6 +86,7 @@ public:
     void testRemoveRequestBannedMultiDevices();
     void testBanUnbanMultiDevice();
     void testBanUnbanGotFirstConv();
+    void testBanHostWhileHosting();
 
     std::string aliceId;
     std::string bobId;
@@ -125,6 +126,7 @@ private:
     CPPUNIT_TEST(testRemoveRequestBannedMultiDevices);
     CPPUNIT_TEST(testBanUnbanMultiDevice);
     CPPUNIT_TEST(testBanUnbanGotFirstConv);
+    CPPUNIT_TEST(testBanHostWhileHosting);
     CPPUNIT_TEST_SUITE_END();
 };
 
@@ -135,7 +137,8 @@ void
 ConversationMembersEventTest::setUp()
 {
     // Init daemon
-    libjami::init(libjami::InitFlag(libjami::LIBJAMI_FLAG_DEBUG | libjami::LIBJAMI_FLAG_CONSOLE_LOG));
+    libjami::init(
+        libjami::InitFlag(libjami::LIBJAMI_FLAG_DEBUG | libjami::LIBJAMI_FLAG_CONSOLE_LOG));
     if (not Manager::instance().initialized)
         CPPUNIT_ASSERT(libjami::start("jami-sample.yml"));
 
@@ -202,9 +205,9 @@ ConversationMembersEventTest::generateFakeInvite(std::shared_ptr<JamiAccount> ac
     cr.commitMessage(Json::writeString(wbuilder, json));
 
     libjami::sendMessage(account->getAccountID(),
-                       convId,
-                       "trigger the fake history to be pulled"s,
-                       "");
+                         convId,
+                         "trigger the fake history to be pulled"s,
+                         "");
 }
 
 void
@@ -412,15 +415,16 @@ ConversationMembersEventTest::testMemberAddedNoBadFile()
                 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();
-        }));
+    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");
     generateFakeInvite(aliceAccount, convId, bobUri);
@@ -705,7 +709,8 @@ ConversationMembersEventTest::testRemoveInvitedMember()
         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];
+                auto deviceAnnounced
+                    = details[libjami::Account::VolatileProperties::DEVICE_ANNOUNCED];
                 if (deviceAnnounced == "true") {
                     carlaConnected = true;
                     cv.notify_one();
@@ -796,7 +801,8 @@ ConversationMembersEventTest::testMemberBanNoBadFile()
         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];
+                auto deviceAnnounced
+                    = details[libjami::Account::VolatileProperties::DEVICE_ANNOUNCED];
                 if (deviceAnnounced == "true") {
                     carlaConnected = true;
                     cv.notify_one();
@@ -823,15 +829,16 @@ ConversationMembersEventTest::testMemberBanNoBadFile()
             }
             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();
-        }));
+    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; }));
@@ -1078,7 +1085,8 @@ ConversationMembersEventTest::testMemberCannotBanOther()
         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];
+                auto deviceAnnounced
+                    = details[libjami::Account::VolatileProperties::DEVICE_ANNOUNCED];
                 if (deviceAnnounced == "true") {
                     carlaConnected = true;
                     cv.notify_one();
@@ -1105,15 +1113,16 @@ ConversationMembersEventTest::testMemberCannotBanOther()
             }
             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();
-        }));
+    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; }));
@@ -1174,7 +1183,8 @@ ConversationMembersEventTest::testMemberCannotUnBanOther()
         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];
+                auto deviceAnnounced
+                    = details[libjami::Account::VolatileProperties::DEVICE_ANNOUNCED];
                 if (deviceAnnounced == "true") {
                     carlaConnected = true;
                     cv.notify_one();
@@ -1203,15 +1213,16 @@ ConversationMembersEventTest::testMemberCannotUnBanOther()
             }
             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();
-        }));
+    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; }));
@@ -1302,21 +1313,23 @@ ConversationMembersEventTest::testCheckAdminFakeAVoteIsDetected()
         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];
+                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();
-        }));
+    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; }));
@@ -1434,15 +1447,16 @@ ConversationMembersEventTest::testCommitUnauthorizedUser()
                 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();
-        }));
+    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);
@@ -1511,21 +1525,23 @@ ConversationMembersEventTest::testMemberJoinsNoBadFile()
         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];
+                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();
-        }));
+    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->convModule()->addConversationMember(convId, carlaUri, false);
@@ -1595,21 +1611,23 @@ ConversationMembersEventTest::testMemberAddedNoCertificate()
         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];
+                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();
-        }));
+    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->convModule()->addConversationMember(convId, carlaUri, false);
@@ -1689,21 +1707,23 @@ ConversationMembersEventTest::testMemberJoinsInviteRemoved()
         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];
+                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();
-        }));
+    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->convModule()->addConversationMember(convId, carlaUri, false);
@@ -1768,16 +1788,17 @@ ConversationMembersEventTest::testFailAddMemberInOneToOne()
     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::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) {
@@ -1821,16 +1842,17 @@ ConversationMembersEventTest::testOneToOneFetchWithNewMemberRefused()
     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::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) {
@@ -1852,15 +1874,16 @@ ConversationMembersEventTest::testOneToOneFetchWithNewMemberRefused()
             }
             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();
-        }));
+    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, {});
@@ -1950,16 +1973,17 @@ ConversationMembersEventTest::testGetConversationsMembersWhileSyncing()
     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::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) {
@@ -2058,12 +2082,13 @@ ConversationMembersEventTest::testAvoidTwoOneToOne()
             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();
-        }));
+    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
@@ -2149,14 +2174,15 @@ ConversationMembersEventTest::testAvoidTwoOneToOneMultiDevices()
                 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();
-        }));
+    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
@@ -2526,6 +2552,80 @@ ConversationMembersEventTest::testBanUnbanGotFirstConv()
     CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return bobMsgReceived && bob2MsgReceived; }));
 }
 
+void
+ConversationMembersEventTest::testBanHostWhileHosting()
+{
+    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::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;
+    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;
+    libjami::acceptConversationRequest(bobId, convId);
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return memberMessageGenerated; }));
+
+    // Now, Bob starts a call
+    auto callId = libjami::placeCallWithMedia(bobId, "swarm:" + convId, {});
+    // should get message
+    CPPUNIT_ASSERT(cv.wait_for(lk, 30s, [&]() { return callMessageGenerated; }));
+
+    // 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;
+    libjami::removeConversationMember(aliceId, convId, bobUri);
+    CPPUNIT_ASSERT(
+        cv.wait_for(lk, 30s, [&]() { return memberMessageGenerated && voteMessageGenerated; }));
+    auto members = libjami::getConversationMembers(aliceId, convId);
+    CPPUNIT_ASSERT(members.size() == 1);
+    CPPUNIT_ASSERT(members[0]["uri"] == aliceAccount->getUsername());
+    CPPUNIT_ASSERT(members[0]["role"] == "admin");
+    CPPUNIT_ASSERT(libjami::getActiveCalls(aliceId, convId).size() == 0);
+
+    libjami::unregisterSignalHandlers();
+}
 } // namespace test
 } // namespace jami