diff --git a/bin/dbus/cx.ring.Ring.CallManager.xml b/bin/dbus/cx.ring.Ring.CallManager.xml
index 426320592364a77ed10f4c9727ff2045706d19ec..a2e8b328df94c2a64463570604a2762ad8311652 100644
--- a/bin/dbus/cx.ring.Ring.CallManager.xml
+++ b/bin/dbus/cx.ring.Ring.CallManager.xml
@@ -25,30 +25,6 @@
             <arg type="s" name="callId" direction="out"/>
         </method>
 
-        <method name="placeCallWithDetails" tp:name-for-bindings="placeCallWithDetails">
-            <tp:added version="4.1.0"/>
-            <tp:docstring>
-              <p>This is a method in order to place a new call. This version allows to pass some call details. The call is registered with the daemon using this method.</p>
-            </tp:docstring>
-            <arg type="s" name="accountId" direction="in">
-              <tp:docstring>
-                The ID of the account with which you want to make a call. If the call is to be placed without any account by means of a SIP URI (i.e. sip:num@server), the "IP2IP_PROFILE" is passed as the accountId. For more details on accounts see the configuration manager interface.
-              </tp:docstring>
-            </arg>
-            <arg type="s" name="to" direction="in">
-              <tp:docstring>
-                If bound to a VoIP account, then the argument is the phone number. In case of calls involving "IP2IP_PROFILE", a complete SIP URI must be specified.
-              </tp:docstring>
-            </arg>
-            <annotation name="org.qtproject.QtDBus.QtTypeName.In2" value="MapStringString"/>
-            <arg type="a{ss}" name="VolatileCallDetails" direction="in">
-              <tp:docstring>
-                TBD.
-              </tp:docstring>
-            </arg>
-            <arg type="s" name="callId" direction="out"/>
-        </method>
-
         <method name="placeCallWithMedia" tp:name-for-bindings="placeCallWithMedia">
             <tp:added version="9.10.0"/>
             <tp:docstring>
diff --git a/bin/dbus/dbuscallmanager.cpp b/bin/dbus/dbuscallmanager.cpp
index 1c12c3b889af6ec1cf0f3edd5dfa1674042d72ae..9cff6a97c4324cf658b778cc437d42009a6c8f01 100644
--- a/bin/dbus/dbuscallmanager.cpp
+++ b/bin/dbus/dbuscallmanager.cpp
@@ -31,16 +31,6 @@ DBusCallManager::placeCall(const std::string& accountId, const std::string& to)
 {
     return DRing::placeCall(accountId, to);
 }
