diff --git a/bin/dbus/cx.ring.Ring.CallManager.xml b/bin/dbus/cx.ring.Ring.CallManager.xml
index 5d95f18557e6a2962416f295c1b57b7bffe45871..e87142360738fd76e03236599b633426602c15b4 100644
--- a/bin/dbus/cx.ring.Ring.CallManager.xml
+++ b/bin/dbus/cx.ring.Ring.CallManager.xml
@@ -1026,7 +1026,15 @@
               <p>Report mediation negotation status.</p>
             </tp:docstring>
             <arg type="s" name="callID" />
-            <arg type="s" name="event" />
+            <arg type="s" name="event" >
+              <tp:docstring>
+                The acceptable states are:
+                <ul>
+                  <li>NEGOTIATION_SUCCESS: Medias are ready</li>
+                  <li>NEGOTIATION_FAIL: Medias negotion failed</li>
+                </ul>
+              </tp:docstring>
+            </arg>
         </signal>
     </interface>
 </node>
diff --git a/compat/msvc/config.h b/compat/msvc/config.h
index ed4b174ae857cf006ffef694aabd40714625660f..186b5506755e46920c32c571f3fb351eacc849cc 100644
--- a/compat/msvc/config.h
+++ b/compat/msvc/config.h
@@ -152,10 +152,10 @@ systems. This function is required for `alloca.c' support on those systems.
 #define PACKAGE_BUGREPORT "jami@lists.savoirfairelinux.net"
 
 /* Define to the full name of this package. */
-#define PACKAGE_NAME "Jami"
+#define PACKAGE_NAME "Jami Daemon"
 
 /* Define to the full name and version of this package. */
-#define PACKAGE_STRING "Jami"
+#define PACKAGE_STRING "Jami Daemon 10.0.2"
 
 /* Define to the one symbol short name of this package. */
 #define PACKAGE_TARNAME "jami"
@@ -164,7 +164,7 @@ systems. This function is required for `alloca.c' support on those systems.
 #define PACKAGE_URL ""
 
 /* Define to the version of this package. */
-#define PACKAGE_VERSION "2.3.0"
+#define PACKAGE_VERSION "10.0.2"
 
 /* Define to necessary symbol if this constant uses a non-standard name on
 your system. */
@@ -201,7 +201,7 @@ STACK_DIRECTION = 0 => direction of growth unknown */
 #undef _MBCS
 
 /* Version number of package */
-#define VERSION "2.3.0"
+#define VERSION "10.0.2"
 
 // UWP compatibility
 #define PROGSHAREDIR ""
diff --git a/configure.ac b/configure.ac
index db6e14710c94b358665164a79f2ec14bd8de5de3..757ca2eae3cf011dfc323a4f336d4391ff9cca0e 100644
--- a/configure.ac
+++ b/configure.ac
@@ -2,7 +2,7 @@ dnl Jami - configure.ac
 
 dnl Process this file with autoconf to produce a configure script.
 AC_PREREQ([2.69])
-AC_INIT([Jami Daemon],[10.0.1],[jami@gnu.org],[jami])
+AC_INIT([Jami Daemon],[10.0.2],[jami@gnu.org],[jami])
 
 dnl Clear the implicit flags that default to '-g -O2', otherwise they
 dnl take precedence over the values we set via the
diff --git a/meson.build b/meson.build
index 530a14feb9480b320893637add6834be1d21a65e..109776557da65aca7bf7659aca9e3f386cfe9aff 100644
--- a/meson.build
+++ b/meson.build
@@ -1,5 +1,5 @@
 project('jami-daemon', ['c', 'cpp'],
-        version: '10.0.1',
+        version: '10.0.2',
         license: 'GPL3+',
         default_options: ['cpp_std=gnu++17', 'buildtype=debugoptimized'],
         meson_version:'>= 0.54'
diff --git a/src/account.cpp b/src/account.cpp
index 88c161b307783d1741f0189189ca88db46aba73d..edbfda705af2cb08602a1d7463ee8eb0f407c0b5 100644
--- a/src/account.cpp
+++ b/src/account.cpp
@@ -709,4 +709,18 @@ Account::removeDefaultModerator(const std::string& uri)
     defaultModerators_.erase(uri);
 }
 
