diff --git a/bin/dbus/cx.ring.Ring.CallManager.xml b/bin/dbus/cx.ring.Ring.CallManager.xml
index 76c1cf8427bddbfb73705238baa0ff7a893bfe80..7ae32d13dcdb6e43d723bac6e2429ddfde2ad350 100644
--- a/bin/dbus/cx.ring.Ring.CallManager.xml
+++ b/bin/dbus/cx.ring.Ring.CallManager.xml
@@ -842,5 +842,12 @@
             <annotation name="org.qtproject.QtDBus.QtTypeName.In1" value="VectorMapStringString"/>
             <arg type="aa{ss}" name="infos" />
         </signal>
+
+        <signal name="remoteRecordingChanged" tp:name-for-bindings="remoteRecordingChanged">
+            <tp:added version="1.0.0"/>
+            <arg type="s" name="callID" />
+            <arg type="s" name="peerNumber" />
+            <arg type="b" name="remoteRecordingState" />
+        </signal>
     </interface>
 </node>
diff --git a/bin/dbus/dbusclient.cpp b/bin/dbus/dbusclient.cpp
index a848a2fa71333161e6e04b34029d0fd101314b7f..c4057bfef0eca8aea1f76172092ae41786695256 100644
--- a/bin/dbus/dbusclient.cpp
+++ b/bin/dbus/dbusclient.cpp
@@ -177,7 +177,8 @@ DBusClient::initLibrary(int flags)
         exportable_callback<CallSignal::PeerHold>(bind(&DBusCallManager::peerHold, callM, _1, _2)),
         exportable_callback<CallSignal::AudioMuted>(bind(&DBusCallManager::audioMuted, callM, _1, _2)),
         exportable_callback<CallSignal::VideoMuted>(bind(&DBusCallManager::videoMuted, callM, _1, _2)),
-        exportable_callback<CallSignal::SmartInfo>(bind(&DBusCallManager::SmartInfo, callM, _1))
+        exportable_callback<CallSignal::SmartInfo>(bind(&DBusCallManager::SmartInfo, callM, _1)),
+        exportable_callback<CallSignal::RemoteRecordingChanged>(bind(&DBusCallManager::remoteRecordingChanged, callM, _1, _2, _3))
     };
 
     // Configuration event handlers
diff --git a/bin/jni/callmanager.i b/bin/jni/callmanager.i
index b0d5f1e9c61bb2edd475016e5d004dad1d55effc..077e6b4b8d212b2cb9fce6b1bb9e1b444c18167a 100644
--- a/bin/jni/callmanager.i
+++ b/bin/jni/callmanager.i
@@ -48,6 +48,7 @@ public:
     virtual void onConferenceInfosUpdated(const std::string& confId, const std::vector<std::map<std::string, std::string>>& infos) {}
     virtual void peerHold(const std::string& call_id, bool holding){}
     virtual void connectionUpdate(const std::string& id, int state){}
+    virtual void remoteRecordingChanged(const std::string& call_id, const std::string& peer_number, bool state){}
 };
 
 
@@ -137,4 +138,6 @@ public:
     virtual void onConferenceInfosUpdated(const std::string& confId, const std::vector<std::map<std::string, std::string>>& infos) {}
     virtual void peerHold(const std::string& call_id, bool holding){}
     virtual void connectionUpdate(const std::string& id, int state){}
+    virtual void remoteRecordingChanged(const std::string& call_id, const std::string& peer_number, bool state){}
+
 };
diff --git a/bin/jni/jni_interface.i b/bin/jni/jni_interface.i
index 13c1005fd933536d7a1d9d00ff4af2cd8f6d8d65..e68d64747d252d0dd503957d369414eeef511e25 100644
--- a/bin/jni/jni_interface.i
+++ b/bin/jni/jni_interface.i
@@ -249,7 +249,8 @@ void init(ConfigurationCallback* confM, Callback* callM, PresenceCallback* presM
         exportable_callback<CallSignal::RtcpReportReceived>(bind(&Callback::onRtcpReportReceived, callM, _1, _2)),
         exportable_callback<CallSignal::OnConferenceInfosUpdated>(bind(&Callback::onConferenceInfosUpdated, callM, _1, _2)),
         exportable_callback<CallSignal::PeerHold>(bind(&Callback::peerHold, callM, _1, _2)),
-        exportable_callback<CallSignal::ConnectionUpdate>(bind(&Callback::connectionUpdate, callM, _1, _2))
+        exportable_callback<CallSignal::ConnectionUpdate>(bind(&Callback::connectionUpdate, callM, _1, _2)),
+        exportable_callback<CallSignal::RemoteRecordingChanged>(bind(&Callback::remoteRecordingChanged, callM, _1, _2, _3))
     };
 
     // Configuration event handlers