-
-auto
-DBusCallManager::placeCallWithDetails(const std::string& accountId,
-                                      const std::string& to,
-                                      const std::map<std::string, std::string>& VolatileCallDetails)
-    -> decltype(DRing::placeCall(accountId, to, VolatileCallDetails))
-{
-    return DRing::placeCall(accountId, to, VolatileCallDetails);
-}
-
 auto
 DBusCallManager::placeCallWithMedia(const std::string& accountId,
                                     const std::string& to,
diff --git a/bin/dbus/dbuscallmanager.h b/bin/dbus/dbuscallmanager.h
index dc976ad38ad482da58ae8789966a0b0deec64adf..e7d5203b35323a21970464607c10e22c41dde5d7 100644
--- a/bin/dbus/dbuscallmanager.h
+++ b/bin/dbus/dbuscallmanager.h
@@ -55,10 +55,6 @@ public:
 
     // Methods
     std::string placeCall(const std::string& accountId, const std::string& to);
-    std::string placeCallWithDetails(const std::string& accountId,
-                                     const std::string& to,
-                                     const std::map<std::string, std::string>& VolatileCallDetails);
-
     std::string placeCallWithMedia(const std::string& accountId,
                                    const std::string& to,
                                    const std::vector<std::map<std::string, std::string>>& mediaList);
diff --git a/bin/jni/callmanager.i b/bin/jni/callmanager.i
index fdc6ea7b4c719b3e066ab5d138a91f86734d48ae..2c13cc7a657d29ab71f872000719e106fcf6aaba 100644
--- a/bin/jni/callmanager.i
+++ b/bin/jni/callmanager.i
@@ -66,7 +66,6 @@ public:
 namespace DRing {
 
 /* Call related methods */
-std::string placeCall(const std::string& accountId, const std::string& to, const std::map<std::string, std::string>& volatileCallDetails);
 std::string placeCallWithMedia(const std::string& accountId,
                                const std::string& to,
                                const std::vector<std::map<std::string, std::string>>& mediaList);
diff --git a/src/account.h b/src/account.h
index 5c923a96d53835500cfbf3d7fa6728886cced34c..93d07e9cbf215ecc3686418733d491f93ded095c 100644
--- a/src/account.h
+++ b/src/account.h
@@ -146,16 +146,6 @@ public:
 
     RegistrationState getRegistrationState() const { return registrationState_; }
 
-    /**
-     * Create a new outgoing call.
-     *
-     * @param toUrl The address to call
-     * @return std::shared_ptr<Call> A pointer on the created call
-     */
-    virtual std::shared_ptr<Call> newOutgoingCall(
-        std::string_view toUrl, const std::map<std::string, std::string>& volatileCallDetails = {})
-        = 0;
-
     /**
      * Create a new outgoing call.
      *
@@ -357,13 +347,6 @@ public:
     bool isEmptyOffersEnabled() const { return emptyOffersEnabled_; }
     void enableEmptyOffers(bool enable) { emptyOffersEnabled_ = enable; }
 
-    // Enable/disable multi-stream feature.
-    // Multi-stream feature changes the callflow of the re-invite process. All
-    // clients must support this feature before it can be enabled by default.
-    // These two internal APIs allow controlling the callflow accordingly. They
-    // should be removed once the multi-stream feature is fully supported.
-    bool isMultiStreamEnabled() const { return multiStreamEnabled_; }
-    void enableMultiStream(bool enable) { multiStreamEnabled_ = enable; }
     // Check if a Daemon version (typically peer's version) satisfies the
     // minimum required version. This check is typically used to disable a
     // feature if it's not backward compatible with the peer's version.
diff --git a/src/call_factory.cpp b/src/call_factory.cpp
index cc625c8b96bad8c577ec572a1d41390f17bb89fc..e55979a37accdecef4a9031f048ef4b001a8d008 100644
--- a/src/call_factory.cpp
+++ b/src/call_factory.cpp
@@ -39,24 +39,6 @@ CallFactory::getNewCallID() const
     return random_id;
 }
 
-std::shared_ptr<SIPCall>
-CallFactory::newSipCall(const std::shared_ptr<SIPAccountBase>& account,
-                        Call::CallType type,
-                        const std::map<std::string, std::string>& details)
-{
-    if (not allowNewCall_) {
-        JAMI_WARN("Creation of new calls is not allowed");
-        return nullptr;
-    }
-
-    std::lock_guard<std::recursive_mutex> lk(callMapsMutex_);
-    auto id = getNewCallID();
-    auto call = std::make_shared<SIPCall>(account, id, type, details);
-    callMaps_[call->getLinkType()].emplace(id, call);
-    account->attach(call);
-    return call;
-}
-
 std::shared_ptr<SIPCall>
 CallFactory::newSipCall(const std::shared_ptr<SIPAccountBase>& account,
                         Call::CallType type,
diff --git a/src/call_factory.h b/src/call_factory.h
index 4acfb15c3ed45d43fccf211043d8c20822539121..c6b48fb93bd12f077618fe0cf75450eb23ec4872 100644
--- a/src/call_factory.h
+++ b/src/call_factory.h
@@ -42,17 +42,6 @@ public:
         : rand_(rand)
     {}
 
-    /**
-     * Create and register a new SIPCall instance.
-     * @param account Account used to create this call
-     * @param type The call type (incoming/outgoing)
-     * @param details Call details
-     * @return A shared pointer to the created call
-     */
-    std::shared_ptr<SIPCall> newSipCall(const std::shared_ptr<SIPAccountBase>& account,
-                                        Call::CallType type,
-                                        const std::map<std::string, std::string>& details = {});
-
     std::string getNewCallID() const;
 
     /**
diff --git a/src/client/callmanager.cpp b/src/client/callmanager.cpp
index ebc0d029c5ec39a23d5d7d830f27707d83d0d8b6..ecd6d9f7426e99c284f7457be53d3dae07bd1323 100644
--- a/src/client/callmanager.cpp
+++ b/src/client/callmanager.cpp
@@ -47,27 +47,9 @@ registerCallHandlers(const std::map<std::string, std::shared_ptr<CallbackWrapper
 std::string
 placeCall(const std::string& accountId, const std::string& to)
 {
-    // Check if a destination number is available
-    if (to.empty()) {
-        JAMI_DBG("No number entered - Call stopped");
-        return {};
-    } else {
-        return jami::Manager::instance().outgoingCall(accountId, to);
-    }
-}
-
-std::string
-placeCall(const std::string& accountId,
-          const std::string& to,
-          const std::map<std::string, std::string>& volatileCallDetails)
-{
-    // Check if a destination number is available
-    if (to.empty()) {
-        JAMI_DBG("No number entered - Call stopped");
-        return {};
-    } else {
-        return jami::Manager::instance().outgoingCall(accountId, to, {}, volatileCallDetails);
-    }
+    // TODO. Remove ASAP.
+    JAMI_WARN("This API is deprecated, use placeCallWithMedia() instead");
+    return placeCallWithMedia(accountId, to, {});
 }
 
 std::string
diff --git a/src/jami/callmanager_interface.h b/src/jami/callmanager_interface.h
index ff8738a192acf850175b83186b075ada1fa2fce2..f2a7ff3c38abc3a002270b1775233300c80bd8ac 100644
--- a/src/jami/callmanager_interface.h
+++ b/src/jami/callmanager_interface.h
@@ -40,9 +40,7 @@ namespace DRing {
 
 /* Call related methods */
 DRING_PUBLIC std::string placeCall(const std::string& accountId, const std::string& to);
-DRING_PUBLIC std::string placeCall(const std::string& accountId,
-                                   const std::string& to,
-                                   const std::map<std::string, std::string>& VolatileCallDetails);
+
 DRING_PUBLIC std::string placeCallWithMedia(
     const std::string& accountId,
     const std::string& to,
@@ -67,12 +65,6 @@ DRING_PUBLIC std::map<std::string, std::string> getCallDetails(const std::string
 DRING_PUBLIC std::vector<std::string> getCallList(const std::string& accountId);
 
 /* APIs that supports an arbitrary number of media */
-DRING_PUBLIC std::string placeCall(const std::string& accountId,
-                                   const std::string& to,
-                                   const std::vector<DRing::MediaMap>& mediaList);
-DRING_PUBLIC bool accept(const std::string& accountId,
-                         const std::string& callId,
-                         const std::vector<DRing::MediaMap>& mediaList);
 DRING_PUBLIC bool acceptWithMedia(const std::string& accountId,
                                   const std::string& callId,
                                   const std::vector<DRing::MediaMap>& mediaList);
diff --git a/src/jamidht/jamiaccount.cpp b/src/jamidht/jamiaccount.cpp
index 8e9fe1da92966a9859049a821b183029865ae138..7f90c98c68d0b9d75f7ad1686296af1554d1d56a 100644
--- a/src/jamidht/jamiaccount.cpp
+++ b/src/jamidht/jamiaccount.cpp
@@ -414,29 +414,6 @@ JamiAccount::newIncomingCall(const std::string& from,
     return nullptr;
 }
 
-std::shared_ptr<Call>
-JamiAccount::newOutgoingCall(std::string_view toUrl,
-                             const std::map<std::string, std::string>& volatileCallDetails)
-{
-    auto& manager = Manager::instance();
-    auto call = manager.callFactory.newSipCall(shared(),
-                                               Call::CallType::OUTGOING,
-                                               volatileCallDetails);
-    if (not call)
-        return {};
-
-    if (call->isIceEnabled()) {
-        call->createIceMediaTransport();
-        getIceOptions([=](auto&& opts) {
-            call->initIceMediaTransport(true, std::forward<IceTransportOptions>(opts));
-        });
-    }
-
-    newOutgoingCallHelper(call, toUrl);
-
-    return call;
-}
-
 std::shared_ptr<Call>
 JamiAccount::newOutgoingCall(std::string_view toUrl, const std::vector<DRing::MediaMap>& mediaList)
 {
@@ -444,8 +421,18 @@ JamiAccount::newOutgoingCall(std::string_view toUrl, const std::vector<DRing::Me
     JAMI_DBG() << *this << "Calling peer " << suffix;
 
     auto& manager = Manager::instance();
+    std::shared_ptr<SIPCall> call;
 
-    auto call = manager.callFactory.newSipCall(shared(), Call::CallType::OUTGOING, mediaList);
+    // SIP allows sending empty invites, this use case is not used with Jami accounts.
+    if (not mediaList.empty()) {
+        call = manager.callFactory.newSipCall(shared(), Call::CallType::OUTGOING, mediaList);
+    } else {
+        JAMI_WARN("Media list is empty, setting a default list");
+        call = manager.callFactory.newSipCall(shared(),
+                                              Call::CallType::OUTGOING,
+                                              MediaAttribute::mediaAttributesToMediaMaps(
+                                                  createDefaultMediaList(isVideoEnabled())));
+    }
 
     if (not call)
         return {};
@@ -506,15 +493,7 @@ std::shared_ptr<SIPCall>
 JamiAccount::createSubCall(const std::shared_ptr<SIPCall>& mainCall)
 {
     auto mediaList = MediaAttribute::mediaAttributesToMediaMaps(mainCall->getMediaAttributeList());
-    if (not mediaList.empty()) {
-        return Manager::instance().callFactory.newSipCall(shared(),
-                                                          Call::CallType::OUTGOING,
-                                                          mediaList);
-    } else {
-        return Manager::instance().callFactory.newSipCall(shared(),
-                                                          Call::CallType::OUTGOING,
-                                                          mainCall->getDetails());
-    }
+    return Manager::instance().callFactory.newSipCall(shared(), Call::CallType::OUTGOING, mediaList);
 }
 
 void
diff --git a/src/jamidht/jamiaccount.h b/src/jamidht/jamiaccount.h
index 02bf3972772552e1c5e117ed9aca6d9a6726e6c4..9396e94ff12a1338e4927220cd84036990118086 100644
--- a/src/jamidht/jamiaccount.h
+++ b/src/jamidht/jamiaccount.h
@@ -258,17 +258,6 @@ public:
     /* Returns true if the username and/or hostname match this account */
     MatchRank matches(std::string_view username, std::string_view hostname) const override;
 
-    /**
-     * Create outgoing SIPCall.
-     * @param[in] toUrl The address to call
-     * @return std::shared_ptr<T> A shared pointer on the created call.
-     *      The type of this instance is given in template argument.
-     *      This type can be any base class of SIPCall class (included).
-     */
-    std::shared_ptr<Call> newOutgoingCall(
-        std::string_view toUrl,
-        const std::map<std::string, std::string>& volatileCallDetails = {}) override;
-
     /**
      * Create outgoing SIPCall.
      * @param[in] toUrl The address to call
diff --git a/src/manager.cpp b/src/manager.cpp
index 870336ca653a45dbe99ce7ff1d5fdbffc8a9cfc4..86138ba947666c57eda634e1b72b9fc57cd4aaaf 100644
--- a/src/manager.cpp
+++ b/src/manager.cpp
@@ -1000,29 +1000,6 @@ Manager::unregisterAccounts()
 ///////////////////////////////////////////////////////////////////////////////
 /* Main Thread */
 
-std::string
-Manager::outgoingCall(const std::string& account_id,
-                      const std::string& to,
-                      std::shared_ptr<Conference>,
-                      const std::map<std::string, std::string>& volatileCallDetails)
-{
-    JAMI_DBG() << "try outgoing call to '" << to << "'"
-               << " with account '" << account_id << "'";
-
-    try {
-        if (auto call = newOutgoingCall(trim(to), account_id, volatileCallDetails)) {
-            stopTone();
-
-            pimpl_->switchCall(call->getCallId());
-
-            return call->getCallId();
-        }
-    } catch (const std::exception& e) {
-        JAMI_ERR("%s", e.what());
-    }
-    return {};
-}
-
 std::string
 Manager::outgoingCall(const std::string& account_id,
                       const std::string& to,
@@ -1499,7 +1476,7 @@ Manager::createConfFromParticipantList(const std::string& accountId,
         pimpl_->unsetCurrentCall();
 
         // Create call
-        auto callId = outgoingCall(account, tostr, conf);
+        auto callId = outgoingCall(account, tostr, {}, conf);
         if (callId.empty())
             continue;
 
@@ -1846,32 +1823,23 @@ Manager::incomingCall(const std::string& accountId, Call& call)
         return;
     }
 
-    if (account->isMultiStreamEnabled()) {
-        // 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());
-        }
+    // Report incoming call using "CallSignal::IncomingCallWithMedia" signal.
+    auto const& mediaList = MediaAttribute::mediaAttributesToMediaMaps(call.getMediaAttributeList());
 
-        JAMI_INFO("Incoming call %s on account %s with %lu media",
-                  call.getCallId().c_str(),
-                  accountId.c_str(),
-                  mediaList.size());
+    if (mediaList.empty()) {
+        JAMI_WARN("Incoming call %s has an empty media list", call.getCallId().c_str());
+    }
 
-        // Report the call using new API.
-        emitSignal<DRing::CallSignal::IncomingCallWithMedia>(accountId,
-                                                             call.getCallId(),
-                                                             call.getPeerDisplayName() + " " + from,
-                                                             mediaList);
-    } else {
-        JAMI_INFO("Incoming call %s on account %s", call.getCallId().c_str(), accountId.c_str());
+    JAMI_INFO("Incoming call %s on account %s with %lu media",
+              call.getCallId().c_str(),
+              accountId.c_str(),
+              mediaList.size());
 
-        emitSignal<DRing::CallSignal::IncomingCall>(accountId,
-                                                    call.getCallId(),
-                                                    call.getPeerDisplayName() + " " + from);
-    }
+    // Report the call using new API.
+    emitSignal<DRing::CallSignal::IncomingCallWithMedia>(accountId,
+                                                         call.getCallId(),
+                                                         call.getPeerDisplayName() + " " + from,
+                                                         mediaList);
 
     // Process the call.
     pimpl_->processIncomingCall(accountId, call);
@@ -3023,20 +2991,6 @@ Manager::getAudioDriver()
     return pimpl_->audiodriver_;
 }
 
-std::shared_ptr<Call>
-Manager::newOutgoingCall(std::string_view toUrl,
-                         const std::string& accountId,
-                         const std::map<std::string, std::string>& volatileCallDetails)
-{
-    auto account = getAccount(accountId);
-    if (!account or !account->isUsable()) {
-        JAMI_WARN("Account is not usable for calling");
-        return nullptr;
-    }
-
-    return account->newOutgoingCall(toUrl, volatileCallDetails);
-}
-
 std::shared_ptr<Call>
 Manager::newOutgoingCall(std::string_view toUrl,
                          const std::string& accountId,
diff --git a/src/manager.h b/src/manager.h
index b3cd17df1cab4954caf6825881af78ada4b9062b..2e7920d15b6f7847ebf5b7bc9d81493a50a70f21 100644
--- a/src/manager.h
+++ b/src/manager.h
@@ -142,19 +142,6 @@ public:
         return std::make_unique<AudioDeviceGuard>(*this, stream);
     }
 
-    /**
-     * Functions which occur with a user's action
-     * Place a new call
-     * @param accountId The account to make the call with
-     * @param to  The recipient of the call
-     * @param conf_id The conference identifier if any
-     * @return id The call ID on success, empty string otherwise
-     */
-    std::string outgoingCall(const std::string& accountId,
-                             const std::string& to,
-                             std::shared_ptr<Conference> conference = {},
-                             const std::map<std::string, std::string>& volatileCallDetails = {});
-
     /**
      * Place a new call
      * @param accountId the user's account ID
@@ -166,7 +153,7 @@ public:
      */
     std::string outgoingCall(const std::string& accountId,
                              const std::string& callee,
-                             const std::vector<DRing::MediaMap>& mediaList,
+                             const std::vector<DRing::MediaMap>& mediaList = {},
                              std::shared_ptr<Conference> conference = {});
 
     /**
@@ -783,18 +770,6 @@ public:
      * Call periodically to poll for VoIP events */
     void pollEvents();
 
-    /**
-     * Create a new outgoing call
-     * @param toUrl The address to call
-     * @param accountId account to use
-     * @return Call*  A shared pointer on a valid call.
-     * @note This function raises VoipLinkException() on errors.
-     */
-    std::shared_ptr<Call> newOutgoingCall(
-        std::string_view toUrl,
-        const std::string& accountId,
-        const std::map<std::string, std::string>& volatileCallDetails = {});
-
     /**
      * Create a new outgoing call
      * @param toUrl Destination address
diff --git a/src/sip/sipaccount.cpp b/src/sip/sipaccount.cpp
index 0c3193014890edda93da48e8b488ad9c9cb91d60..4a401efb022d8f5b855481a3168b7a6f4eec9378 100644
--- a/src/sip/sipaccount.cpp
+++ b/src/sip/sipaccount.cpp
@@ -177,10 +177,8 @@ SIPAccount::newIncomingCall(const std::string& from UNUSED,
     return call;
 }
 
-template<>
-std::shared_ptr<SIPCall>
-SIPAccount::newOutgoingCall(std::string_view toUrl,
-                            const std::map<std::string, std::string>& volatileCallDetails)
+std::shared_ptr<Call>
+SIPAccount::newOutgoingCall(std::string_view toUrl, const std::vector<DRing::MediaMap>& mediaList)
 {
     std::string to;
     int family;
@@ -188,97 +186,19 @@ SIPAccount::newOutgoingCall(std::string_view toUrl,
     JAMI_DBG() << *this << "Calling SIP peer " << toUrl;
 
     auto& manager = Manager::instance();
-    auto call = manager.callFactory.newSipCall(shared(),
-                                               Call::CallType::OUTGOING,
-                                               volatileCallDetails);
-    if (isIP2IP()) {
-        bool ipv6 = IpAddr::isIpv6(toUrl);
-        to = ipv6 ? IpAddr(toUrl).toString(false, true) : toUrl;
-        family = ipv6 ? pj_AF_INET6() : pj_AF_INET();
-
-        // TODO: resolve remote host using SIPVoIPLink::resolveSrvName
-        std::shared_ptr<SipTransport> t
-            = isTlsEnabled()
-                  ? link_.sipTransportBroker->getTlsTransport(tlsListener_,
-                                                              IpAddr(sip_utils::getHostFromUri(to)))
-                  : transport_;
-        setTransport(t);
-        call->setSipTransport(t, getContactHeader());
-
-        JAMI_DBG("New %s IP to IP call to %s", ipv6 ? "IPv6" : "IPv4", to.c_str());
-    } else {
-        to = toUrl;
-        auto contactHdr = getContactHeader();
-        call->setSipTransport(transport_, contactHdr);
-        // FIXME : for now, use the same address family as the SIP transport
-        family = pjsip_transport_type_get_af(getTransportType());
-
-        JAMI_DBG("UserAgent: New registered account call to %.*s", (int) toUrl.size(), toUrl.data());
-    }
-
-    auto toUri = getToUri(to);
-    if (call->isIceEnabled()) {
-        call->createIceMediaTransport();
-        call->initIceMediaTransport(true);
-    }
-    call->setPeerNumber(toUri);
-    call->setPeerUri(toUri);
-
-    const auto localAddress = ip_utils::getInterfaceAddr(getLocalInterface(), family);
-
-    IpAddr addrSdp;
-    if (getUPnPActive()) {
-        /* use UPnP addr, or published addr if its set */
-        addrSdp = getPublishedSameasLocal() ? getUPnPIpAddress() : getPublishedIpAddress();
-    } else {
-        addrSdp = isStunEnabled() or (not getPublishedSameasLocal()) ? getPublishedIpAddress()
-                                                                     : localAddress;
-    }
+    std::shared_ptr<SIPCall> call;
 
-    /* fallback on local address */
-    if (not addrSdp)
-        addrSdp = localAddress;
-
-    // Building the local SDP offer
-    auto& sdp = call->getSDP();
-
-    if (getPublishedSameasLocal())
-        sdp.setPublishedIP(addrSdp);
-    else
-        sdp.setPublishedIP(getPublishedAddress());
-
-    auto mediaList = createDefaultMediaList(videoEnabled_ and not call->isAudioOnly());
-    const bool created = sdp.createOffer(mediaList);
-
-    if (created) {
-        std::weak_ptr<SIPCall> weak_call = call;
-        manager.scheduler().run([this, weak_call] {
-            if (auto call = weak_call.lock()) {
-                if (not SIPStartCall(call)) {
-                    JAMI_ERR("Could not send outgoing INVITE request for new call");
-                    call->onFailure();
-                }
-            }
-            return false;
-        });
+    // SIP allows sending empty invites.
+    if (not mediaList.empty() or isEmptyOffersEnabled()) {
+        call = manager.callFactory.newSipCall(shared(), Call::CallType::OUTGOING, mediaList);
     } else {
-        throw VoipLinkException("Could not send outgoing INVITE request for new call");
+        JAMI_WARN("Media list is empty, setting a default list");
+        call = manager.callFactory.newSipCall(shared(),
+                                              Call::CallType::OUTGOING,
+                                              MediaAttribute::mediaAttributesToMediaMaps(
+                                                  createDefaultMediaList(isVideoEnabled())));
     }
 
-    return call;
-}
-
-std::shared_ptr<Call>
-SIPAccount::newOutgoingCall(std::string_view toUrl, const std::vector<DRing::MediaMap>& mediaList)
-{
-    std::string to;
-    int family;
-
-    JAMI_DBG() << *this << "Calling SIP peer " << toUrl;
-
-    auto& manager = Manager::instance();
-
-    auto call = manager.callFactory.newSipCall(shared(), Call::CallType::OUTGOING, mediaList);
     if (not call)
         throw std::runtime_error("Failed to create the call");
 
@@ -308,7 +228,7 @@ SIPAccount::newOutgoingCall(std::string_view toUrl, const std::vector<DRing::Med
 
     auto toUri = getToUri(to);
 
-    // Do not init ICE yet if the the media list is empty. This may occur
+    // Do not init ICE yet if the media list is empty. This may occur
     // if we are sending an invite with no SDP offer.
     if (call->isIceEnabled() and not mediaList.empty()) {
         call->createIceMediaTransport();
@@ -433,13 +353,6 @@ SIPAccount::getTransportSelector()
     return SIPVoIPLink::getTransportSelector(transport_->get());
 }
 
-std::shared_ptr<Call>
-SIPAccount::newOutgoingCall(std::string_view toUrl,
-                            const std::map<std::string, std::string>& volatileCallDetails)
-{
-    return newOutgoingCall<SIPCall>(toUrl, volatileCallDetails);
-}
-
 bool
 SIPAccount::SIPStartCall(std::shared_ptr<SIPCall>& call)
 {
@@ -1527,7 +1440,7 @@ SIPAccount::getToUri(const std::string& username) const
         scheme = "sip:";
 
     // Check if scheme is already specified
-    if (username.find("sip") == 0)
+    if (username.find("sip") != std::string::npos)
         scheme = "";
 
     // Check if hostname is already specified
@@ -1537,7 +1450,11 @@ SIPAccount::getToUri(const std::string& username) const
     if (not hostname.empty() and IpAddr::isIpv6(hostname))
         hostname = IpAddr(hostname).toString(false, true);
 
-    return "<" + scheme + username + (hostname.empty() ? "" : "@") + hostname + transport + ">";
+    auto ltSymbol = username.find('<') == std::string::npos ? "<" : "";
+    auto gtSymbol = username.find('>') == std::string::npos ? ">" : "";
+
+    return ltSymbol + scheme + username + (hostname.empty() ? "" : "@") + hostname + transport
+           + gtSymbol;
 }
 
 std::string
diff --git a/src/sip/sipaccount.h b/src/sip/sipaccount.h
index dcb93f0c98e93000b7c50e9630d42a98bb3434cb..05f76ac3c46dbd3c93d7975311aadc33399cdec5 100644
--- a/src/sip/sipaccount.h
+++ b/src/sip/sipaccount.h
@@ -419,31 +419,6 @@ public:
      */
     void supportPresence(int function, bool enable);
 
-    /**
-     * Implementation of Account::newOutgoingCall()
-     * Note: keep declaration before newOutgoingCall template.
-     */
-    std::shared_ptr<Call> newOutgoingCall(
-        std::string_view toUrl,
-        const std::map<std::string, std::string>& volatileCallDetails = {}) override;
-
-    /**
-     * Create outgoing SIPCall.
-     * @param[in] toUrl The address to call
-     * @return std::shared_ptr<T> A shared pointer on the created call.
-     *      The type of this instance is given in template argument.
-     *      This type can be any base class of SIPCall class (included).
-     */
-#ifndef _MSC_VER
-    template<class T = SIPCall>
-    std::shared_ptr<enable_if_base_of<T, SIPCall>> newOutgoingCall(
-        std::string_view toUrl, const std::map<std::string, std::string>& volatileCallDetails = {});
-#else
-    template<class T>
-    std::shared_ptr<T> newOutgoingCall(
-        std::string_view toUrl, const std::map<std::string, std::string>& volatileCallDetails = {});
-#endif
-
     /**
      * Create outgoing SIPCall.
      * @param[in] toUrl the address to call
diff --git a/src/sip/sipaccountbase.cpp b/src/sip/sipaccountbase.cpp
index 498561e7e50d3f1176215f6153b6b2c2a39c65c5..a322c3bfbbff51911d288ab525230ff750dc9670 100644
--- a/src/sip/sipaccountbase.cpp
+++ b/src/sip/sipaccountbase.cpp
@@ -80,15 +80,15 @@ SIPAccountBase::CreateClientDialogAndInvite(const pj_str_t* from,
                                             pjsip_inv_session** inv)
 {
     JAMI_DBG("Creating SIP dialog: \n"
-             "from: %s\n"
-             "contact: %s\n"
-             "to: %s\n",
+             "From: %s\n"
+             "Contact: %s\n"
+             "To: %s\n",
              from->ptr,
              contact->ptr,
              to->ptr);
 
     if (target) {
-        JAMI_DBG("target: %s", target->ptr);
+        JAMI_DBG("Target: %s", target->ptr);
     } else {
         JAMI_DBG("No target provided, using 'to' as target");
     }
diff --git a/src/sip/sipcall.cpp b/src/sip/sipcall.cpp
index 9179e3da9817ede92c791088a1ee26701b0b7bb2..cd670fe2ffd8edcf3c447fe056355aefecf75a42 100644
--- a/src/sip/sipcall.cpp
+++ b/src/sip/sipcall.cpp
@@ -90,40 +90,6 @@ static const std::vector<unsigned> MULTISTREAM_REQUIRED_VERSION
 
 constexpr auto DUMMY_VIDEO_STR = "dummy video session";
 
-SIPCall::SIPCall(const std::shared_ptr<SIPAccountBase>& account,
-                 const std::string& callId,
-                 Call::CallType type,
-                 const std::map<std::string, std::string>& details)
-    : Call(account, callId, type, details)
-    , sdp_(new Sdp(callId))
-    , enableIce_(account->isIceForMediaEnabled())
-    , srtpEnabled_(account->isSrtpEnabled())
-{
-    if (account->getUPnPActive())
-        upnp_.reset(new upnp::Controller());
-
-    setCallMediaLocal();
-
-    // Set the media caps.
-    sdp_->setLocalMediaCapabilities(MediaType::MEDIA_AUDIO,
-                                    account->getActiveAccountCodecInfoList(MEDIA_AUDIO));
-#ifdef ENABLE_VIDEO
-    sdp_->setLocalMediaCapabilities(MediaType::MEDIA_VIDEO,
-                                    account->getActiveAccountCodecInfoList(MEDIA_VIDEO));
-#endif
-    auto mediaAttrList = getSIPAccount()->createDefaultMediaList(getSIPAccount()->isVideoEnabled()
-                                                                     and not isAudioOnly(),
-                                                                 getState() == CallState::HOLD);
-    JAMI_DBG("[call:%s] Create a new [%s] SIP call with %lu media",
-             getCallId().c_str(),
-             type == Call::CallType::INCOMING
-                 ? "INCOMING"
-                 : (type == Call::CallType::OUTGOING ? "OUTGOING" : "MISSED"),
-             mediaAttrList.size());
-
-    initMediaStreams(mediaAttrList);
-}
-
 SIPCall::SIPCall(const std::shared_ptr<SIPAccountBase>& account,
                  const std::string& callId,
                  Call::CallType type,
@@ -2548,11 +2514,11 @@ SIPCall::handleMediaChangeRequest(const std::vector<DRing::MediaMap>& remoteMedi
         return;
     }
 
-    // If multi-stream is supported and the offered media differ from
-    // the current media, the request is reported to the client to be
-    // processed. Otherwise, we answer with the current local media.
+    // If the offered media differ from the current local media, the
+    // request is reported to the client to be processed. Otherwise,
+    // it will be processed using the current local media.
 
-    if (account->isMultiStreamEnabled() and checkMediaChangeRequest(remoteMediaList)) {
+    if (checkMediaChangeRequest(remoteMediaList)) {
         // Report the media change request.
         emitSignal<DRing::CallSignal::MediaChangeRequested>(getAccountId(),
                                                             getCallId(),
@@ -2611,76 +2577,9 @@ SIPCall::onReceiveReinvite(const pjmedia_sdp_session* offer, pjsip_rx_data* rdat
     } else {
         handleMediaChangeRequest(remoteMediaList);
     }
-
     return res;
 }
 
-int
-SIPCall::onReceiveOffer(const pjmedia_sdp_session* offer, const pjsip_rx_data* rdata)
-{
-    if (!sdp_)
-        return !PJ_SUCCESS;
-    sdp_->clearIce();
-    auto acc = getSIPAccount();
-    if (!acc) {
-        JAMI_ERR("No account detected");
-        return !PJ_SUCCESS;
-    }
-
-    JAMI_DBG("[call:%s] Received a new offer (re-invite)", getCallId().c_str());
-
-    sdp_->setReceivedOffer(offer);
-
-    // Use current media list.
-    sdp_->processIncomingOffer(getMediaAttributeList());
-
-    if (isIceEnabled() and offer != nullptr) {
-        setupIceResponse();
-    }
-
-    sdp_->startNegotiation();
-
-    pjsip_tx_data* tdata = nullptr;
-
-    if (pjsip_inv_initial_answer(inviteSession_.get(),
-                                 const_cast<pjsip_rx_data*>(rdata),
-                                 PJSIP_SC_OK,
-                                 NULL,
-                                 NULL,
-                                 &tdata)
-        != PJ_SUCCESS) {
-        JAMI_ERR("[call:%s] Could not create initial answer OK", getCallId().c_str());
-        return !PJ_SUCCESS;
-    }
-
-    // Add user-agent header
-    sip_utils::addUserAgentHeader(getSIPAccount()->getUserAgentName(), tdata);
-
-    if (pjsip_inv_answer(inviteSession_.get(), PJSIP_SC_OK, NULL, sdp_->getLocalSdpSession(), &tdata)
-        != PJ_SUCCESS) {
-        JAMI_ERR("Could not create answer OK");
-        return !PJ_SUCCESS;
-    }
-
-    if (contactHeader_.empty()) {
-        JAMI_ERR("[call:%s] Contact header is empty!", getCallId().c_str());
-        return !PJ_SUCCESS;
-    }
-
-    sip_utils::addContactHeader(contactHeader_, tdata);
-
-    if (pjsip_inv_send_msg(inviteSession_.get(), tdata) != PJ_SUCCESS) {
-        JAMI_ERR("[call:%s] Could not send msg OK", getCallId().c_str());
-        return !PJ_SUCCESS;
-    }
-
-    if (upnp_) {
-        openPortsUPnP();
-    }
-
-    return PJ_SUCCESS;
-}
-
 void
 SIPCall::onReceiveOfferIn200OK(const pjmedia_sdp_session* offer)
 {
diff --git a/src/sip/sipcall.h b/src/sip/sipcall.h
index d89f4e03b278c6005872d54511b79acc857cea67..11243c0f77309cf24ef39cc0bae926e7c7bcdf82 100644
--- a/src/sip/sipcall.h
+++ b/src/sip/sipcall.h
@@ -95,17 +95,6 @@ public:
      */
     ~SIPCall();
 
-    /**
-     * Constructor
-     * @param id The call identifier
-     * @param type The type of the call. Could be Incoming or Outgoing
-     * @param details Extra infos
-     */
-    SIPCall(const std::shared_ptr<SIPAccountBase>& account,
-            const std::string& id,
-            Call::CallType type,
-            const std::map<std::string, std::string>& details = {});
-
     /**
      * Constructor
      * @param id The call identifier
@@ -215,12 +204,6 @@ public:
      * Peer closed the connection
      */
     void onClosed();
-    /**
-     * Report a new offer from peer on a existing invite session
-     * (aka re-invite)
-     */
-    [[deprecated("Replaced by onReceiveReinvite")]] int onReceiveOffer(
-        const pjmedia_sdp_session* offer, const pjsip_rx_data* rdata);
 
     pj_status_t onReceiveReinvite(const pjmedia_sdp_session* offer, pjsip_rx_data* rdata);
     void onReceiveOfferIn200OK(const pjmedia_sdp_session* offer);
diff --git a/src/sip/sipvoiplink.cpp b/src/sip/sipvoiplink.cpp
index 98c226b2ad5380d408990794f955c8780a4351ee..eea0c2468ae0f33bc154063c5768961337e0dc62 100644
--- a/src/sip/sipvoiplink.cpp
+++ b/src/sip/sipvoiplink.cpp
@@ -1001,11 +1001,7 @@ reinvite_received_cb(pjsip_inv_session* inv, const pjmedia_sdp_session* offer, p
         return !PJ_SUCCESS;
     if (auto call = getCallFromInvite(inv)) {
         if (auto const& account = call->getAccount().lock()) {
-            if (account->isMultiStreamEnabled()) {
-                return call->onReceiveReinvite(offer, rdata);
-            } else {
-                return call->onReceiveOffer(offer, rdata);
-            }
+            return call->onReceiveReinvite(offer, rdata);
         }
     }
 
@@ -1248,7 +1244,10 @@ transferCall(SIPCall& call, const std::string& refer_to)
     const auto& callId = call.getCallId();
     JAMI_WARN("[call:%s] Trying to transfer to %s", callId.c_str(), refer_to.c_str());
     try {
-        Manager::instance().newOutgoingCall(refer_to, call.getAccountId());
+        Manager::instance().newOutgoingCall(refer_to,
+                                            call.getAccountId(),
+                                            MediaAttribute::mediaAttributesToMediaMaps(
+                                                call.getMediaAttributeList()));
         Manager::instance().hangupCall(call.getAccountId(), callId);
     } catch (const std::exception& e) {
         JAMI_ERR("[call:%s] SIP transfer failed: %s", callId.c_str(), e.what());
diff --git a/test/agent/src/bindings/call.h b/test/agent/src/bindings/call.h
index d62f4c921b8debb3ec358ece45478ea8ce34dee2..df318c033d10c2867635fdf467c4e4add6b2ab46 100644
--- a/test/agent/src/bindings/call.h
+++ b/test/agent/src/bindings/call.h
@@ -28,22 +28,9 @@
 #include "utils.h"
 
 static SCM
-place_call_binding(SCM accountID_str, SCM contact_str, SCM call_details_alist_optional)
-{
-    LOG_BINDING();
-
-    if (SCM_UNBNDP(call_details_alist_optional)) {
-        return to_guile(DRing::placeCall(from_guile(accountID_str),
-                                         from_guile(contact_str)));
-    }
-
-    return to_guile(DRing::placeCall(from_guile(accountID_str),
-                                     from_guile(contact_str),
-                                     static_cast<std::map<std::string, std::string>>(from_guile(call_details_alist_optional))));
-}
-
-static SCM
-place_call_with_media_binding(SCM accountID_str, SCM contact_str, SCM call_media_vector_alist_optional)
+place_call_with_media_binding(SCM accountID_str,
+                              SCM contact_str,
+                              SCM call_media_vector_alist_optional)
 {
     LOG_BINDING();
 
@@ -61,8 +48,7 @@ hang_up_binding(SCM accountID_str, SCM callID_str)
 {
     LOG_BINDING();
 
-    return to_guile(DRing::hangUp(from_guile(accountID_str),
-                                  from_guile(callID_str)));
+    return to_guile(DRing::hangUp(from_guile(accountID_str), from_guile(callID_str)));
 }
 
 static SCM
@@ -71,8 +57,7 @@ accept_binding(SCM accountID_str, SCM callID_str, SCM call_media_vector_alist_op
     LOG_BINDING();
 
     if (SCM_UNBNDP(call_media_vector_alist_optional)) {
-            return to_guile(DRing::accept(from_guile(accountID_str),
-                                          from_guile(callID_str)));
+        return to_guile(DRing::accept(from_guile(accountID_str), from_guile(callID_str)));
     }
 
     return to_guile(DRing::acceptWithMedia(from_guile(accountID_str),
@@ -85,8 +70,7 @@ refuse_binding(SCM accountID_str, SCM callID_str)
 {
     LOG_BINDING();
 
-    return to_guile(DRing::refuse(from_guile(accountID_str),
-                                  from_guile(callID_str)));
+    return to_guile(DRing::refuse(from_guile(accountID_str), from_guile(callID_str)));
 }
 
 static SCM
@@ -94,8 +78,7 @@ hold_binding(SCM accountID_str, SCM callID_str)
 {
     LOG_BINDING();
 
-    return to_guile(DRing::hold(from_guile(accountID_str),
-                                from_guile(callID_str)));
+    return to_guile(DRing::hold(from_guile(accountID_str), from_guile(callID_str)));
 }
 
 static SCM
@@ -103,14 +86,12 @@ unhold_binding(SCM accountID_str, SCM callID_str)
 {
     LOG_BINDING();
 
-    return to_guile(DRing::unhold(from_guile(accountID_str),
-                                  from_guile(callID_str)));
+    return to_guile(DRing::unhold(from_guile(accountID_str), from_guile(callID_str)));
 }
 
 static void
-install_call_primitives(void *)
+install_call_primitives(void*)
 {
-    define_primitive("place-call", 2, 1, 0, (void*) place_call_binding);
     define_primitive("place-call/media", 2, 1, 0, (void*) place_call_with_media_binding);
     define_primitive("hang-up", 2, 0, 0, (void*) hang_up_binding);
     define_primitive("accept", 2, 1, 0, (void*) accept_binding);
diff --git a/test/sip/test_SIP.cpp b/test/sip/test_SIP.cpp
index 66203a1da72e4960074090c070d52609ff86d8a9..23fe3f58affddde06fac4ab46a4f53618f79431a 100644
--- a/test/sip/test_SIP.cpp
+++ b/test/sip/test_SIP.cpp
@@ -144,7 +144,7 @@ test_SIP::testSimpleOutgoingIpCall()
     CPPUNIT_ASSERT(!Manager::instance().hasCurrentCall());
 
     // start a new call sending INVITE message to sipp instance
-    testcallid = Manager::instance().outgoingCall(testaccount, testcallnumber);
+    testcallid = DRing::placeCallWithMedia(testaccount, testcallnumber, {});
 
     // wait for receiving 180 and 200 message from peer
     std::this_thread::sleep_for(std::chrono::seconds(1)); // should be enough
@@ -256,7 +256,7 @@ test_SIP::testMultipleOutgoingIpCall()
         // start a user agent server waiting for a call
         sippThread("sipp -sn uas -i 127.0.0.1 -p 5061 -m " + std::to_string(numberOfCall) + " -bg");
 
-        callID[i] = Manager::instance().outgoingCall(testaccount, callNumber);
+        callID[i] = DRing::placeCallWithMedia(testaccount, callNumber, {});
         auto newCall = Manager::instance().getCallFromCallID(callID[i]);
         CPPUNIT_ASSERT(newCall);
 
@@ -302,7 +302,7 @@ test_SIP::testHoldIpCall()
 
     auto callThread = sippThread("sipp -sf sippxml/test_3.xml -i 127.0.0.1 -p 5062 -m 1 -bg");
 
-    auto testCallId = Manager::instance().outgoingCall(testAccount, testCallNumber);
+    auto testCallId = DRing::placeCallWithMedia(testAccount, testCallNumber, {});
     auto call = Manager::instance().getCallFromCallID(testCallId);
 
     std::this_thread::sleep_for(std::chrono::seconds(2));
diff --git a/test/unitTest/call/call.cpp b/test/unitTest/call/call.cpp
index b8311412e537174bb5f73d51bce95fc91bc41e43..79240fdc8e446d0ea88388cb8d8763fc80f16ea7 100644
--- a/test/unitTest/call/call.cpp
+++ b/test/unitTest/call/call.cpp
@@ -128,13 +128,13 @@ CallTest::testCall()
     DRing::registerSignalHandlers(confHandlers);
 
     JAMI_INFO("Start call between alice and Bob");
-    auto call = aliceAccount->newOutgoingCall(bobUri);
+    auto call = DRing::placeCallWithMedia(aliceId, bobUri, {});
 
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&] { return callReceived.load(); }));
 
     JAMI_INFO("Stop call between alice and Bob");
     callStopped = 0;
-    Manager::instance().hangupCall(aliceId, call->getCallId());
+    Manager::instance().hangupCall(aliceId, call);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&] { return callStopped == 2; }));
 }
 
@@ -186,12 +186,12 @@ CallTest::testCachedCall()
         cv.wait_for(lk, std::chrono::seconds(30), [&] { return successfullyConnected.load(); }));
 
     JAMI_INFO("Start call between alice and Bob");
-    auto call = aliceAccount->newOutgoingCall(bobUri);
+    auto call = DRing::placeCallWithMedia(aliceId, bobUri, {});
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&] { return callReceived.load(); }));
 
     callStopped = 0;
     JAMI_INFO("Stop call between alice and Bob");
-    Manager::instance().hangupCall(aliceId, call->getCallId());
+    Manager::instance().hangupCall(aliceId, call);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&] { return callStopped == 2; }));
 }
 
