diff --git a/bin/dbus/cx.ring.Ring.CallManager.xml b/bin/dbus/cx.ring.Ring.CallManager.xml
index f44b4978581fc49d775c7c404919be7690504823..6178ae67e6644627850a5a233e3cb533d1de7840 100644
--- a/bin/dbus/cx.ring.Ring.CallManager.xml
+++ b/bin/dbus/cx.ring.Ring.CallManager.xml
@@ -904,9 +904,10 @@
               Emitted when a new conference is created. Client is responsible for storing the confID and call <tp:member-ref>getParticipantList</tp:member-ref> to update the display.
             </tp:docstring>
             <arg type="s" name="accountId" />
-            <arg type="s" name="confID">
+            <arg type="s" name="conversationId" />
+            <arg type="s" name="confId">
               <tp:docstring>
-                A new conference ID.
+                A new conference Id.
               </tp:docstring>
             </arg>
         </signal>
diff --git a/bin/dbus/dbuscallmanager.hpp b/bin/dbus/dbuscallmanager.hpp
index 6b5630afdc1701cae9e843d219aed01afc8b769f..1fe65b7f70bdfb1397b84d58a6c3286f2c6832b3 100644
--- a/bin/dbus/dbuscallmanager.hpp
+++ b/bin/dbus/dbuscallmanager.hpp
@@ -489,7 +489,7 @@ private:
                 exportable_serialized_callback<CallSignal::RecordPlaybackFilepath>(
                     std::bind(&DBusCallManager::emitRecordPlaybackFilepath, this, _1, _2)),
                 exportable_serialized_callback<CallSignal::ConferenceCreated>(
-                    std::bind(&DBusCallManager::emitConferenceCreated, this, _1, _2)),
+                    std::bind(&DBusCallManager::emitConferenceCreated, this, _1, _2, _3)),
                 exportable_serialized_callback<CallSignal::ConferenceChanged>(
                     std::bind(&DBusCallManager::emitConferenceChanged, this, _1, _2, _3)),
                 exportable_serialized_callback<CallSignal::UpdatePlaybackScale>(
diff --git a/bin/jni/callmanager.i b/bin/jni/callmanager.i
index b197107b7732f416858439170efd7ddc264f677e..96c52814f16ab9924ec325eeb20dcf7e4301c865 100644
--- a/bin/jni/callmanager.i
+++ b/bin/jni/callmanager.i
@@ -40,7 +40,7 @@ public:
     virtual void mediaChangeRequested(const std::string& accountId, const std::string& callId,
         const std::vector<std::map<std::string, std::string>>& mediaList){}
     virtual void recordPlaybackFilepath(const std::string& id, const std::string& filename){}
-    virtual void conferenceCreated(const std::string& accountId, const std::string& confId){}
+    virtual void conferenceCreated(const std::string& accountId, const std::string& conversationId, const std::string& confId){}
     virtual void conferenceChanged(const std::string& accountId, const std::string& confId, const std::string& state){}
     virtual void conferenceRemoved(const std::string& accountId, const std::string& confId){}
     virtual void updatePlaybackScale(const std::string& filepath, int position, int scale){}
@@ -164,7 +164,7 @@ public:
     virtual void mediaChangeRequested(const std::string& accountId, const std::string& callId,
         const std::vector<std::map<std::string, std::string>>& mediaList){}
     virtual void recordPlaybackFilepath(const std::string& id, const std::string& filename){}
-    virtual void conferenceCreated(const std::string& accountId, const std::string& confId){}
+    virtual void conferenceCreated(const std::string& accountId, const std::string& conversationId, const std::string& confId){}
     virtual void conferenceChanged(const std::string& accountId, const std::string& confId, const std::string& state){}
     virtual void conferenceRemoved(const std::string& accountId, const std::string& confId){}
     virtual void updatePlaybackScale(const std::string& filepath, int position, int scale){}
diff --git a/bin/jni/jni_interface.i b/bin/jni/jni_interface.i
index d2b4e166fce874516b083140d0fb2f64f3850df3..90da5cdeb01808bc8f432fb1b2091e2e78355b33 100644
--- a/bin/jni/jni_interface.i
+++ b/bin/jni/jni_interface.i
@@ -248,7 +248,7 @@ void init(ConfigurationCallback* confM, Callback* callM, PresenceCallback* presM
         exportable_callback<CallSignal::IncomingCallWithMedia>(bind(&Callback::incomingCallWithMedia, callM, _1, _2, _3, _4)),
         exportable_callback<CallSignal::MediaChangeRequested>(bind(&Callback::mediaChangeRequested, callM, _1, _2, _3)),
         exportable_callback<CallSignal::RecordPlaybackFilepath>(bind(&Callback::recordPlaybackFilepath, callM, _1, _2)),
-        exportable_callback<CallSignal::ConferenceCreated>(bind(&Callback::conferenceCreated, callM, _1, _2)),
+        exportable_callback<CallSignal::ConferenceCreated>(bind(&Callback::conferenceCreated, callM, _1, _2, _3)),
         exportable_callback<CallSignal::ConferenceChanged>(bind(&Callback::conferenceChanged, callM, _1, _2, _3)),
         exportable_callback<CallSignal::ConferenceRemoved>(bind(&Callback::conferenceRemoved, callM, _1, _2)),
         exportable_callback<CallSignal::UpdatePlaybackScale>(bind(&Callback::updatePlaybackScale, callM, _1, _2, _3)),
diff --git a/bin/nodejs/callback.h b/bin/nodejs/callback.h
index e1299401a7d463fc9f3427d76c660e75144e66c2..4ec2e5923288606e1ddb1bd3a153798eecf50502 100644
--- a/bin/nodejs/callback.h
+++ b/bin/nodejs/callback.h
@@ -811,15 +811,16 @@ onConversationError(const std::string& accountId,
 }
 
 void
-conferenceCreated(const std::string& accountId, const std::string& confId)
+conferenceCreated(const std::string& accountId, const std::string& conversationId, const std::string& confId)
 {
     std::lock_guard lock(pendingSignalsLock);
-    pendingSignals.emplace([accountId, confId]() {
+    pendingSignals.emplace([accountId, confId, conversationId]() {
         Local<Function> func = Local<Function>::New(Isolate::GetCurrent(), conferenceCreatedCb);
         if (!func.IsEmpty()) {
             SWIGV8_VALUE callback_args[] = {V8_STRING_NEW_LOCAL(accountId),
+                                            V8_STRING_NEW_LOCAL(conversationId),
                                             V8_STRING_NEW_LOCAL(confId)};
-            func->Call(SWIGV8_CURRENT_CONTEXT(), SWIGV8_NULL(), 2, callback_args);
+            func->Call(SWIGV8_CURRENT_CONTEXT(), SWIGV8_NULL(), 3, callback_args);
         }
     });
     uv_async_send(&signalAsync);
diff --git a/bin/nodejs/callmanager.i b/bin/nodejs/callmanager.i
index b197107b7732f416858439170efd7ddc264f677e..96c52814f16ab9924ec325eeb20dcf7e4301c865 100644
--- a/bin/nodejs/callmanager.i
+++ b/bin/nodejs/callmanager.i
@@ -40,7 +40,7 @@ public:
     virtual void mediaChangeRequested(const std::string& accountId, const std::string& callId,
         const std::vector<std::map<std::string, std::string>>& mediaList){}
     virtual void recordPlaybackFilepath(const std::string& id, const std::string& filename){}
-    virtual void conferenceCreated(const std::string& accountId, const std::string& confId){}
+    virtual void conferenceCreated(const std::string& accountId, const std::string& conversationId, const std::string& confId){}
     virtual void conferenceChanged(const std::string& accountId, const std::string& confId, const std::string& state){}
     virtual void conferenceRemoved(const std::string& accountId, const std::string& confId){}
     virtual void updatePlaybackScale(const std::string& filepath, int position, int scale){}
@@ -164,7 +164,7 @@ public:
     virtual void mediaChangeRequested(const std::string& accountId, const std::string& callId,
         const std::vector<std::map<std::string, std::string>>& mediaList){}
     virtual void recordPlaybackFilepath(const std::string& id, const std::string& filename){}
-    virtual void conferenceCreated(const std::string& accountId, const std::string& confId){}
+    virtual void conferenceCreated(const std::string& accountId, const std::string& conversationId, const std::string& confId){}
     virtual void conferenceChanged(const std::string& accountId, const std::string& confId, const std::string& state){}
     virtual void conferenceRemoved(const std::string& accountId, const std::string& confId){}
     virtual void updatePlaybackScale(const std::string& filepath, int position, int scale){}
diff --git a/src/jami/callmanager_interface.h b/src/jami/callmanager_interface.h
index f04ec7d9ccaa93fa465bdee3a1c6798664bab3ca..a3bcca9fedede6bf5ca92e3d4832e098fa4adc83 100644
--- a/src/jami/callmanager_interface.h
+++ b/src/jami/callmanager_interface.h
@@ -258,7 +258,7 @@ struct LIBJAMI_PUBLIC CallSignal
     struct LIBJAMI_PUBLIC ConferenceCreated
     {
         constexpr static const char* name = "ConferenceCreated";
-        using cb_type = void(const std::string&, const std::string&);
+        using cb_type = void(const std::string&, const std::string&, const std::string&);
     };
     struct LIBJAMI_PUBLIC ConferenceChanged
     {
diff --git a/src/jamidht/conversation_module.cpp b/src/jamidht/conversation_module.cpp
index 725848d3ed14ea5ceb9206c2b7086bbc9ce9e03e..fced6f2bf3bbff7d4638e8f8472d1e11da14508a 100644
--- a/src/jamidht/conversation_module.cpp
+++ b/src/jamidht/conversation_module.cpp
@@ -2858,10 +2858,10 @@ ConversationModule::getActiveCalls(const std::string& conversationId) const
     });
 }
 
-void
+std::shared_ptr<SIPCall>
 ConversationModule::call(const std::string& url,
-                         const std::shared_ptr<SIPCall>& call,
-                         std::function<void(const std::string&, const DeviceId&)>&& cb)
+                         const std::vector<libjami::MediaMap>& mediaList,
+                         std::function<void(const std::string&, const DeviceId&, const std::shared_ptr<SIPCall>&)>&& cb)
 {
     std::string conversationId = "", confId = "", uri = "", deviceId = "";
     if (url.find('/') == std::string::npos) {
@@ -2870,7 +2870,7 @@ ConversationModule::call(const std::string& url,
         auto parameters = jami::split_string(url, '/');
         if (parameters.size() != 4) {
             JAMI_ERROR("Incorrect url {:s}", url);
-            return;
+            return {};
         }
         conversationId = parameters[0];
         uri = parameters[1];
@@ -2878,36 +2878,14 @@ ConversationModule::call(const std::string& url,
         confId = parameters[3];
     }
 
-    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));
-    };
 
     auto conv = pimpl_->getConversation(conversationId);
     if (!conv)
-        return;
+        return {};
     std::unique_lock lk(conv->mtx);
     if (!conv->conversation) {
         JAMI_ERROR("Conversation {:s} not found", conversationId);
-        return;
+        return {};
     }
 
     // Check if we want to join a specific conference
@@ -2920,7 +2898,6 @@ ConversationModule::call(const std::string& url,
     auto sendCallRequest = false;
     if (confId != "") {
         sendCallRequest = true;
-        confId = confId == "0" ? Manager::instance().callFactory.getNewCallID() : confId;
         JAMI_DEBUG("Calling self, join conference");
     } else if (!activeCalls.empty()) {
         // Else, we try to join active calls
@@ -2929,7 +2906,6 @@ ConversationModule::call(const std::string& url,
         confId = ac.at("id");
         uri = ac.at("uri");
         deviceId = ac.at("device");
-        JAMI_DEBUG("Calling last active call: {:s}", callUri);
     } else if (itRdvAccount != infos.end() && itRdvDevice != infos.end()) {
         // Else, creates "to" (accountId/deviceId/conversationId/confId) and ask remote host
         sendCallRequest = true;
@@ -2938,65 +2914,100 @@ ConversationModule::call(const std::string& url,
         confId = "0";
         JAMI_DEBUG("Remote host detected. Calling {:s} on device {:s}", uri, deviceId);
     }
+    lk.unlock();
 
-    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();
-            if (confId == "0")
-                confId = call->getCallId();
-            hostConference(conversationId, confId, call->getCallId(), true);
-            return;
+    if (!sendCallRequest
+        || (uri == pimpl_->username_ && deviceId == pimpl_->deviceId_)) {
+        confId = confId == "0" ? Manager::instance().callFactory.getNewCallID() : confId;
+        // TODO attach host with media list
+        hostConference(conversationId, confId, "");
+        return {};
+    }
+
+    // Else we need to create a call
+    auto account = pimpl_->account_.lock();
+    auto& manager = Manager::instance();
+    std::shared_ptr<SIPCall> call;
+
+    // SIP allows sending empty invites, this use case is not used with Jami accounts.
+    if (not mediaList.empty()) {
+        call = manager.callFactory.newSipCall(account, Call::CallType::OUTGOING, mediaList);
+    } else {
+        JAMI_WARN("Media list is empty, setting a default list");
+        call = manager.callFactory.newSipCall(account,
+                                              Call::CallType::OUTGOING,
+                                              MediaAttribute::mediaAttributesToMediaMaps(
+                                                  account->createDefaultMediaList(account->isVideoEnabled())));
+    }
+
+    if (not call)
+        return {};
+
+    auto callUri = fmt::format("{}/{}/{}/{}", conversationId, uri, deviceId, confId);
+    account->getIceOptions([call, accountId = account->getAccountID(), callUri, uri = std::move(uri), conversationId, deviceId, cb=std::move(cb)](auto&& opts) {
+        if (call->isIceEnabled()) {
+            if (not call->createIceMediaTransport(false)
+                or not call->initIceMediaTransport(true,
+                                                   std::forward<dhtnet::IceTransportOptions>(opts))) {
+                return;
+            }
         }
+        JAMI_DEBUG("New outgoing call with {}", uri);
+        call->setPeerNumber(uri);
+        call->setPeerUri("swarm:" + uri);
+
         JAMI_DEBUG("Calling: {:s}", callUri);
-        sendCall();
-        return;
-    }
+        call->setState(Call::ConnectionState::TRYING);
+        call->setPeerNumber(callUri);
+        call->setPeerUri("rdv:" + callUri);
+        call->addStateListener([accountId, conversationId](Call::CallState call_state,
+                                                            Call::ConnectionState cnx_state,
+                                                            int) {
+            if (cnx_state == Call::ConnectionState::DISCONNECTED
+                && call_state == Call::CallState::MERROR) {
+                emitSignal<libjami::ConfigurationSignal::NeedsHost>(accountId, conversationId);
+                return true;
+            }
+            return true;
+        });
+        cb(callUri, DeviceId(deviceId), call);
+    });
 
-    // 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(), true);
+    return call;
 }
 
 void
 ConversationModule::hostConference(const std::string& conversationId,
                                    const std::string& confId,
-                                   const std::string& callId,
-                                   bool local)
+                                   const std::string& callId)
 {
     auto acc = pimpl_->account_.lock();
     if (!acc)
         return;
-    std::shared_ptr<Call> call;
-    call = acc->getCall(callId);
-    if (!call) {
-        JAMI_WARNING("No call with id {} found", callId);
-        return;
-    }
     auto conf = acc->getConference(confId);
     auto createConf = !conf;
+    std::shared_ptr<SIPCall> call;
+    if (!callId.empty()) {
+        call = std::dynamic_pointer_cast<SIPCall>(acc->getCall(callId));
+        if (!call) {
+            JAMI_WARNING("No call with id {} found", callId);
+            return;
+        }
+    }
     if (createConf) {
-        conf = std::make_shared<Conference>(acc, confId, local, call->getMediaAttributeList());
+        conf = std::make_shared<Conference>(acc, confId, callId.empty(), callId.empty()? std::vector<MediaAttribute> {} : call->getMediaAttributeList());
         acc->attach(conf);
     }
-    conf->addParticipant(callId);
+
+    if (!callId.empty())
+        conf->addParticipant(callId);
+
+    if (!createConf && callId.empty()) // TODO use mediaList
+        conf->attachLocalParticipant();
 
     if (createConf) {
-        emitSignal<libjami::CallSignal::ConferenceCreated>(acc->getAccountID(), confId);
+        emitSignal<libjami::CallSignal::ConferenceCreated>(acc->getAccountID(), conversationId, confId);
     } else {
-        conf->attachLocalParticipant();
         conf->reportMediaNegotiationStatus();
         emitSignal<libjami::CallSignal::ConferenceChanged>(acc->getAccountID(),
                                                            conf->getConfId(),
@@ -3036,7 +3047,7 @@ ConversationModule::hostConference(const std::string& conversationId,
     // 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, conv](
+        [w = pimpl_->weak(), accountUri = pimpl_->username_, confId, conversationId, conv](
             int duration) {
             auto shared = w.lock();
             if (shared) {
diff --git a/src/jamidht/conversation_module.h b/src/jamidht/conversation_module.h
index d01f9ccf16c7718bbcef39a97793c72ceb056fff..571808eeef994843eba1ebf37ef9f38e5878bf5a 100644
--- a/src/jamidht/conversation_module.h
+++ b/src/jamidht/conversation_module.h
@@ -450,16 +450,16 @@ public:
     /**
      * Call the conversation
      * @param url       Url to call (swarm:conversation or swarm:conv/account/device/conf to join)
-     * @param call      Call to use
+     * @param mediaList The media list
      * @param cb        Callback to pass which device to call (called in the same thread)
+     * @return call if a call is started, else nullptr
      */
-    void call(const std::string& url,
-              const std::shared_ptr<SIPCall>& call,
-              std::function<void(const std::string&, const DeviceId&)>&& cb);
+    std::shared_ptr<SIPCall> call(const std::string& url,
+                                const std::vector<libjami::MediaMap>& mediaList,
+                                std::function<void(const std::string&, const DeviceId&, const std::shared_ptr<SIPCall>&)>&& cb);
     void hostConference(const std::string& conversationId,
                         const std::string& confId,
-                        const std::string& callId,
-                        bool local);
+                        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/jamiaccount.cpp b/src/jamidht/jamiaccount.cpp
index 8e63f016fbec9f94afa254840ad9ecc7b4237475..0ee7fa9bd62ebe04dfe0232d73093422cf97bcfc 100644
--- a/src/jamidht/jamiaccount.cpp
+++ b/src/jamidht/jamiaccount.cpp
@@ -340,6 +340,13 @@ JamiAccount::newIncomingCall(const std::string& from,
 std::shared_ptr<Call>
 JamiAccount::newOutgoingCall(std::string_view toUrl, const std::vector<libjami::MediaMap>& mediaList)
 {
+    auto uri = Uri(toUrl);
+    if (uri.scheme() == Uri::Scheme::SWARM || uri.scheme() == Uri::Scheme::RENDEZVOUS) {
+        // NOTE: In this case newOutgoingCall can act as "unholdConference" and just attach the
+        // host to the current hosted conference. So, no call will be returned in that case.
+        return newSwarmOutgoingCallHelper(uri, mediaList);
+    }
+
     auto& manager = Manager::instance();
     std::shared_ptr<SIPCall> call;
 
@@ -357,7 +364,6 @@ JamiAccount::newOutgoingCall(std::string_view toUrl, const std::vector<libjami::
     if (not call)
         return {};
 
-    auto uri = Uri(toUrl);
     connectionManager_->getIceOptions([call, w = weak(), uri = std::move(uri)](auto&& opts) {
         if (call->isIceEnabled()) {
             if (not call->createIceMediaTransport(false)
@@ -373,10 +379,7 @@ JamiAccount::newOutgoingCall(std::string_view toUrl, const std::vector<libjami::
         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);
+        shared->newOutgoingCallHelper(call, uri);
     });
 
     return call;
@@ -419,16 +422,17 @@ JamiAccount::newOutgoingCallHelper(const std::shared_ptr<SIPCall>& call, const U
     }
 }
 
-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,
-        [this, uri, call](const std::string& accountUri, const DeviceId& deviceId) {
+std::shared_ptr<SIPCall>
+JamiAccount::newSwarmOutgoingCallHelper(const Uri& uri, const std::vector<libjami::MediaMap>& mediaList)
+{
+    JAMI_DEBUG("[Account {}] Calling conversation {}",
+             getAccountID(),
+             uri.authority());
+    return convModule()->call(
+        uri.authority(), mediaList,
+        [this, uri](const auto& accountUri, const auto& deviceId, const auto& call) {
+            if (!call)
+                return;
             std::unique_lock lkSipConn(sipConnsMtx_);
             for (auto& [key, value] : sipConns_) {
                 if (key.first != accountUri || key.second != deviceId)
@@ -469,8 +473,8 @@ JamiAccount::newSwarmOutgoingCallHelper(const std::shared_ptr<SIPCall>& call, co
 
             // 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());
+            JAMI_WARNING("[call {}] No channeled socket with this peer. Send request",
+                      call->getCallId());
             requestSIPConnection(accountUri, deviceId, type, true, call);
         });
 }
@@ -552,7 +556,7 @@ JamiAccount::handleIncomingConversationCall(const std::string& callId,
     if (isNotHosting) {
         JAMI_DEBUG("Creating conference for swarm {} with id {}", conversationId, confId);
         // Create conference and host it.
-        convModule()->hostConference(conversationId, confId, callId, false);
+        convModule()->hostConference(conversationId, confId, callId);
     } else {
         JAMI_DEBUG("Adding participant {} for swarm {} with id {}", callId, conversationId, confId);
         Manager::instance().addAudio(*call);
@@ -3152,6 +3156,12 @@ JamiAccount::getIceOptions() const noexcept
     return connectionManager_->getIceOptions();
 }
 
+void
+JamiAccount::getIceOptions(std::function<void(dhtnet::IceTransportOptions&&)> cb) const noexcept
+{
+    return connectionManager_->getIceOptions(std::move(cb));
+}
+
 dhtnet::IpAddr
 JamiAccount::getPublishedIpAddress(uint16_t family) const
 {
diff --git a/src/jamidht/jamiaccount.h b/src/jamidht/jamiaccount.h
index 1502556e3c446bec8343e1d950cfcc9a33b9cc06..fb7e2535166baa85836bb5b4e35f014b9ef76747 100644
--- a/src/jamidht/jamiaccount.h
+++ b/src/jamidht/jamiaccount.h
@@ -338,6 +338,7 @@ public:
      * Create and return ICE options.
      */
     dhtnet::IceTransportOptions getIceOptions() const noexcept override;
+    void getIceOptions(std::function<void(dhtnet::IceTransportOptions&&)> cb) const noexcept;
     dhtnet::IpAddr getPublishedIpAddress(uint16_t family = PF_UNSPEC) const override;
 
     /* Devices */
@@ -695,7 +696,7 @@ private:
     void generateDhParams();
 
     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> newSwarmOutgoingCallHelper(const Uri& uri, const std::vector<libjami::MediaMap>& mediaList);
     std::shared_ptr<SIPCall> createSubCall(const std::shared_ptr<SIPCall>& mainCall);
 
     std::filesystem::path idPath_ {};
diff --git a/src/manager.cpp b/src/manager.cpp
index 947c19b4b38ab7c379e7d8bdd1589fac96dafeeb..078aa7b1638b0ae251f8dccb61a68a8d685fb625 100644
--- a/src/manager.cpp
+++ b/src/manager.cpp
@@ -1113,7 +1113,7 @@ Manager::outgoingCall(const std::string& account_id,
     try {
         call = newOutgoingCall(trim(to), account_id, mediaList);
     } catch (const std::exception& e) {
-        JAMI_ERR("%s", e.what());
+        JAMI_ERROR("{}", e.what());
         return {};
     }
 
@@ -1452,8 +1452,12 @@ Manager::ManagerPimpl::addMainParticipant(Conference& conf)
 bool
 Manager::ManagerPimpl::hangupConference(Conference& conference)
 {
-    JAMI_DBG("Hangup conference %s", conference.getConfId().c_str());
+    JAMI_DEBUG("Hangup conference {}", conference.getConfId());
     ParticipantSet participants(conference.getParticipantList());
+    if (participants.empty()) {
+        if (auto account = conference.getAccount())
+            account->removeConference(conference.getConfId());
+    }
     for (const auto& callId : participants) {
         if (auto call = base_.getCallFromCallID(callId))
             base_.hangupCall(call->getAccountId(), callId);
@@ -1527,7 +1531,7 @@ Manager::joinParticipant(const std::string& accountId,
         mediaAttr = call2->getMediaAttributeList();
     auto conf = std::make_shared<Conference>(account, "", true, mediaAttr);
     account->attach(conf);
-    emitSignal<libjami::CallSignal::ConferenceCreated>(account->getAccountID(), conf->getConfId());
+    emitSignal<libjami::CallSignal::ConferenceCreated>(account->getAccountID(), "", conf->getConfId());
 
     // Bind calls according to their state
     pimpl_->bindCallToConference(*call1, *conf);
@@ -1585,7 +1589,7 @@ Manager::createConfFromParticipantList(const std::string& accountId,
     // Create the conference if and only if at least 2 calls have been successfully created
     if (successCounter >= 2) {
         account->attach(conf);
-        emitSignal<libjami::CallSignal::ConferenceCreated>(accountId, conf->getConfId());
+        emitSignal<libjami::CallSignal::ConferenceCreated>(accountId, "", conf->getConfId());
     }
 }
 
@@ -2619,7 +2623,7 @@ Manager::ManagerPimpl::processIncomingCall(const std::string& accountId, Call& i
             // First call
             auto conf = std::make_shared<Conference>(account, "", false);
             account->attach(conf);
-            emitSignal<libjami::CallSignal::ConferenceCreated>(account->getAccountID(),
+            emitSignal<libjami::CallSignal::ConferenceCreated>(account->getAccountID(), "",
                                                                conf->getConfId());
 
             // Bind calls according to their state
diff --git a/test/agent/src/bindings/signal.cpp b/test/agent/src/bindings/signal.cpp
index e40443e1412e18644bba46a2859a9e128b914893..3bfdf0596981247b7f9bae918383ea9f5d3e3c5c 100644
--- a/test/agent/src/bindings/signal.cpp
+++ b/test/agent/src/bindings/signal.cpp
@@ -211,7 +211,7 @@ install_signal_primitives(void*)
     add_handler<libjami::CallSignal::RecordPlaybackFilepath, const std::string&, const std::string&>(
         handlers, "record-playback-filepath");
 
-    add_handler<libjami::CallSignal::ConferenceCreated, const std::string&, const std::string&>(
+    add_handler<libjami::CallSignal::ConferenceCreated, const std::string&, const std::string&, const std::string&>(
         handlers, "conference-created");
 
     add_handler<libjami::CallSignal::ConferenceChanged,
diff --git a/test/unitTest/call/conference.cpp b/test/unitTest/call/conference.cpp
index 19d7bd2d64495c4ce455ad34b6a8ec705b0bef71..d864703e2329c01565f492e6aa93a5ec36640273 100644
--- a/test/unitTest/call/conference.cpp
+++ b/test/unitTest/call/conference.cpp
@@ -235,7 +235,7 @@ ConferenceTest::registerSignalHandlers()
             cv.notify_one();
         }));
     confHandlers.insert(libjami::exportable_callback<libjami::CallSignal::ConferenceCreated>(
-        [=](const std::string&, const std::string& conferenceId) {
+        [=](const std::string&, const std::string&, const std::string& conferenceId) {
             confId = conferenceId;
             cv.notify_one();
         }));
diff --git a/tools/jamictrl/controller.py b/tools/jamictrl/controller.py
index 760c86afe93ca2f3f53f7eae7ccffd87f551b9c9..cff1ac9ef6afbc0f7662075a246e7383d239c40e 100644
--- a/tools/jamictrl/controller.py
+++ b/tools/jamictrl/controller.py
@@ -298,10 +298,10 @@ class libjamiCtrl(Thread):
     def onConferenceCreated_cb(self):
         pass
 
-    def onConferenceCreated_callback(self, confId):
+    def onConferenceCreated_callback(self, convId, confId):
         pass
 
-    def onConferenceCreated(self, confId):
+    def onConferenceCreated(self, convId, confId):
         self.currentConfId = confId
         self.onConferenceCreated_cb()
         self.onConferenceCreated_callback(confId)