<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Dialog Brain</title>
    <description>The latest articles on DEV Community by Dialog Brain (@dialogbrain).</description>
    <link>https://dev.to/dialogbrain</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4104153%2Fd605cd82-26c7-48fe-aabd-1f01469fc643.png</url>
      <title>DEV Community: Dialog Brain</title>
      <link>https://dev.to/dialogbrain</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/dialogbrain"/>
    <language>en</language>
    <item>
      <title>The call transfer that reported success and rang nobody</title>
      <dc:creator>Dialog Brain</dc:creator>
      <pubDate>Wed, 02 Sep 2026 04:21:46 +0000</pubDate>
      <link>https://dev.to/dialogbrain/the-call-transfer-that-reported-success-and-rang-nobody-1c01</link>
      <guid>https://dev.to/dialogbrain/the-call-transfer-that-reported-success-and-rang-nobody-1c01</guid>
      <description>&lt;p&gt;Our voice agents can hand a live phone call to a human. Getting there took two rewrites, and the first one shipped without ever delivering a single call — while our own logs said it worked. This is what the feature does now, and what the failure taught us.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the caller hears
&lt;/h2&gt;

&lt;p&gt;Someone asks to speak to a person, or the agent decides the question is not its own: a complaint, a payment, something outside its instructions. The agent says one short line, and from there the caller hears real ringing — the second number is being dialled.&lt;/p&gt;

&lt;p&gt;When the phone is answered, the agent goes quiet. Two people are on the line and nothing else is on it with them. If nobody answers, nothing happens: the caller stays with the agent, which knows the dial failed and can offer a callback or take a message.&lt;/p&gt;

&lt;h2&gt;
  
  
  The route a transfer takes
&lt;/h2&gt;

&lt;p&gt;A transfer here is not "hand the line to the carrier". It is a second call, dialled into the same conversation.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Is the number on the agent's approved list?&lt;/strong&gt; If not, refused — and the agent is still on the line.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Is this a phone call at all?&lt;/strong&gt; A messenger chat or a web voice widget has no second line to connect.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dial the second number into the same call.&lt;/strong&gt; The caller hears ringing; the agent is still there.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Did they pick up within 45 seconds?&lt;/strong&gt; If not, the dial is cancelled and the caller stays with the agent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;They picked up&lt;/strong&gt; — the agent loses the right to speak, and the two of them talk.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Both gates sit &lt;em&gt;before&lt;/em&gt; anything is dialled, so a refusal never costs the caller a single ring. The pickup fork is the only place where a call becomes a transferred one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why not SIP REFER
&lt;/h2&gt;

&lt;p&gt;Telephony has a cheaper way: a &lt;code&gt;REFER&lt;/code&gt;, after which the carrier connects the two parties itself and you leave the call — the minutes stop flowing through you.&lt;/p&gt;

&lt;p&gt;We built that first. It never delivered a call. Telnyx answered every REFER with &lt;code&gt;200 OK&lt;/code&gt; and did nothing: no progress NOTIFY, no ring at the destination. Meanwhile our own bookkeeping recorded &lt;code&gt;outcome = transferred&lt;/code&gt;, so the dashboards were green while the caller was simply dropped.&lt;/p&gt;

&lt;p&gt;The documentation explains it. REFER-based external transfer on a SIP trunk requires an &lt;strong&gt;active inbound call to your own number&lt;/strong&gt; to match against, plus a &lt;code&gt;Diversion&lt;/code&gt; header carrying that number. We were sending REFER on calls &lt;em&gt;we&lt;/em&gt; originated — the one shape it cannot work on. And LiveKit's &lt;code&gt;TransferSIPParticipant&lt;/code&gt; has no field for a custom header, so the requirement is unreachable from there anyway.&lt;/p&gt;

&lt;p&gt;Two other explanations were tested and disproved rather than assumed: enabling third-party call control on the connection changed nothing, and neither did a &lt;code&gt;sip:&lt;/code&gt; URI instead of &lt;code&gt;tel:&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three things measurement changed
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Presence in the room is not a pickup signal.&lt;/strong&gt; The dialled participant appears in the media room the moment dialling starts — measured at 310 ms, with no call at the destination. Only &lt;code&gt;wait_until_answered&lt;/code&gt; tells you a human lifted the handset.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Removing the agent takes the call with it.&lt;/strong&gt; Measured in isolation on a live call: &lt;code&gt;RemoveParticipant&lt;/code&gt;, and within five seconds the room was empty, carrier leg included. The room does not outlive its agent job. So the agent is silenced, not removed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Silencing alone is not enough.&lt;/strong&gt; Revoking publish stops the agent being &lt;em&gt;heard&lt;/em&gt;. Its job keeps running: nine seconds after one handover it composed and "spoke" a line that went into the call history, while it carried on transcribing two people who had just been handed to each other. It now gets an explicit stand-down.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest cost
&lt;/h2&gt;

&lt;p&gt;Bridging means the conversation runs over our media path, and minutes are billed on both legs — exactly what a REFER avoids. In exchange it works identically on inbound and outbound calls, on any carrier, with nothing to configure on the telephony side.&lt;/p&gt;

&lt;p&gt;If your logs can say "transferred" without anyone's phone ringing, that is the bug worth looking for first.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://dialogbrain.com/articles/call-transfer-to-human" rel="noopener noreferrer"&gt;dialogbrain.com&lt;/a&gt;, where the same piece has a flow diagram.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>webdev</category>
      <category>architecture</category>
    </item>
  </channel>
</rss>