@@ -221,7 +221,7 @@ CallTest::testStopSearching()
     DRing::registerSignalHandlers(confHandlers);
 
     JAMI_INFO("Start call between alice and Bob");
-    auto call = aliceAccount->newOutgoingCall(bobUri);
+    auto call = DRing::placeCallWithMedia(aliceId, bobUri, {});
 
     // Bob not there, so we should get a SEARCHING STATUS
     JAMI_INFO("Wait OVER state");
@@ -282,7 +282,7 @@ CallTest::testDeclineMultiDevice()
     DRing::registerSignalHandlers(confHandlers);
 
     JAMI_INFO("Start call between alice and Bob");
-    auto call = aliceAccount->newOutgoingCall(bobUri);
+    auto call = DRing::placeCallWithMedia(aliceId, bobUri, {});
 
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(30), [&] {
         return callReceived == 2 && !callIdBob.empty();
diff --git a/test/unitTest/call/conference.cpp b/test/unitTest/call/conference.cpp
index b2ffe302fff62e9ecf9598cbf66fa21e56fc0cd6..c3e904641c65ec0d8cabe48101a60aaff705a3c6 100644
--- a/test/unitTest/call/conference.cpp
+++ b/test/unitTest/call/conference.cpp
@@ -165,8 +165,11 @@ ConferenceTest::registerSignalHandlers()
             }
             cv.notify_one();
         }));