diff --git a/configure.ac b/configure.ac
index 455f6256a2a27faa3f2e8eed05e9129c0f6e6ad9..2efeab8971d40ead044420c0b2bb10da01c19941 100644
--- a/configure.ac
+++ b/configure.ac
@@ -4,7 +4,7 @@ dnl Process this file with autoconf to produce a configure script.
 AC_PREREQ([2.65])
 AC_INIT([Jami Daemon],[9.5.0],[ring@gnu.org],[jami])
 
-AC_COPYRIGHT([[Copyright (c) Savoir-faire Linux 2004-2019]])
+AC_COPYRIGHT([[Copyright (c) Savoir-faire Linux 2004-2020]])
 AC_REVISION([$Revision$])
 
 dnl Where to find configure files
diff --git a/src/client/ring_signal.cpp b/src/client/ring_signal.cpp
index dbbb657a468db61fdee3c38b6fe9dbb9b486d1e6..c63eada36fb22ea3b130c3bd53a40bd9deef5e3d 100644
--- a/src/client/ring_signal.cpp
+++ b/src/client/ring_signal.cpp
@@ -49,6 +49,7 @@ getSignalHandlers()
         exported_callback<DRing::CallSignal::SmartInfo>(),
         exported_callback<DRing::CallSignal::ConnectionUpdate>(),
         exported_callback<DRing::CallSignal::OnConferenceInfosUpdated>(),
+        exported_callback<DRing::CallSignal::RemoteRecordingChanged>(),
 
         /* Configuration */
         exported_callback<DRing::ConfigurationSignal::VolumeChanged>(),
diff --git a/src/dring/callmanager_interface.h b/src/dring/callmanager_interface.h
index 60820a97f192d18fe121954c2961b93c22784999..41baed45c64bdb9aee50eccec466b296c337f7dc 100644
--- a/src/dring/callmanager_interface.h
+++ b/src/dring/callmanager_interface.h
@@ -228,6 +228,10 @@ struct DRING_PUBLIC CallSignal
         using cb_type = void(const std::string&,
                              const std::vector<std::map<std::string, std::string>>&);
     };
+    struct DRING_PUBLIC RemoteRecordingChanged {
+                constexpr static const char* name = "RemoteRecordingChanged";
+                using cb_type = void(const std::string&, const std::string&, bool);
+        };
 };
 
 } // namespace DRing
diff --git a/src/media/video/video_rtp_session.cpp b/src/media/video/video_rtp_session.cpp
index e7fa759d8f3ea85fe9c7a7abedcf0563358126f3..54837382a84ffbede64818c2f0b30f2b334c9d06 100644
--- a/src/media/video/video_rtp_session.cpp
+++ b/src/media/video/video_rtp_session.cpp
@@ -649,6 +649,8 @@ VideoRtpSession::initRecorder(std::shared_ptr<MediaRecorder>& rec)
             }
         }
     }
+    if (recordingStateCallback_)
+        recordingStateCallback_(true);
 }
 
 void
@@ -664,6 +666,9 @@ VideoRtpSession::deinitRecorder(std::shared_ptr<MediaRecorder>& rec)
             input->detach(ob);
         }
     }
+    if (recordingStateCallback_)
+        recordingStateCallback_(false);
+
 }
 
 void
diff --git a/src/media/video/video_rtp_session.h b/src/media/video/video_rtp_session.h
index 35dc6807b4d827232b020ce8c48d513e66dcf97f..d7f404c7f3f5ffc5c727e9ce22ee576ad30aade2 100644
--- a/src/media/video/video_rtp_session.h
+++ b/src/media/video/video_rtp_session.h
@@ -97,6 +97,9 @@ public:
     const std::string& getInput() const { return input_; }
 
     void setChangeOrientationCallback(std::function<void(int)> cb);
+    void setRecStateCallback(std::function<void(bool)> cb) {
+        recordingStateCallback_ = std::move(cb);
+    }
     void initRecorder(std::shared_ptr<MediaRecorder>& rec) override;
     void deinitRecorder(std::shared_ptr<MediaRecorder>& rec) override;
 
@@ -161,6 +164,8 @@ private:
 
     std::function<void(int)> changeOrientationCallback_;
 
+    std::function<void(bool)> recordingStateCallback_;
+
     // interval in seconds between RTCP checkings
     std::chrono::seconds rtcp_checking_interval {4};
 
diff --git a/src/sip/sipcall.cpp b/src/sip/sipcall.cpp
index e606e9f14a6e5ca691cf1bccc0291d741f7dd1fb..222aa60a8388c2cd66984f2e739f123790c0f932 100644
--- a/src/sip/sipcall.cpp
+++ b/src/sip/sipcall.cpp
@@ -335,6 +335,25 @@ SIPCall::sendSIPInfo(const char* const body, const char* const subtype)
         pjsip_dlg_send_request(inv->dlg, tdata, Manager::instance().sipVoIPLink().getModId(), NULL);
 }
 
