diff --git a/daemon/src/call.h b/daemon/src/call.h
index 2c1fae99e7de678c0b88116eb8b7a39179868ede..cab2800ea38c0e6a732e1f16e39cf2437e1fab6b 100644
--- a/daemon/src/call.h
+++ b/daemon/src/call.h
@@ -283,6 +283,12 @@ class Call : public Recordable {
          */
         virtual void peerHungup() = 0;
 
+        /**
+         * Send DTMF
+         * @param code  The char code
+         */
+        virtual void carryingDTMFdigits(char code) = 0;
+
     private:
         bool validTransition(CallState newState);
 
diff --git a/daemon/src/iax/iaxcall.cpp b/daemon/src/iax/iaxcall.cpp
index 801962a0703c66df79e23f7e5a4e5e2393af5e13..76ba15605605bfbfa01ef931604cee420bf04392 100644
--- a/daemon/src/iax/iaxcall.cpp
+++ b/daemon/src/iax/iaxcall.cpp
@@ -219,3 +219,10 @@ IAXCall::peerHungup()
 
     link_->removeIaxCall(getCallId());
 }
+
+void
+IAXCall::carryingDTMFdigits(char code)
+{
+    std::lock_guard<std::mutex> lock(IAXVoIPLink::mutexIAX);
+    iax_send_dtmf(session, code);
+}
diff --git a/daemon/src/iax/iaxcall.h b/daemon/src/iax/iaxcall.h
index 36b2048cc0bf77da9b21f5a18b0a4fd3b8ac6e77..84101fb98a9fdc9d645557a62b64142006f53cf5 100644
--- a/daemon/src/iax/iaxcall.h
+++ b/daemon/src/iax/iaxcall.h
@@ -96,6 +96,8 @@ class IAXCall : public Call {
 
         void peerHungup();
 
+        void carryingDTMFdigits(char code);
+
     private:
         NON_COPYABLE(IAXCall);
 
diff --git a/daemon/src/iax/iaxvoiplink.cpp b/daemon/src/iax/iaxvoiplink.cpp
index 12d0b401c947cc5d0d248b4d7e19e49dd07f205b..55ad10a23c8c52a8ded182e95f2f476e69af54d8 100644
--- a/daemon/src/iax/iaxvoiplink.cpp
+++ b/daemon/src/iax/iaxvoiplink.cpp
@@ -313,20 +313,6 @@ IAXVoIPLink::newOutgoingCall(const std::string& id, const std::string& toUrl, co
     return call;
 }
 
-void
-IAXVoIPLink::carryingDTMFdigits(const std::string& id, char code)
-{
-    std::lock_guard<std::mutex> lock(iaxCallMapMutex_);
-    auto call = getIAXCall(id);
-    if (!call)
-        return;
-
-    {
-        std::lock_guard<std::mutex> lock(mutexIAX);
-        iax_send_dtmf(call->session, code);
-    }
-}
-
 #if HAVE_INSTANT_MESSAGING
 void
 IAXVoIPLink::sendTextMessage(const std::string& callID,
diff --git a/daemon/src/iax/iaxvoiplink.h b/daemon/src/iax/iaxvoiplink.h
index 4c8a60be4b1fbeb0af7cdc1860cd2b1736be52dc..6fd8acca27bdf9495ce8febad6aa12a1318abac8 100644
--- a/daemon/src/iax/iaxvoiplink.h
+++ b/daemon/src/iax/iaxvoiplink.h
@@ -127,14 +127,6 @@ class IAXVoIPLink : public VoIPLink {
          */
         virtual void cancel(const std::string& /*id*/) {}
 
-        /**
-         * Send DTMF
-         * @param id The ID of the call
-         * @param code  The code of the DTMF
-         */
-        virtual void carryingDTMFdigits(const std::string& id, char code);
-
-
 #if HAVE_INSTANT_MESSAGING
         virtual void sendTextMessage(const std::string& callID, const std::string& message, const std::string& from);
 #endif
diff --git a/daemon/src/managerimpl.cpp b/daemon/src/managerimpl.cpp
index e780450a1f3523807d99c30e3aa81aab7cb80063..909e32e242b79a55f66f3848267aa2cfdbfbf2a6 100644
--- a/daemon/src/managerimpl.cpp
+++ b/daemon/src/managerimpl.cpp
@@ -1331,8 +1331,8 @@ void ManagerImpl::sendDtmf(const std::string& id, char code)
     if (id.empty())
         return;
 
-    std::string accountid(getAccountFromCall(id));
-    getAccountLink(accountid)->carryingDTMFdigits(id, code);
+    if (auto call = getCallFromCallID(id))
+        call->carryingDTMFdigits(code);
 }
 
 //THREAD=Main | VoIPLink
diff --git a/daemon/src/sip/sipcall.cpp b/daemon/src/sip/sipcall.cpp
index 7166ec2e9b01cf463246c4fb44f49bab7f1557a6..e3a29ba0fb92c2b8496ec98d01b5fc745b785fae 100644
--- a/daemon/src/sip/sipcall.cpp
+++ b/daemon/src/sip/sipcall.cpp
@@ -36,6 +36,7 @@
 #include "logger.h" // for _debug
 #include "sdp.h"
 #include "manager.h"
+#include "array_size.h"
 
 #include "audio/audiortp/audio_rtp_factory.h" // for AudioRtpFactoryException
 
@@ -89,6 +90,35 @@ stopRtpIfCurrent(SIPCall& call)
     }
 }
 