-    confHandlers.insert(DRing::exportable_callback<DRing::CallSignal::StateChange>(
-        [=](const std::string& accountId, const std::string& callId, const std::string& state, signed) {
+    confHandlers.insert(
+        DRing::exportable_callback<DRing::CallSignal::StateChange>([=](const std::string& accountId,
+                                                                       const std::string& callId,
+                                                                       const std::string& state,
+                                                                       signed) {
             if (accountId == aliceId) {
                 auto details = DRing::getCallDetails(aliceId, callId);
                 if (details["PEER_NUMBER"].find(bobUri) != std::string::npos)
@@ -231,7 +234,7 @@ ConferenceTest::startConference()
     auto carlaUri = carlaAccount->getUsername();
 
     JAMI_INFO("Start call between Alice and Bob");
-    auto call1 = aliceAccount->newOutgoingCall(bobUri);
+    auto call1 = DRing::placeCallWithMedia(aliceId, bobUri, {});
     CPPUNIT_ASSERT(
         cv.wait_for(lk, std::chrono::seconds(20), [&] { return !bobCall.callId.empty(); }));
     Manager::instance().answerCall(bobId, bobCall.callId);
@@ -239,18 +242,20 @@ ConferenceTest::startConference()
         cv.wait_for(lk, std::chrono::seconds(20), [&] { return bobCall.hostState == "CURRENT"; }));
 
     JAMI_INFO("Start call between Alice and Carla");
-    auto call2 = aliceAccount->newOutgoingCall(carlaUri);
+    auto call2 = DRing::placeCallWithMedia(aliceId, carlaUri, {});
     CPPUNIT_ASSERT(
         cv.wait_for(lk, std::chrono::seconds(20), [&] { return !carlaCall.callId.empty(); }));
     Manager::instance().answerCall(carlaId, carlaCall.callId);
-    CPPUNIT_ASSERT(
-        cv.wait_for(lk, std::chrono::seconds(20), [&] { return carlaCall.hostState == "CURRENT"; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(20), [&] {
+        return carlaCall.hostState == "CURRENT";
+    }));
 
     JAMI_INFO("Start conference");
     confChanged = false;
-    Manager::instance().joinParticipant(aliceId, call1->getCallId(), aliceId, call2->getCallId());
+    Manager::instance().joinParticipant(aliceId, call1, aliceId, call2);
     // ConfChanged is the signal emitted when the 2 calls will be added to the conference
-    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(20), [&] { return !confId.empty() && confChanged; }));
+    CPPUNIT_ASSERT(
+        cv.wait_for(lk, std::chrono::seconds(20), [&] { return !confId.empty() && confChanged; }));
 }
 
 void
@@ -318,29 +323,31 @@ ConferenceTest::testAudioVideoMutedStates()
     auto carlaUri = carlaAccount->getUsername();
 
     JAMI_INFO("Start call between Alice and Bob");
-    auto call1 = aliceAccount->newOutgoingCall(bobUri);
+    auto call1Id = DRing::placeCallWithMedia(aliceId, bobUri, {});
     CPPUNIT_ASSERT(
         cv.wait_for(lk, std::chrono::seconds(20), [&] { return !bobCall.callId.empty(); }));
     Manager::instance().answerCall(bobId, bobCall.callId);
     CPPUNIT_ASSERT(
         cv.wait_for(lk, std::chrono::seconds(20), [&] { return bobCall.hostState == "CURRENT"; }));
-
+    auto call1 = aliceAccount->getCall(call1Id);
     call1->muteMedia(DRing::Media::MediaAttributeValue::AUDIO, true);
     call1->muteMedia(DRing::Media::MediaAttributeValue::VIDEO, true);
 
     JAMI_INFO("Start call between Alice and Carla");
-    auto call2 = aliceAccount->newOutgoingCall(carlaUri);
+    auto call2Id = DRing::placeCallWithMedia(aliceId, carlaUri, {});
     CPPUNIT_ASSERT(
         cv.wait_for(lk, std::chrono::seconds(20), [&] { return !carlaCall.callId.empty(); }));
     Manager::instance().answerCall(carlaId, carlaCall.callId);
-    CPPUNIT_ASSERT(
-        cv.wait_for(lk, std::chrono::seconds(20), [&] { return carlaCall.hostState == "CURRENT"; }));
+    CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(20), [&] {
+        return carlaCall.hostState == "CURRENT";
+    }));
 
+    auto call2 = aliceAccount->getCall(call2Id);
     call2->muteMedia(DRing::Media::MediaAttributeValue::AUDIO, true);
     call2->muteMedia(DRing::Media::MediaAttributeValue::VIDEO, true);
 
     JAMI_INFO("Start conference");
-    Manager::instance().joinParticipant(aliceId, call1->getCallId(), aliceId, call2->getCallId());
+    Manager::instance().joinParticipant(aliceId, call1Id, aliceId, call2Id);
     CPPUNIT_ASSERT(cv.wait_for(lk, std::chrono::seconds(20), [&] { return !confId.empty(); }));
 
     auto conf = aliceAccount->getConference(confId);
@@ -401,13 +408,13 @@ ConferenceTest::testMuteStatusAfterRemove()
     startConference();
 
     JAMI_INFO("Start call between Alice and Davi");
-    auto call1 = aliceAccount->newOutgoingCall(daviUri);
+    auto call1 = DRing::placeCallWithMedia(aliceId, daviUri, {});
     CPPUNIT_ASSERT(
         cv.wait_for(lk, std::chrono::seconds(20), [&] { return !daviCall.callId.empty(); }));
     Manager::instance().answerCall(daviId, daviCall.callId);
     CPPUNIT_ASSERT(
         cv.wait_for(lk, std::chrono::seconds(20), [&] { return daviCall.hostState == "CURRENT"; }));
-    Manager::instance().addParticipant(aliceId, call1->getCallId(), aliceId, confId);
+    Manager::instance().addParticipant(aliceId, call1, aliceId, confId);
 
     DRing::muteParticipant(aliceId, confId, daviUri, true);
     CPPUNIT_ASSERT(
@@ -418,13 +425,13 @@ ConferenceTest::testMuteStatusAfterRemove()
         cv.wait_for(lk, std::chrono::seconds(20), [&] { return daviCall.state == "OVER"; }));
     daviCall.reset();
 
-    auto call2 = aliceAccount->newOutgoingCall(daviUri);
+    auto call2 = DRing::placeCallWithMedia(aliceId, daviUri, {});
     CPPUNIT_ASSERT(
         cv.wait_for(lk, std::chrono::seconds(20), [&] { return !daviCall.callId.empty(); }));
     Manager::instance().answerCall(daviId, daviCall.callId);
     CPPUNIT_ASSERT(
         cv.wait_for(lk, std::chrono::seconds(20), [&] { return daviCall.hostState == "CURRENT"; }));
-    Manager::instance().addParticipant(aliceId, call2->getCallId(), aliceId, confId);
+    Manager::instance().addParticipant(aliceId, call2, aliceId, confId);
 
     CPPUNIT_ASSERT(
         cv.wait_for(lk, std::chrono::seconds(5), [&] { return !daviCall.moderatorMuted.load(); }));
@@ -460,13 +467,13 @@ ConferenceTest::testHandsUp()
         cv.wait_for(lk, std::chrono::seconds(5), [&] { return !bobCall.raisedHand.load(); }));
 
     JAMI_INFO("Start call between Alice and Davi");
-    auto call1 = aliceAccount->newOutgoingCall(daviUri);
+    auto call1 = DRing::placeCallWithMedia(aliceId, daviUri, {});
     CPPUNIT_ASSERT(
         cv.wait_for(lk, std::chrono::seconds(20), [&] { return !daviCall.callId.empty(); }));
     Manager::instance().answerCall(daviId, daviCall.callId);
     CPPUNIT_ASSERT(
         cv.wait_for(lk, std::chrono::seconds(20), [&] { return daviCall.hostState == "CURRENT"; }));
-    Manager::instance().addParticipant(aliceId, call1->getCallId(), aliceId, confId);
+    Manager::instance().addParticipant(aliceId, call1, aliceId, confId);
 
     DRing::raiseParticipantHand(aliceId, confId, daviUri, true);
     CPPUNIT_ASSERT(
@@ -477,13 +484,13 @@ ConferenceTest::testHandsUp()
         cv.wait_for(lk, std::chrono::seconds(20), [&] { return daviCall.state == "OVER"; }));
     daviCall.reset();
 
-    auto call2 = aliceAccount->newOutgoingCall(daviUri);
+    auto call2 = DRing::placeCallWithMedia(aliceId, daviUri, {});
     CPPUNIT_ASSERT(
         cv.wait_for(lk, std::chrono::seconds(20), [&] { return !daviCall.callId.empty(); }));
     Manager::instance().answerCall(daviId, daviCall.callId);
     CPPUNIT_ASSERT(
         cv.wait_for(lk, std::chrono::seconds(20), [&] { return daviCall.hostState == "CURRENT"; }));
-    Manager::instance().addParticipant(aliceId, call2->getCallId(), aliceId, confId);
+    Manager::instance().addParticipant(aliceId, call2, aliceId, confId);
 
     CPPUNIT_ASSERT(
         cv.wait_for(lk, std::chrono::seconds(5), [&] { return !daviCall.raisedHand.load(); }));
@@ -537,7 +544,7 @@ ConferenceTest::testJoinCallFromOtherAccount()
         cv.wait_for(lk, std::chrono::seconds(5), [&] { return !bobCall.raisedHand.load(); }));
 
     JAMI_INFO("Start call between Alice and Davi");