+void
+SIPCall::updateRecState(bool state)
+{
+    std::string BODY =
+    "<?xml version=\"1.0\" encoding=\"utf-8\" ?>"
+    "<media_control><vc_primitive><to_encoder>"
+    "<recording_state=" + std::to_string(state) + "/>"
+    "</to_encoder></vc_primitive></media_control>";
+    // see https://tools.ietf.org/html/rfc5168 for XML Schema for Media Control details
+
+    JAMI_DBG("Sending recording state via SIP INFO");
+
+    try {
+        sendSIPInfo(BODY.c_str(), "media_control+xml");
+    } catch (const std::exception& e) {
+        JAMI_ERR("Error sending recording state: %s", e.what());
+    }
+}
+
 void
 SIPCall::requestKeyframe()
 {
@@ -1037,6 +1056,12 @@ SIPCall::startAllMedia()
                 this_->setVideoOrientation(angle);
         });
     });
+    videortp_->setRecStateCallback([wthis = weak()] (bool state) {
+        runOnMainThread([wthis, state] {
+            if (auto this_ = wthis.lock())
+                this_->updateRecState(state);
+        });
+    });
 #endif
 
     for (const auto& slot : slots) {
@@ -1599,4 +1624,16 @@ SIPCall::rtpSetupSuccess(MediaType type)
         toggleRecording();
 }
 
+void
+SIPCall::setRemoteRecording(bool state)
+{
+    if (state) {
+        JAMI_WARN("SIP remote recording enabled");
+        emitSignal<DRing::CallSignal::RemoteRecordingChanged>(getCallId(), getPeerNumber(), true);
+    } else {
+        JAMI_WARN("SIP remote recording disabled");
+        emitSignal<DRing::CallSignal::RemoteRecordingChanged>(getCallId(), getPeerNumber(), false);
+    }
+}
+
 } // namespace jami
diff --git a/src/sip/sipcall.h b/src/sip/sipcall.h
index 750b54083b697b148cf247b5adcfec181f5ae936..72478f81087d86fac4edb155760f3ab980013651 100644
--- a/src/sip/sipcall.h
+++ b/src/sip/sipcall.h
@@ -174,6 +174,8 @@ public: // SIP related
 
     void requestKeyframe();
 
+    void updateRecState(bool state);
+
     SIPAccountBase& getSIPAccount() const;
 
     void updateSDPFromSTUN();
@@ -233,6 +235,8 @@ public: // NOT SIP RELATED (good candidates to be moved elsewhere)
 
     void rtpSetupSuccess(MediaType type);
 
+    void setRemoteRecording(bool state);
+
 private:
     using clock = std::chrono::steady_clock;
     using time_point = clock::time_point;
diff --git a/src/sip/sipvoiplink.cpp b/src/sip/sipvoiplink.cpp
index 72cac8deaee8b257be2b91dc0b646942d6f63e94..8c0dc4ed1759040b1e783ad7fcfbdc2d4f7a343f 100644
--- a/src/sip/sipvoiplink.cpp
+++ b/src/sip/sipvoiplink.cpp
@@ -1052,6 +1052,7 @@ handleMediaControl(SIPCall& call, pjsip_msg_body* body)
         pj_strset(&control_st, (char*) body->data, body->len);
         static constexpr pj_str_t PICT_FAST_UPDATE = CONST_PJ_STR("picture_fast_update");
         static constexpr pj_str_t DEVICE_ORIENTATION = CONST_PJ_STR("device_orientation");
+        static constexpr pj_str_t RECORDING_STATE = CONST_PJ_STR("recording_state");
 
         if (pj_strstr(&control_st, &PICT_FAST_UPDATE)) {
             call.sendKeyframe();
@@ -1078,6 +1079,22 @@ handleMediaControl(SIPCall& call, pjsip_msg_body* body)
                 return true;
             }
         }
+        else if (pj_strstr(&control_st, &RECORDING_STATE)) {
+            static const std::regex REC_REGEX("recording_state=([0-1])");
+            std::string body_msg(control_st.ptr, control_st.slen);
+            std::smatch matched_pattern;
+            std::regex_search(body_msg, matched_pattern, REC_REGEX);
+
+            if (matched_pattern.ready() && !matched_pattern.empty() && matched_pattern[1].matched) {
+                try {
+                    bool state = std::stoi(matched_pattern[1]);
+                    call.setRemoteRecording(state);
+                } catch (const std::exception& e) {
+                    JAMI_WARN("Error parsing state remote recording: %s", e.what());
+                }
+                return true;
+            }
+        }
     }
 
     return false;