+bool
+Account::meetMinimumRequiredVersion(const std::vector<unsigned>& jamiVersion,
+                                    const std::vector<unsigned>& minRequiredVersion)
+{
+    if (jamiVersion.size() != 3 or minRequiredVersion.size() != 3) {
+        JAMI_ERR("Unexpected vector size");
+        return false;
+    }
+
+    return jamiVersion[0] > minRequiredVersion[0]
+           or (jamiVersion[0] == minRequiredVersion[0] and jamiVersion[1] > minRequiredVersion[1])
+           or (jamiVersion[0] == minRequiredVersion[0] and jamiVersion[1] == minRequiredVersion[1]
+               and jamiVersion[2] >= minRequiredVersion[2]);
+}
 } // namespace jami
diff --git a/src/account.h b/src/account.h
index afc086763a0b86172279353e84bae29846af9b33..b3ec6e267bfabc223d53ec1a549016c110f44721 100644
--- a/src/account.h
+++ b/src/account.h
@@ -353,6 +353,11 @@ public:
     // 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.
+    static bool meetMinimumRequiredVersion(const std::vector<unsigned>& jamiVersion,
+                                           const std::vector<unsigned>& minRequiredVersion);
 
     // Enable/disable compliancy with RFC-5245 for component IDs format.
     // The ICE component IDs are enumerated relative to the SDP session,
diff --git a/src/sip/sip_utils.cpp b/src/sip/sip_utils.cpp
index da79ad6ca55f153b275b362692c34ed9b2b1108d..6a2c24e9b2263df0abaa24a6114b856093e53622 100644
--- a/src/sip/sip_utils.cpp
+++ b/src/sip/sip_utils.cpp
@@ -205,20 +205,47 @@ addUserAgentHeader(const std::string& userAgent, pjsip_tx_data* tdata)
         pjsip_user_agent_hdr_create(tdata->pool, &STR_USER_AGENT, &pjUserAgent));
 
     if (hdr != nullptr) {
-        JAMI_DBG("Add header to SIP message: \"%.*s: %.*s\"", (int) hdr->name.slen, hdr->name.ptr,
-           (int) pjUserAgent.slen, pjUserAgent.ptr);
+        JAMI_DBG("Add header to SIP message: \"%.*s: %.*s\"",
+                 (int) hdr->name.slen,
+                 hdr->name.ptr,
+                 (int) pjUserAgent.slen,
+                 pjUserAgent.ptr);
         pjsip_msg_add_hdr(tdata->msg, hdr);
     }
 }
 