-    auto call1 = aliceAccount->newOutgoingCall(daviUri);
+    auto call1 = DRing::placeCallWithMedia(aliceId, daviUri, {});
     CPPUNIT_ASSERT(
         cv.wait_for(lk, std::chrono::seconds(20), [&] { return !daviCall.callId.empty(); }));
     Manager::instance().answerCall(daviId, daviCall.callId);
diff --git a/test/unitTest/ice/ice_media_cand_exchange.cpp b/test/unitTest/ice/ice_media_cand_exchange.cpp
index 0307b9ce769962f10d36c3eb39342edfab7f5ec3..705c166e4258be07314f2c7ecb4be8415920fdc1 100644
--- a/test/unitTest/ice/ice_media_cand_exchange.cpp
+++ b/test/unitTest/ice/ice_media_cand_exchange.cpp
@@ -328,8 +328,6 @@ IceMediaCandExchangeTest::setupJamiAccount(CallData& user)
     details[ConfProperties::UPNP_ENABLED] = user.upnpEnabled_ ? "true" : "false";
     details[ConfProperties::TURN::ENABLED] = user.turnEnabled_ ? "true" : "false";
     DRing::setAccountDetails(user.accountId_, details);
-
-    account->enableMultiStream(true);
 }
 
 void
@@ -359,7 +357,6 @@ IceMediaCandExchangeTest::setupSipAccount(CallData& user)
     user.alias_ = details[ConfProperties::ALIAS];
 
     account->enableIceForMedia(true);