+static void
+dtmfSend(SIPCall &call, char code, const std::string &dtmf)
+{
+    if (dtmf == SIPAccount::OVERRTP_STR) {
+        call.getAudioRtp().sendDtmfDigit(code);
+        return;
+    } else if (dtmf != SIPAccount::SIPINFO_STR) {
+        WARN("Unknown DTMF type %s, defaulting to %s instead",
+             dtmf.c_str(), SIPAccount::SIPINFO_STR);
+    } // else : dtmf == SIPINFO
+
+    int duration = Manager::instance().voipPreferences.getPulseLength();
+    char dtmf_body[1000];
+    const char *normal_str= "Signal=%c\r\nDuration=%d\r\n";
+    const char *flash_str = "Signal=%d\r\nDuration=%d\r\n";
+    const char *str;
+
+    // handle flash code
+    if (code == '!') {
+        str = flash_str;
+        code = 16;
+    } else {
+        str = normal_str;
+    }
+
+    snprintf(dtmf_body, sizeof dtmf_body - 1, str, code, duration);
+    call.sendSIPInfo(dtmf_body, "dtmf-relay");
+}
+
 SIPCall::SIPCall(const std::string& id, Call::CallType type,
         pj_caching_pool *caching_pool, const std::string &account_id) :
     Call(id, type, account_id)
@@ -147,6 +177,35 @@ VoIPLink*
 SIPCall::getVoIPLink() const
 { return &SIPVoIPLink::instance(); }
 
+void
+SIPCall::sendSIPInfo(const char *const body, const char *const subtype)
+{
+    pj_str_t methodName = CONST_PJ_STR("INFO");
+    pjsip_method method;
+    pjsip_method_init_np(&method, &methodName);
+
+    /* Create request message. */
+    pjsip_tx_data *tdata;
+
+    if (pjsip_dlg_create_request(inv->dlg, &method, -1, &tdata) != PJ_SUCCESS) {
+        ERROR("Could not create dialog");
+        return;
+    }
+
+    /* Create "application/<subtype>" message body. */
+    pj_str_t content;
+    pj_cstr(&content, body);
+    const pj_str_t type = CONST_PJ_STR("application");
+    pj_str_t pj_subtype;
+    pj_cstr(&pj_subtype, subtype);
+    tdata->msg->body = pjsip_msg_body_create(tdata->pool, &type, &pj_subtype, &content);
+
+    if (tdata->msg->body == NULL)
+        pjsip_tx_data_dec_ref(tdata);
+    else
+        pjsip_dlg_send_request(inv->dlg, tdata, SIPVoIPLink::instance().getMod()->id, NULL);
+}
+
 void SIPCall::answer()
 {
     SIPAccount *account = Manager::instance().getSipAccount(getAccountId());
@@ -578,3 +637,14 @@ SIPCall::peerHungup()
 
     siplink.removeSipCall(getCallId());
 }