-void logMessageHeaders(const pjsip_hdr* hdr_list)
+std::string
+getPeerUserAgent(const pjsip_rx_data* rdata)
+{
+    if (rdata == nullptr or rdata->msg_info.msg == nullptr) {
+        JAMI_ERR("Unexpected null poiter!");
+        return {};
+    }
+
+    std::string peerUa {};
+    pjsip_generic_string_hdr* uaHdr {nullptr};
+    constexpr auto USER_AGENT_STR = CONST_PJ_STR("User-Agent");
+
+    uaHdr = (pjsip_generic_string_hdr*) pjsip_msg_find_hdr_by_name(rdata->msg_info.msg,
+                                                                   &USER_AGENT_STR,
+                                                                   nullptr);
+
+    if (uaHdr) {
+        peerUa = {uaHdr->hvalue.ptr, static_cast<size_t>(uaHdr->hvalue.slen)};
+    }
+
+    return peerUa;
+}
+
+void
+logMessageHeaders(const pjsip_hdr* hdr_list)
 {
     const pjsip_hdr* hdr = hdr_list->next;
     const pjsip_hdr* end = hdr_list;
     std::string msgHdrStr("Message headers:\n");
     for (; hdr != end; hdr = hdr->next) {
         char buf[1024];
-        int size = pjsip_hdr_print_on((void*)hdr, buf, sizeof(buf));
+        int size = pjsip_hdr_print_on((void*) hdr, buf, sizeof(buf));
         if (size > 0) {
             msgHdrStr.append(buf, size);
             msgHdrStr.push_back('\n');
@@ -233,7 +260,7 @@ sip_strerror(pj_status_t code)
 {
     char err_msg[PJ_ERR_MSG_SIZE];
     auto ret = pj_strerror(code, err_msg, sizeof err_msg);
-    return std::string {ret.ptr, ret.ptr+ret.slen};
+    return std::string {ret.ptr, ret.ptr + ret.slen};
 }
 
 void
diff --git a/src/sip/sip_utils.h b/src/sip/sip_utils.h
index b6bea44ef3f1db61f0b7905eac3805e68d2f1d6b..0828c405bd267d5a1b36583e04f4424997fcae0a 100644
--- a/src/sip/sip_utils.h
+++ b/src/sip/sip_utils.h
@@ -98,6 +98,7 @@ std::string_view getHostFromUri(std::string_view sipUri);
 
 void addContactHeader(const pj_str_t* contactStr, pjsip_tx_data* tdata);
 void addUserAgentHeader(const std::string& userAgent, pjsip_tx_data* tdata);
+std::string getPeerUserAgent(const pjsip_rx_data* rdata);
 void logMessageHeaders(const pjsip_hdr* hdr_list);
 
 std::string sip_strerror(pj_status_t code);
diff --git a/src/sip/sipcall.cpp b/src/sip/sipcall.cpp
index 21b7360b787162793ec593146e4b1f22cd3a7b13..9d67ac53f2b3f1861ca51a9ce015dbad82d78544 100644
--- a/src/sip/sipcall.cpp
+++ b/src/sip/sipcall.cpp
@@ -118,6 +118,7 @@ SIPCall::SIPCall(const std::shared_ptr<SIPAccountBase>& account,
                  Call::CallType type,
                  const std::vector<MediaAttribute>& mediaAttrList)
     : Call(account, callId, type)
+    , peerSupportMultiStream_(false)
     , sdp_(new Sdp(callId))
 {
     if (account->getUPnPActive())
@@ -1454,6 +1455,77 @@ SIPCall::sendKeyframe()
 #endif
 }
 
+void
+SIPCall::setPeerUaVersion(const std::string& ua)
+{
+    if (peerUserAgent_ == ua or ua.empty()) {
+        // Silently ignore if it did not change or empty.
+        return;
+    }
+
+    if (peerUserAgent_.empty()) {
+        JAMI_DBG("[call:%s] Set peer's User-Agent to [%s]", getCallId().c_str(), ua.c_str());
+    } else if (not peerUserAgent_.empty()) {
+        // Unlikely, but should be handled since we dont have control over the peer.
+        // Even if it's unexpected, we still try to parse the UA version.
+        JAMI_WARN("[call:%s] Peer's User-Agent unexpectedely changed from [%s] to [%s]",
+                  getCallId().c_str(),
+                  peerUserAgent_.c_str(),
+                  ua.c_str());
+    }
+
+    peerUserAgent_ = ua;
+
+    // User-agent parsing
+    constexpr std::string_view PACK_NAME(PACKAGE_NAME " ");
+    std::string_view s {peerUserAgent_};
+    std::string version;
+
+    auto pos = s.find(PACK_NAME);
+    if (pos == std::string::npos) {
+        // Must have the expected package name.
+        JAMI_WARN("Could not find the expected package name in peer's User-Agent");
+        return;
+    }
+
+    s = s.substr(pos + PACK_NAME.length());
+
+    // Unstable (un-released) versions has a hiphen + commit Id after
+    // the version number. Find the commit Id if any, and ignore it.
+    pos = s.find("-");
+    if (pos != std::string::npos) {
+        // Get the version and ignore the commit ID.
+        version = s.substr(0, pos);
+    } else {
+        // Extract the version number.
+        pos = s.find(" ");
+        if (pos != std::string::npos) {
+            version = s.substr(0, pos);
+        }
+    }
+
+    if (version.empty()) {
+        JAMI_DBG("[call:%s] Could not parse peer's Jami version", getCallId().c_str());
+    }
+
+    auto peerJamiVersion = split_string_to_unsigned(version, '.');
+    if (peerJamiVersion.size() != 3) {
+        JAMI_WARN("Could not parse peer's Jami version");
+        return;
+    }
+
+    // Check if peer's version is at least 10.0.2 to enable multi-stream.
+    peerSupportMultiStream_ = Account::meetMinimumRequiredVersion(peerJamiVersion, {10, 0, 2});
+
+    if (not peerSupportMultiStream_) {
+        JAMI_DBG("Peer's version [%u.%u.%u] does not support multi-stream. Min required version: "
+                 "[10.0.2]",
+                 peerJamiVersion[0],
+                 peerJamiVersion[1],
+                 peerJamiVersion[2]);
+    }
+}
+
 void
 SIPCall::onPeerRinging()
 {
@@ -1970,6 +2042,15 @@ SIPCall::isReinviteRequired(const std::vector<MediaAttribute>& mediaAttrList)
 bool
 SIPCall::requestMediaChange(const std::vector<MediaAttribute>& mediaAttrList)
 {
+    // If the peer does not support multi-stream and the size of the new
+    // media list is different from the current media list, the media
+    // change request will be ignored.
+    if (not peerSupportMultiStream_ and rtpStreams_.size() != mediaAttrList.size()) {
+        JAMI_WARN("[call:%s] Peer does not support multi-stream. Media change request ignored",
+                  getCallId().c_str());
+        return false;
+    }
+
     JAMI_DBG("[call:%s] Requesting media change. List of new media:", getCallId().c_str());
 
     unsigned idx = 0;
@@ -2611,6 +2692,9 @@ SIPCall::merge(Call& call)
         tmpMediaTransport_ = std::move(subcall.tmpMediaTransport_);
     }
 
+    peerUserAgent_ = subcall.peerUserAgent_;
+    peerSupportMultiStream_ = subcall.peerSupportMultiStream_;
+
     Call::merge(subcall);
     startIceMedia();
 }
diff --git a/src/sip/sipcall.h b/src/sip/sipcall.h
index b0a77b7f70842e3dd48e4624ee08e9719f81b688..b019b529fc27f81de00f5a938e0f72cf4cd37d5e 100644
--- a/src/sip/sipcall.h
+++ b/src/sip/sipcall.h
@@ -165,6 +165,11 @@ public:
 
     void monitor() const override;
 
+    /**
+     * Set peer's User-Agent found in the message header
+     */
+    void setPeerUaVersion(const std::string& ua);
+
     /**
      * Return the SDP's manager of this call
      */
@@ -382,6 +387,11 @@ private:
     }
     inline std::weak_ptr<SIPCall> weak() { return std::weak_ptr<SIPCall>(shared()); }
 
+    // Peer's User-Agent.
+    std::string peerUserAgent_ {};
+    // Flag to indicate the the peer's Daemon version support multi-stream.
+    bool peerSupportMultiStream_ {false};
+
     // Vector holding the current RTP sessions.
     std::vector<RtpStream> rtpStreams_;
 
diff --git a/src/sip/sipvoiplink.cpp b/src/sip/sipvoiplink.cpp
index 039f4b599c0e9ff3ba68151bebab74a14ccb26b4..60ccd5a068837b816acf22d314e2585aa4e45080 100644
--- a/src/sip/sipvoiplink.cpp
+++ b/src/sip/sipvoiplink.cpp
@@ -397,6 +397,9 @@ transaction_request_cb(pjsip_rx_data* rdata)
     if (!call) {
         return PJ_FALSE;
     }
+
+    call->setPeerUaVersion(sip_utils::getPeerUserAgent(rdata));
+
     call->setTransport(transport);
 
     // JAMI_DBG("transaction_request_cb viaHostname %s toUsername %s addrToUse %s addrSdp %s
@@ -866,6 +869,10 @@ getCallFromInvite(pjsip_inv_session* inv)
 static void
 invite_session_state_changed_cb(pjsip_inv_session* inv, pjsip_event* ev)
 {
+    if (inv == nullptr or ev == nullptr) {
+        throw VoipLinkException("unexpected null pointer");
+    }
+
     auto call = getCallFromInvite(inv);
     if (not call)
         return;
@@ -907,6 +914,12 @@ invite_session_state_changed_cb(pjsip_inv_session* inv, pjsip_event* ev)
                  inv->cause);
     }
 
+    if (ev->type == PJSIP_EVENT_RX_MSG) {
+        call->setPeerUaVersion(sip_utils::getPeerUserAgent(ev->body.rx_msg.rdata));
+    } else if (ev->type == PJSIP_EVENT_TSX_STATE and ev->body.tsx_state.type == PJSIP_EVENT_RX_MSG) {
+        call->setPeerUaVersion(sip_utils::getPeerUserAgent(ev->body.tsx_state.src.rdata));
+    }
+
     switch (inv->state) {
     case PJSIP_INV_STATE_EARLY:
         if (status_code == PJSIP_SC_RINGING)