-    account->enableMultiStream(true);
 
     user.dest_ = ip_utils::getLocalAddr(AF_INET);
     user.dest_.setPort(user.listeningPort_);
diff --git a/test/unitTest/ice/ice_sdp_parser.cpp b/test/unitTest/ice/ice_sdp_parser.cpp
index 55fad046054535a2960d18a875b771f0fbaf109b..bcf74d346a19495243abc9d5315b63900ece194b 100644
--- a/test/unitTest/ice/ice_sdp_parser.cpp
+++ b/test/unitTest/ice/ice_sdp_parser.cpp
@@ -206,9 +206,7 @@ IceSdpParsingTest::setUp()
 
     JAMI_INFO("Initialize accounts ...");
     auto aliceAccount = Manager::instance().getAccount<SIPAccount>(aliceData_.accountId_);
-    aliceAccount->enableMultiStream(true);
     auto bobAccount = Manager::instance().getAccount<SIPAccount>(bobData_.accountId_);
-    bobAccount->enableMultiStream(true);
 }
 
 void
diff --git a/test/unitTest/media_negotiation/hold_resume.cpp b/test/unitTest/media_negotiation/hold_resume.cpp
index d2644adeb484e0c50b9128460e03ff22d694112d..0c57663f5ce306669d3fdf1c06d6e4fe94b7742d 100644
--- a/test/unitTest/media_negotiation/hold_resume.cpp
+++ b/test/unitTest/media_negotiation/hold_resume.cpp
@@ -81,7 +81,6 @@ struct CallData
     std::string userName_ {};
     std::string alias_ {};
     std::string callId_ {};
-    bool enableMultiStream_ {true};
     std::vector<Signal> signals_;
     std::condition_variable cv_ {};
     std::mutex mtx_;
@@ -163,9 +162,7 @@ HoldResumeTest::setUp()
 
     JAMI_INFO("Initialize account...");
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceData_.accountId_);
-    aliceAccount->enableMultiStream(true);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobData_.accountId_);
-    bobAccount->enableMultiStream(true);
 
     wait_for_announcement_of({aliceAccount->getAccountID(), bobAccount->getAccountID()});
 }
@@ -402,7 +399,6 @@ HoldResumeTest::configureScenario(CallData& aliceData, CallData& bobData)
         auto const& account = Manager::instance().getAccount<JamiAccount>(aliceData.accountId_);
         aliceData.userName_ = account->getAccountDetails()[ConfProperties::USERNAME];
         aliceData.alias_ = account->getAccountDetails()[ConfProperties::ALIAS];
-        account->enableMultiStream(aliceData.enableMultiStream_);
     }
 
     {
@@ -410,7 +406,6 @@ HoldResumeTest::configureScenario(CallData& aliceData, CallData& bobData)
         auto const& account = Manager::instance().getAccount<JamiAccount>(bobData.accountId_);
         bobData.userName_ = account->getAccountDetails()[ConfProperties::USERNAME];
         bobData.alias_ = account->getAccountDetails()[ConfProperties::ALIAS];
-        account->enableMultiStream(bobData.enableMultiStream_);
     }
 
     std::map<std::string, std::shared_ptr<DRing::CallbackWrapperBase>> signalHandlers;
@@ -493,11 +488,7 @@ HoldResumeTest::testWithScenario(CallData& aliceData,
               bobData.accountId_.c_str());
 
     // Wait for incoming call signal.
-    if (bobData.enableMultiStream_) {
-        CPPUNIT_ASSERT(waitForSignal(bobData, DRing::CallSignal::IncomingCallWithMedia::name));
-    } else {
-        CPPUNIT_ASSERT(waitForSignal(bobData, DRing::CallSignal::IncomingCall::name));
-    }
+    CPPUNIT_ASSERT(waitForSignal(bobData, DRing::CallSignal::IncomingCallWithMedia::name));
 
     // Answer the call.
     {
diff --git a/test/unitTest/media_negotiation/media_negotiation.cpp b/test/unitTest/media_negotiation/media_negotiation.cpp
index be5bb40464ae68d81ede383dbbf9a93ec3309430..cef99d187decb6e339368775f55c82a80a71ed33 100644
--- a/test/unitTest/media_negotiation/media_negotiation.cpp
+++ b/test/unitTest/media_negotiation/media_negotiation.cpp
@@ -83,7 +83,6 @@ struct CallData
     std::string userName_ {};
     std::string alias_ {};
     std::string callId_ {};
-    bool enableMultiStream_ {true};
     std::vector<Signal> signals_;
     std::condition_variable cv_ {};
     std::mutex mtx_;
@@ -114,14 +113,12 @@ private:
     void audio_only_then_add_video();
     void audio_and_video_then_mute_audio();
     void audio_and_video_then_change_video_source();
-    void audio_only_then_add_video_but_peer_disabled_multistream();
 
     CPPUNIT_TEST_SUITE(MediaNegotiationTest);
     CPPUNIT_TEST(audio_and_video_then_mute_video);
     CPPUNIT_TEST(audio_only_then_add_video);
     CPPUNIT_TEST(audio_and_video_then_mute_audio);
     CPPUNIT_TEST(audio_and_video_then_change_video_source);
-    CPPUNIT_TEST(audio_only_then_add_video_but_peer_disabled_multistream);
     CPPUNIT_TEST_SUITE_END();
 
     // Event/Signal handlers
@@ -178,9 +175,7 @@ MediaNegotiationTest::setUp()
 
     JAMI_INFO("Initialize account...");
     auto aliceAccount = Manager::instance().getAccount<JamiAccount>(aliceData_.accountId_);
-    aliceAccount->enableMultiStream(true);
     auto bobAccount = Manager::instance().getAccount<JamiAccount>(bobData_.accountId_);
-    bobAccount->enableMultiStream(true);
 
     wait_for_announcement_of({aliceAccount->getAccountID(), bobAccount->getAccountID()});
 }
@@ -490,7 +485,6 @@ MediaNegotiationTest::configureScenario(CallData& aliceData, CallData& bobData)
         auto const& account = Manager::instance().getAccount<JamiAccount>(aliceData.accountId_);
         aliceData.userName_ = account->getAccountDetails()[ConfProperties::USERNAME];
         aliceData.alias_ = account->getAccountDetails()[ConfProperties::ALIAS];