+
+void
+SIPCall::carryingDTMFdigits(char code)
+{
+    const std::string accountID(getAccountId());
+    SIPAccount *account = Manager::instance().getSipAccount(accountID);
+    if (!account)
+        return;
+
+    dtmfSend(*this, code, account->getDtmfType());
+}
diff --git a/daemon/src/sip/sipcall.h b/daemon/src/sip/sipcall.h
index dfd92711291166a06a36cef51a3ae9f7ed906873..9becb5cd0aa373832d6c42be6e1f846bb1c2fa04 100644
--- a/daemon/src/sip/sipcall.h
+++ b/daemon/src/sip/sipcall.h
@@ -112,6 +112,8 @@ class SIPCall : public Call {
 
         VoIPLink* getVoIPLink() const;
 
+        void sendSIPInfo(const char *const body, const char *const subtype);
+
         void answer();
 
         void hangup(int reason);
@@ -128,6 +130,8 @@ class SIPCall : public Call {
 
         void peerHungup();
 
+        void carryingDTMFdigits(char code);
+
     private:
 
         // override of Call::createHistoryEntry
diff --git a/daemon/src/sip/sipvoiplink.cpp b/daemon/src/sip/sipvoiplink.cpp
index 47821142cf39cf9e86f59456c7e266f164ec5f71..5d0b91c45253d974675770411b032f6c12668340 100644
--- a/daemon/src/sip/sipvoiplink.cpp
+++ b/daemon/src/sip/sipvoiplink.cpp
@@ -1144,65 +1144,6 @@ SIPVoIPLink::tryGetSIPCall(const std::string& id)
     return call;
 }
 
-static void
-sendSIPInfo(const SIPCall &call, const char *const body, const char *const subtype)
-{
-    pj_str_t methodName = CONST_PJ_STR("INFO");
-    pjsip_method method;
-    pjsip_method_init_np(&method, &methodName);
-
-    /* Create request message. */
-    pjsip_tx_data *tdata;
-
-    if (pjsip_dlg_create_request(call.inv->dlg, &method, -1, &tdata) != PJ_SUCCESS) {
-        ERROR("Could not create dialog");
-        return;
-    }
-
-    /* Create "application/<subtype>" message body. */
-    pj_str_t content;
-    pj_cstr(&content, body);
-    const pj_str_t type = CONST_PJ_STR("application");
-    pj_str_t pj_subtype;
-    pj_cstr(&pj_subtype, subtype);
-    tdata->msg->body = pjsip_msg_body_create(tdata->pool, &type, &pj_subtype, &content);
-
-    if (tdata->msg->body == NULL)
-        pjsip_tx_data_dec_ref(tdata);
-    else
-        pjsip_dlg_send_request(call.inv->dlg, tdata, mod_ua_.id, NULL);
-}
-
-static void
-dtmfSend(SIPCall &call, char code, const std::string &dtmf)
-{
-    if (dtmf == SIPAccount::OVERRTP_STR) {
-        call.getAudioRtp().sendDtmfDigit(code);
-        return;
-    } else if (dtmf != SIPAccount::SIPINFO_STR) {
-        WARN("Unknown DTMF type %s, defaulting to %s instead",
-             dtmf.c_str(), SIPAccount::SIPINFO_STR);
-    } // else : dtmf == SIPINFO
-
-    int duration = Manager::instance().voipPreferences.getPulseLength();
-    char dtmf_body[1000];
-
-    const char *normal_str= "Signal=%c\r\nDuration=%d\r\n";
-    const char *flash_str = "Signal=%d\r\nDuration=%d\r\n";
-    const char *str;
-
-    // handle flash code
-    if (code == '!') {
-        str = flash_str;
-        code = 16;
-    } else {
-        str = normal_str;
-    }
-
-    snprintf(dtmf_body, sizeof dtmf_body - 1, str, code, duration);
-    sendSIPInfo(call, dtmf_body, "dtmf-relay");
-}
-
 #ifdef SFL_VIDEO
 // Called from a video thread
 void
@@ -1246,27 +1187,10 @@ SIPVoIPLink::requestKeyframe(const std::string &callID)
         "</to_encoder></vc_primitive></media_control>";
 
     DEBUG("Sending video keyframe request via SIP INFO");
-    sendSIPInfo(*call, BODY, "media_control+xml");
+    call->sendSIPInfo(BODY, "media_control+xml");
 }
 #endif
 
-void
-SIPVoIPLink::carryingDTMFdigits(const std::string& id, char code)
-{
-    auto call = getSipCall(id);
-    if (!call)
-        return;
-
-    const std::string accountID(call->getAccountId());
-    SIPAccount *account = Manager::instance().getSipAccount(accountID);
-
-    if (!account)
-        return;
-
-    dtmfSend(*call, code, account->getDtmfType());
-}
-
-
 bool
 SIPVoIPLink::SIPStartCall(std::shared_ptr<SIPCall>& call)
 {
diff --git a/daemon/src/sip/sipvoiplink.h b/daemon/src/sip/sipvoiplink.h
index 66fa63a0d286794ae3a28dee7783ed65b7feb0b2..4af99f6e2c0251c8fee6b27d5a7c70df0a830fb8 100644
--- a/daemon/src/sip/sipvoiplink.h
+++ b/daemon/src/sip/sipvoiplink.h
@@ -159,13 +159,6 @@ class SIPVoIPLink : public VoIPLink {
                                                        const std::string& toUrl,
                                                        const std::string &account_id);
 
-        /**
-         * Send DTMF refering to account configuration
-         * @param id The call identifier
-         * @param code  The char code
-         */
-        virtual void carryingDTMFdigits(const std::string& id, char code);
-
         /**
          * Tell the user that the call was answered
          * @param
diff --git a/daemon/src/voiplink.h b/daemon/src/voiplink.h
index aeb97e6c55c38cc95da4e09f0ab8380973438df2..248e187a752fbc36a81f0b725de48aca93ad7946 100644
--- a/daemon/src/voiplink.h
+++ b/daemon/src/voiplink.h
@@ -93,13 +93,6 @@ class VoIPLink {
          */
         virtual std::vector<std::shared_ptr<Call> > getCalls(const std::string &account_id) const = 0;
 
-        /**
-         * Send DTMF
-         * @param id The call identifier
-         * @param code  The char code
-         */
-        virtual void carryingDTMFdigits(const std::string &id, char code) = 0;
-
         /**
          * Send a message to a call identified by its callid
          *