-        account->enableMultiStream(aliceData.enableMultiStream_);
     }
 
     {
@@ -498,7 +492,6 @@ MediaNegotiationTest::configureScenario(CallData& aliceData, CallData& bobData)
         auto const& account = Manager::instance().getAccount<JamiAccount>(bobData.accountId_);
         bobData.userName_ = account->getAccountDetails()[ConfProperties::USERNAME];
         bobData.alias_ = account->getAccountDetails()[ConfProperties::ALIAS];
-        account->enableMultiStream(bobData.enableMultiStream_);
     }
 
     std::map<std::string, std::shared_ptr<DRing::CallbackWrapperBase>> signalHandlers;
@@ -594,11 +587,7 @@ MediaNegotiationTest::testWithScenario(CallData& aliceData,
               bobData.accountId_.c_str());
 
     // Wait for incoming call signal.
-    if (bobData.enableMultiStream_) {
-        CPPUNIT_ASSERT(waitForSignal(bobData, DRing::CallSignal::IncomingCallWithMedia::name));
-    } else {
-        CPPUNIT_ASSERT(waitForSignal(bobData, DRing::CallSignal::IncomingCall::name));
-    }
+    CPPUNIT_ASSERT(waitForSignal(bobData, DRing::CallSignal::IncomingCallWithMedia::name));
 
     // Answer the call.
     {
@@ -899,49 +888,6 @@ MediaNegotiationTest::audio_and_video_then_change_video_source()
     JAMI_INFO("=== End test %s ===", __FUNCTION__);
 }
 
-void
-MediaNegotiationTest::audio_only_then_add_video_but_peer_disabled_multistream()
-{
-    JAMI_INFO("=== Begin test %s ===", __FUNCTION__);
-
-    // Disable multi-stream on Bob's side
-    bobData_.enableMultiStream_ = false;
-
-    configureScenario(aliceData_, bobData_);
-
-    MediaAttribute defaultAudio(MediaType::MEDIA_AUDIO);
-    defaultAudio.label_ = "audio_0";
-    defaultAudio.enabled_ = true;
-
-    MediaAttribute defaultVideo(MediaType::MEDIA_VIDEO);
-    defaultVideo.label_ = "video_0";
-    defaultVideo.enabled_ = true;
-
-    {
-        MediaAttribute audio(defaultAudio);
-        MediaAttribute video(defaultVideo);
-
-        TestScenario scenario;
-        // First offer/answer
-        scenario.offer_.emplace_back(audio);
-        scenario.answer_.emplace_back(audio);
-
-        // Updated offer/answer
-        scenario.offerUpdate_.emplace_back(audio);
-        scenario.offerUpdate_.emplace_back(video);
-        scenario.answerUpdate_.emplace_back(audio);
-        scenario.answerUpdate_.emplace_back(video);
-        scenario.expectMediaRenegotiation_ = false;
-        scenario.expectMediaChangeRequest_ = false;
-
-        testWithScenario(aliceData_, bobData_, scenario);
-    }
-
-    DRing::unregisterSignalHandlers();
-
-    JAMI_INFO("=== End test %s ===", __FUNCTION__);
-}
-
 } // namespace test
 } // namespace jami
 
diff --git a/test/unitTest/sip_account/sip_basic_calls.cpp b/test/unitTest/sip_account/sip_basic_calls.cpp
index 784fb41da74e0991d456e7cbc95be13105707d22..fcd27d7d5a810e17a16a83dd918bcacd647c2439 100644
--- a/test/unitTest/sip_account/sip_basic_calls.cpp
+++ b/test/unitTest/sip_account/sip_basic_calls.cpp
@@ -88,6 +88,7 @@ private:
     void audio_video_test();
     void peer_answer_with_all_media_disabled();
     void hold_resume_test();
+    void blind_transfer_test();
 
     CPPUNIT_TEST_SUITE(SipBasicCallTest);
     CPPUNIT_TEST(audio_only_test);
@@ -95,6 +96,7 @@ private:
     // Test when the peer answers with all the media disabled (RTP port = 0)
     CPPUNIT_TEST(peer_answer_with_all_media_disabled);
     CPPUNIT_TEST(hold_resume_test);
+    CPPUNIT_TEST(blind_transfer_test);
     CPPUNIT_TEST_SUITE_END();
 
     // Event/Signal handlers
@@ -115,7 +117,7 @@ private:
                           std::vector<MediaAttribute> answer,
                           bool expectedToSucceed = true,
                           bool validateMedia = true);
-    static void configureTest(CallData& bob, CallData& alice);
+    static void configureTest(CallData& bob, CallData& alice, CallData& carla);
     static std::string getUserAlias(const std::string& callId);
     // Wait for a signal from the callbacks. Some signals also report the event that
     // triggered the signal a like the StateChange signal.
@@ -126,6 +128,7 @@ private:
 private:
     CallData aliceData_;
     CallData bobData_;
+    CallData carlaData_;
 };
 
 CPPUNIT_TEST_SUITE_NAMED_REGISTRATION(SipBasicCallTest, SipBasicCallTest::name());
@@ -136,6 +139,7 @@ SipBasicCallTest::setUp()
     aliceData_.listeningPort_ = 5080;
     std::map<std::string, std::string> details = DRing::getAccountTemplate("SIP");
     details[ConfProperties::TYPE] = "SIP";
+    details[ConfProperties::USERNAME] = "ALICE";
     details[ConfProperties::DISPLAYNAME] = "ALICE";
     details[ConfProperties::ALIAS] = "ALICE";
     details[ConfProperties::LOCAL_PORT] = std::to_string(aliceData_.listeningPort_);
@@ -145,17 +149,27 @@ SipBasicCallTest::setUp()
     bobData_.listeningPort_ = 5082;
     details = DRing::getAccountTemplate("SIP");
     details[ConfProperties::TYPE] = "SIP";
+    details[ConfProperties::USERNAME] = "BOB";
     details[ConfProperties::DISPLAYNAME] = "BOB";
     details[ConfProperties::ALIAS] = "BOB";
     details[ConfProperties::LOCAL_PORT] = std::to_string(bobData_.listeningPort_);
     details[ConfProperties::UPNP_ENABLED] = "false";
     bobData_.accountId_ = Manager::instance().addAccount(details);
 
+    carlaData_.listeningPort_ = 5084;
+    details = DRing::getAccountTemplate("SIP");
+    details[ConfProperties::TYPE] = "SIP";
+    details[ConfProperties::USERNAME] = "CARLA";
+    details[ConfProperties::DISPLAYNAME] = "CARLA";
+    details[ConfProperties::ALIAS] = "CARLA";
+    details[ConfProperties::LOCAL_PORT] = std::to_string(carlaData_.listeningPort_);
+    details[ConfProperties::UPNP_ENABLED] = "false";
+    carlaData_.accountId_ = Manager::instance().addAccount(details);
+
     JAMI_INFO("Initialize accounts ...");
     auto aliceAccount = Manager::instance().getAccount<SIPAccount>(aliceData_.accountId_);
-    aliceAccount->enableMultiStream(true);
     auto bobAccount = Manager::instance().getAccount<SIPAccount>(bobData_.accountId_);
-    bobAccount->enableMultiStream(true);
+    auto carlaAccount = Manager::instance().getAccount<SIPAccount>(carlaData_.accountId_);
 }
 
 void
@@ -180,7 +194,8 @@ SipBasicCallTest::tearDown()
 
     Manager::instance().removeAccount(aliceData_.accountId_, true);
     Manager::instance().removeAccount(bobData_.accountId_, true);
-    // Because cppunit is not linked with dbus, just poll if removed
+    Manager::instance().removeAccount(carlaData_.accountId_, true);
+
     CPPUNIT_ASSERT(
         cv.wait_for(lk, std::chrono::seconds(30), [&] { return accountsRemoved.load(); }));
 
@@ -356,7 +371,7 @@ SipBasicCallTest::waitForSignal(CallData& callData,
 }
 
 void
-SipBasicCallTest::configureTest(CallData& aliceData, CallData& bobData)
+SipBasicCallTest::configureTest(CallData& aliceData, CallData& bobData, CallData& carlaData)
 {
     {
         CPPUNIT_ASSERT(not aliceData.accountId_.empty());
@@ -374,6 +389,15 @@ SipBasicCallTest::configureTest(CallData& aliceData, CallData& bobData)
         bobData.alias_ = account->getAccountDetails()[ConfProperties::ALIAS];
         account->setLocalPort(bobData.listeningPort_);
     }
+#if 1
+    {
+        CPPUNIT_ASSERT(not carlaData.accountId_.empty());
+        auto const& account = Manager::instance().getAccount<SIPAccount>(carlaData.accountId_);
+        carlaData.userName_ = account->getAccountDetails()[ConfProperties::USERNAME];
+        carlaData.alias_ = account->getAccountDetails()[ConfProperties::ALIAS];
+        account->setLocalPort(carlaData.listeningPort_);
+    }
+#endif
 
     std::map<std::string, std::shared_ptr<DRing::CallbackWrapperBase>> signalHandlers;
 
@@ -388,7 +412,9 @@ SipBasicCallTest::configureTest(CallData& aliceData, CallData& bobData)
                 onIncomingCallWithMedia(accountId,
                                         callId,
                                         mediaList,
-                                        user == aliceData.alias_ ? aliceData : bobData);
+                                        user == aliceData.alias_
+                                            ? aliceData
+                                            : (user == bobData.alias_ ? bobData : carlaData));
         }));
 
     signalHandlers.insert(
@@ -401,7 +427,9 @@ SipBasicCallTest::configureTest(CallData& aliceData, CallData& bobData)
                 onCallStateChange(accountId,
                                   callId,
                                   state,
-                                  user == aliceData.alias_ ? aliceData : bobData);
+                                  user == aliceData.alias_
+                                      ? aliceData
+                                      : (user == bobData.alias_ ? bobData : carlaData));
         }));
 
     signalHandlers.insert(DRing::exportable_callback<DRing::CallSignal::MediaNegotiationStatus>(
@@ -412,7 +440,9 @@ SipBasicCallTest::configureTest(CallData& aliceData, CallData& bobData)
             if (not user.empty())
                 onMediaNegotiationStatus(callId,
                                          event,
-                                         user == aliceData.alias_ ? aliceData : bobData);
+                                         user == aliceData.alias_
+                                             ? aliceData
+                                             : (user == bobData.alias_ ? bobData : carlaData));
         }));
 
     DRing::registerSignalHandlers(signalHandlers);
@@ -424,11 +454,12 @@ SipBasicCallTest::audio_video_call(std::vector<MediaAttribute> offer,
                                    bool expectedToSucceed,
                                    bool validateMedia)
 {
-    configureTest(aliceData_, bobData_);
+    configureTest(aliceData_, bobData_, carlaData_);
 
     JAMI_INFO("=== Start a call and validate ===");
 
-    std::string bobUri = "127.0.0.1:" + std::to_string(bobData_.listeningPort_);
+    std::string bobUri = bobData_.userName_
+                         + "@127.0.0.1:" + std::to_string(bobData_.listeningPort_);
 
     aliceData_.callId_ = DRing::placeCallWithMedia(aliceData_.accountId_,
                                                    bobUri,
@@ -660,11 +691,12 @@ SipBasicCallTest::hold_resume_test()
     answer.emplace_back(audio);
 
     {
-        configureTest(aliceData_, bobData_);
+        configureTest(aliceData_, bobData_, carlaData_);
 
         JAMI_INFO("=== Start a call and validate ===");
 
-        std::string bobUri = "127.0.0.1:" + std::to_string(bobData_.listeningPort_);
+        std::string bobUri = bobData_.userName_
+                             + "@127.0.0.1:" + std::to_string(bobData_.listeningPort_);
 
         aliceData_.callId_ = DRing::placeCallWithMedia(aliceData_.accountId_,
                                                        bobUri,
@@ -821,6 +853,195 @@ SipBasicCallTest::hold_resume_test()
     }
 }
 
+void
+SipBasicCallTest::blind_transfer_test()
+{
+    // Test a "blind" (a.k.a. unattended) transfer as described in
+    // https://datatracker.ietf.org/doc/html/rfc5589
+
+    /** Call transfer scenario:
+     *
+     * Alice and Bob are in an active call
+     * Alice performs a call transfer (SIP REFER method) to Carla
+     * Bob automatically accepts the transfer request
+     * Alice ends the call with Bob
+     * Bob send a new call invite to Carla
+     * Carla accepts the call
+     * Carl ends the call
+     *
+     * Here is a simplified version of a call flow from
+     * rfc5589
+     *
+     *
+       Alice                     Bob                      Carla
+         |      REFER             |                        |
+         |----------------------->|                        |
+         |      202 Accepted      |                        |
+         |<-----------------------|                        |
+         |      BYE               |                        |
+         |<-----------------------|                        |
+         |      200 OK            |                        |
+         |----------------------->|                        |
+         |                        |     INVITE             |
+         |                        |----------------------->|
+         |                        |     200 OK             |
+         |                        |<-----------------------|
+     */
+
+    JAMI_INFO("=== Begin test %s ===", __FUNCTION__);
+
+    auto const aliceAcc = Manager::instance().getAccount<SIPAccount>(aliceData_.accountId_);
+    auto const bobAcc = Manager::instance().getAccount<SIPAccount>(bobData_.accountId_);
+    auto const carlaAcc = Manager::instance().getAccount<SIPAccount>(carlaData_.accountId_);
+
+    aliceAcc->enableIceForMedia(false);
+    bobAcc->enableIceForMedia(false);
+    carlaAcc->enableIceForMedia(false);
+
+    std::vector<MediaAttribute> offer;
+    std::vector<MediaAttribute> answer;
+
+    MediaAttribute audio(MediaType::MEDIA_AUDIO);
+    MediaAttribute video(MediaType::MEDIA_VIDEO);
+
+    audio.enabled_ = true;
+    audio.label_ = "audio_0";
+
+    // Alice's media
+    offer.emplace_back(audio);
+
+    // Bob's media
+    answer.emplace_back(audio);
+
+    configureTest(aliceData_, bobData_, carlaData_);
+
+    JAMI_INFO("=== Start a call and validate ===");
+
+    std::string bobUri = bobData_.userName_
+                         + "@127.0.0.1:" + std::to_string(bobData_.listeningPort_);
+
+    aliceData_.callId_ = DRing::placeCallWithMedia(aliceData_.accountId_,
+                                                   bobUri,
+                                                   MediaAttribute::mediaAttributesToMediaMaps(
+                                                       offer));
+
+    CPPUNIT_ASSERT(not aliceData_.callId_.empty());
+
+    JAMI_INFO("ALICE [%s] started a call with BOB [%s] and wait for answer",
+              aliceData_.accountId_.c_str(),
+              bobData_.accountId_.c_str());
+
+    // Give it some time to ring
+    std::this_thread::sleep_for(std::chrono::seconds(2));
+
+    // Wait for call to be processed.
+    CPPUNIT_ASSERT(
+        waitForSignal(aliceData_, DRing::CallSignal::StateChange::name, StateEvent::RINGING));
+
+    // Wait for incoming call signal.
+    CPPUNIT_ASSERT(waitForSignal(bobData_, DRing::CallSignal::IncomingCallWithMedia::name));
+
+    // Answer the call.
+    DRing::acceptWithMedia(bobData_.accountId_,
+                           bobData_.callId_,
+                           MediaAttribute::mediaAttributesToMediaMaps(answer));
+
+    // Wait for media negotiation complete signal.
+    CPPUNIT_ASSERT(waitForSignal(bobData_,
+                                 DRing::CallSignal::MediaNegotiationStatus::name,
+                                 DRing::Media::MediaNegotiationStatusEvents::NEGOTIATION_SUCCESS));
+
+    // Wait for the StateChange signal.
+    CPPUNIT_ASSERT(
+        waitForSignal(bobData_, DRing::CallSignal::StateChange::name, StateEvent::CURRENT));
+
+    JAMI_INFO("BOB answered the call [%s]", bobData_.callId_.c_str());
+
+    // Wait for media negotiation complete signal.
+    CPPUNIT_ASSERT(waitForSignal(aliceData_,
+                                 DRing::CallSignal::MediaNegotiationStatus::name,
+                                 DRing::Media::MediaNegotiationStatusEvents::NEGOTIATION_SUCCESS));
+
+    // Give some time to media to start and flow
+    std::this_thread::sleep_for(std::chrono::seconds(2));
+
+    // Transfer the call to Carla
+    std::string carlaUri = carlaAcc->getUsername()
+                           + "@127.0.0.1:" + std::to_string(carlaData_.listeningPort_);
+
+    DRing::transfer(aliceData_.accountId_, aliceData_.callId_, carlaUri); // TODO. Check trim
+
+    // Expect Alice's call to end.
+    CPPUNIT_ASSERT(
+        waitForSignal(aliceData_, DRing::CallSignal::StateChange::name, StateEvent::HUNGUP));
+
+    // Wait for the new call to be processed.
+    CPPUNIT_ASSERT(
+        waitForSignal(bobData_, DRing::CallSignal::StateChange::name, StateEvent::RINGING));
+
+    // Wait for incoming call signal.
+    CPPUNIT_ASSERT(waitForSignal(carlaData_, DRing::CallSignal::IncomingCallWithMedia::name));
+
+    // Let it ring
+    std::this_thread::sleep_for(std::chrono::seconds(2));
+
+    // Carla answers the call.
+    DRing::acceptWithMedia(carlaData_.accountId_,
+                           carlaData_.callId_,
+                           MediaAttribute::mediaAttributesToMediaMaps(answer));
+
+    // Wait for media negotiation complete signal.
+    CPPUNIT_ASSERT(waitForSignal(carlaData_,
+                                 DRing::CallSignal::MediaNegotiationStatus::name,
+                                 DRing::Media::MediaNegotiationStatusEvents::NEGOTIATION_SUCCESS));
+
+    // Wait for the StateChange signal.
+    CPPUNIT_ASSERT(
+        waitForSignal(carlaData_, DRing::CallSignal::StateChange::name, StateEvent::CURRENT));
+
+    JAMI_INFO("CARLA answered the call [%s]", bobData_.callId_.c_str());
+
+    // Wait for media negotiation complete signal.
+    CPPUNIT_ASSERT(waitForSignal(bobData_,
+                                 DRing::CallSignal::MediaNegotiationStatus::name,
+                                 DRing::Media::MediaNegotiationStatusEvents::NEGOTIATION_SUCCESS));
+    // Wait for the StateChange signal.
+    CPPUNIT_ASSERT(
+        waitForSignal(bobData_, DRing::CallSignal::StateChange::name, StateEvent::CURRENT));
+
+    // Validate Carla's side of media direction
+    {
+        auto call = std::static_pointer_cast<SIPCall>(
+            Manager::instance().getCallFromCallID(carlaData_.callId_));
+        auto& sdp = call->getSDP();
+        auto mediaStreams = sdp.getMediaSlots();
+        for (auto const& media : mediaStreams) {
+            CPPUNIT_ASSERT_EQUAL(media.first.direction_, MediaDirection::SENDRECV);
+            CPPUNIT_ASSERT_EQUAL(media.second.direction_, MediaDirection::SENDRECV);
+        }
+    }
+
+    // NOTE:
+    // For now, we dont validate Bob's media because currently
+    // test does not update BOB's call ID (bobData_.callId_
+    // still point to the first call).
+    // It seems there is no easy way to get the ID of the new call
+    // made by Bob to Carla.
+
+    // Give some time to media to start and flow
+    std::this_thread::sleep_for(std::chrono::seconds(2));
+
+    // Bob hang-up.
+    JAMI_INFO("Hang up CARLA's call and wait for CARLA to hang up");
+    DRing::hangUp(carlaData_.accountId_, carlaData_.callId_);
+
+    // Expect end call on Carla's side.
+    CPPUNIT_ASSERT(
+        waitForSignal(carlaData_, DRing::CallSignal::StateChange::name, StateEvent::HUNGUP));
+
+    JAMI_INFO("Calls normally ended on both sides");
+}
+
 } // namespace test
 } // namespace jami
 
diff --git a/test/unitTest/sip_account/sip_empty_offer.cpp b/test/unitTest/sip_account/sip_empty_offer.cpp
index 907b57338a09e162a06a00cb8672d0d5bde4831c..0851f7d7f9d98ff767a3d77bccee863dc33f39c0 100644
--- a/test/unitTest/sip_account/sip_empty_offer.cpp
+++ b/test/unitTest/sip_account/sip_empty_offer.cpp
@@ -142,9 +142,7 @@ SipEmptyOfferTest::setUp()
 
     JAMI_INFO("Initialize accounts ...");
     auto aliceAccount = Manager::instance().getAccount<SIPAccount>(aliceData_.accountId_);
-    aliceAccount->enableMultiStream(true);
     auto bobAccount = Manager::instance().getAccount<SIPAccount>(bobData_.accountId_);
-    bobAccount->enableMultiStream(true);
 }
 
 void
diff --git a/test/unitTest/sip_account/sip_srtp.cpp b/test/unitTest/sip_account/sip_srtp.cpp
index 191ad8dcbf0c2831a290b8d2a33fd1046c0da497..0a174b72630ac5751bc18af63a9192aaea7c84e9 100644
--- a/test/unitTest/sip_account/sip_srtp.cpp
+++ b/test/unitTest/sip_account/sip_srtp.cpp
@@ -147,9 +147,7 @@ SipSrtpTest::setUp()
 
     JAMI_INFO("Initialize accounts ...");
     auto aliceAccount = Manager::instance().getAccount<SIPAccount>(aliceData_.accountId_);
-    aliceAccount->enableMultiStream(true);
     auto bobAccount = Manager::instance().getAccount<SIPAccount>(bobData_.accountId_);
-    bobAccount->enableMultiStream(true);
 }
 
 void