Audio quality problems after our agent does a cold transfer

Hi,

Our client has complained about the audio quality on calls after our agent transfers them to a human. This is what they said:

“Since we started with [the AI], our phone lines are getting very bad with several interruptions. Everyone in the team has the same feeling as you need to wait for the client to talk, give 1/2 seconds and then start talking back… like a walkie-talkie style, else you get the sound completely cut out. We also have a very high number of clients complaining about the same issue and difficulties hearing us.”

To be clear about where this is happening: the conversation with the AI itself sounds fine. The problems are on the part of the call after the transfer. They are reported by the staff who receive the transferred call, and by the customers speaking to those staff once connected. It affects several staff members and many different callers, so we do not think it is one handset or one network.

We found a report that sounds similar to ours here: Severe Static on Transfer Leg — Staff Hears Noise After AI Transfers Call

Now, how we do the transfer, in case it is relevant. We do a cold transfer. The agent runs on a custom LLM over your WebSocket. When the agent decides to hand over, our server returns a response frame with the transfer_number field set to the destination number, along with content_complete: true, and Retell performs the transfer once the agent has finished speaking. We do not use a transfer tool or a conversation flow node for this, it is driven entirely from the WebSocket response. We have never set cold_transfer_mode, so we believe we are on the SIP INVITE default.

On the telephony side: the number our agent answers on is a UK number from Twilio, connected to Retell through a SIP trunk. The number we transfer to is the client’s own number, which is with Yo Telecom, a UK provider.

Do you have any idea what could be causing this, or where we should be looking? We are not sure whether it is something in our configuration, something about how the transfer itself works, or something on the receiving side with their phone provider. If you can point us in the right direction, or check the quality metrics on the transferred leg, that would help a lot.

Below is our setup in case something stands out to you.

Agent ID: agent_655e2f7f91ae3cdd5193d4377d

Agent configuration:
response_engine: custom-llm over our own WebSocket
voice_id: minimax-Rita
voice_speed: 1.0
responsiveness: 0.9
interruption_sensitivity: 0.8
denoising_mode: noise-and-background-speech-cancellation
ambient_sound: coffee-shop
ambient_sound_volume: 1.0
end_call_after_silence_ms: 60000
max_call_duration_ms: 600000

Thanks

Hey @pedro Could you please share the relevant Call ID where you experienced the audio quality issue? This will help us review the call and investigate the problem further.

Thank You

Hey! The client expressed it happened on all of them but here is one example: call_13829a2927a39edfea1cee3a0d8

@pedro I’m checking this with the team and will get back to you as soon as I have an update.

Hello @pedro

What we see:
We pulled the sample call (call_13829a2927a39edfea1cee3a0d8) and the agent record. A few things line up with what your client is describing:
The transfer is being triggered from your WebSocket by returning transfer_number on a response frame. That path always uses the platform default cold transfer mode, which is SIP INVITE . The WebSocket response schema only exposes end_call, transfer_number, show_transferee_as_caller, and digit_to_press, so there is no way to select SIP REFER from that flow.

Under SIP INVITE, Retell (via LiveKit) stays in the media path as a back-to-back user agent after the transfer connects. Every RTP packet from the caller to your staff and back is hairpinned Caller → Twilio → LiveKit → Twilio → Yo Telecom, and gets transcoded on the way through. That extra hop plus the transcode is what produces the walkie-talkie / half-duplex feel your staff is reporting.

On top of that, the agent has Background Voice Cancellation set to remove noise and background speech, and it remains active on the audio bridged through Retell during the transferred leg. On a live human-to-human conversation where both sides talk over each other naturally, that suppression can clip whichever side started talking a fraction of a second later, which matches “you need to wait for the client to talk, give 1/2 seconds and then start talking back” pretty exactly.
The report you linked describes the same underlying setup.

Recommendation
Move the transfer off the custom LLM WebSocket and onto a Retell transfer_call tool (or Transfer Call node in Conversation Flow) with SIP Transfer Method set to SIP REFER. With SIP REFER, Twilio hands the call off directly to Yo Telecom and Retell drops out of the media path entirely, so the post-transfer leg becomes a normal carrier-to-carrier call with no Retell transcoding and no Retell denoising involved.
Docs on the mode: Explicit cold transfer mode selection (01/23/2026) - Retell AI and how tools work with a custom LLM: Function calling for a Retell custom LLM voice agent

Two caveats worth knowing before switching:

  • Under SIP REFER on Retell-managed Twilio numbers, the caller ID presented to your staff will be the original caller’s number, not your Retell number. Show Transferee As Caller is ignored under REFER.
  • Yo Telecom’s trunk needs to accept the incoming REFER-initiated call cleanly (Twilio supports REFER out of the box, and most carriers do). If any subset of destinations rejects it, you would fall back to SIP INVITE for those and live with the current media-path behavior.

If SIP REFER is not viable
Keep the WebSocket transfer_number path but soften the audio processing on the bridged leg by changing Background Voice Cancellation on the agent from “Noise and background speech” to “Noise only” (or turning it off). It will not fully match a native carrier bridge, since Retell is still in the middle under SIP INVITE, but it removes the piece most likely to be clipping overlapping speech during the human conversation.
Docs: Handle background speech & noise - Retell AI

Thank You

@Shah-Fazal — I’m a colleague of pedro’s at Puppeteer AI, picking this up from our side.

First, thanks for the diagnosis. We independently confirmed the hairpin from our Twilio records: on every transfer, the inbound leg and the outbound leg to the destination are both live for the whole conversation and tear down within one second of each other, on the same trunk.

We have since configured our Twilio trunk exactly as your custom telephony docs require — SIP REFER enabled, PSTN transfer enabled, and transfer_caller_id set to from-transferee. We are on Elastic SIP Trunking, not Dial to SIP URI. So the trunk side is ready.

The problem is that we cannot act on the recommendation as written, and we think the two docs you linked contradict each other.

You suggest moving the transfer onto a transfer_call tool. But “Function calling for a Retell custom LLM voice agent” states that for a custom LLM, tools are defined in our own LLM code, and that tool_call_invocation and tool_call_result are logging only. The only Retell-executed actions on the WebSocket are end_call, transfer_number, show_transferee_as_caller and digit_to_press. The agent object accepts no general_tools, and cold_transfer_mode exists only on Create/Update Retell LLM, Conversation Flow and Conversation Flow Component. Since the 01/23/2026 deprecation, show_transferee_as_caller no longer selects REFER either.

So as far as we can tell, a custom-llm agent cannot reach sip_refer by any route. Could you confirm or correct that?

If it is correct, three follow-ups.

  1. Intermediary workaround. If we point transfer_number at an intermediary Retell number backed by a Conversation Flow whose transfer node uses cold_transfer_mode sip_refer, does the REFER on that second leg actually release the original caller leg at our trunk? Or does the first SIP INVITE hop keep Retell in the media path regardless, leaving us hairpinned with an extra hop?
  2. Caller ID through two hops. Assuming that workaround does work, hop 1 is still a SIP INVITE, so the intermediary leg would present our number rather than the caller’s, and the REFER at hop 2 would then forward our number to the destination. Do we need show_transferee_as_caller set to true on the hop-1 frame to preserve the original caller’s CLI through to the destination, or is the CLI resolved some other way?
  3. Feature request. Can cold_transfer_mode be added to the WebSocket response frame? That is the clean fix, it is purely additive, and it would unblock every custom-LLM customer who transfers. As things stand the only workaround appears to be abandoning the custom LLM response engine, which is not viable for us.

For reference, this is not isolated to the agent in the original post. It affects every custom-llm agent we run that transfers to a human.

Thanks.

@Shah-Fazal — follow-up with test data.

We ran a controlled experiment to check whether SIP REFER actually works against our Elastic SIP trunk. We used a throwaway Conversation Flow agent (not our production custom-llm agent) whose start node is a transfer_call node with cold_transfer_mode set to sip_refer, pointed one of our idle trunk numbers at it, and placed a synthetic call.

It works perfectly.

Retell side, call_57983cc866e0d1e76579990671c. disconnection_reason was call_transfer, duration 463 ms, the tool call recorded option type cold_transfer with coldTransferMode sip_refer, and the tool result was “transferred successfully”.

Twilio side. The leg from our trunk into Retell lasted 1 second and ended the instant the transfer fired. The caller then stayed connected to the destination for a further 11 seconds with Retell entirely out of the path.

So your REFER implementation, our trunk configuration, and the carrier side are all fine. Nothing in the telephony setup is blocking us. That isolates the problem to exactly one thing, which is that cold_transfer_mode is not exposed on the custom LLM WebSocket response frame. Everything downstream of that field demonstrably already works.

On question 2 from my previous post, the intermediary Conversation Flow workaround, we think the test answers it and the answer is that it cannot work. A REFER releases only the leg it is issued on and terminates that Retell call. In that topology the REFER would be issued on the second leg, so the first agent stays bridged to the caller and nothing releases the original trunk leg. With BYO numbers it is worse, because the first agent’s outbound INVITE loops back out through our trunk and in again, adding legs while keeping every existing hop. Please correct us if we have that wrong, but we are not planning to pursue it.

That leaves question 3 as the whole ask. Can cold_transfer_mode be exposed on the WebSocket response frame? From the outside this looks like plumbing an existing, working field through to the custom LLM path rather than building new capability. Is there a roadmap item or rough timeline we can track?

If the answer is no, our fallback is to move the transfer off Retell entirely and drive it with Twilio call control on our side. We would rather not, given everything else already works, so any steer on feasibility or timing would help us decide.

Thanks.

Hey @ianspektor cold_transfer_mode is not currently exposed on the LLM WebSocket response frame. The WebSocket transfer response only supports transfer_number and show_transferee_as_caller — there is no cold_transfer_mode field in the WebSocket protocol.

I don’t have a confirmed roadmap or timeline for this feature. However, you can submit it as a feature request in the Feature Requests section of the forum so the team can review it.

Thank You

Thanks @Shah-Fazal — understood on cold_transfer_mode.

Could we get your recommendation for our specific setup? Constraints: our own LLM drives the conversation over the custom-LLM WebSocket, so moving the response engine to Conversation Flow or Retell LLM isn’t open to us. We need a cold transfer that takes Retell out of the media path. Our Twilio trunk is already on transfer_mode: enable-all with transfer_caller_id: from-transferee, and REFER works correctly from a Conversation Flow agent on that same trunk and number.

Specifically:

(1) Can sip_refer be enabled for the WebSocket transfer path at the account level, even if it isn’t a documented field?

(2) Is there a REST endpoint to trigger a transfer on a live call where cold_transfer_mode can be specified, as an alternative to the WebSocket frame?

(3) If neither, is register-phone-call with a Dial-to-SIP-URI from our own Twilio the pattern you’d recommend — and does a transfer on that route avoid the B2BUA hairpin?

(4) Separately: post the 2026-01-23 deprecation, does show_transferee_as_caller: true still change the From on the outbound INVITE? Our transfers currently present our own number to the destination rather than the caller’s.

For context, we’ve already tested chaining to a second Retell agent that performs the REFER — it doesn’t help, since the REFER only releases the leg directly beneath the referring agent and the first agent stays in the media path.

@Shah-Fazal quick update, and a request to escalate.

We’ve submitted the feature request for exposing cold_transfer_mode on the WebSocket response frame. It’s sitting in the Feature Requests moderation queue at the moment.

More importantly, the denoising fallback from your recommendation does not hold up. We tested both variants live with the customer today. With denoising off entirely, the bridged leg picks up every bit of background noise and is worse than before. With it set to noise only, the lag and half-duplex behaviour came back immediately on the first transfer test. We have reverted to noise and background speech.

The customer has now disabled the agent during office hours until there is a real fix, so this is costing production traffic and not just call quality.

One more data point that points at the media path rather than the denoiser: severity tracks caller geography. Calls originating in the US and in Spain to the same UK destination show noticeably different amounts of delay, which is what the extra hairpin and transcode would produce, and not something a denoising setting explains.

That leaves SIP REFER on the WebSocket path as the only remaining fix that does not involve us moving call control off Retell entirely. Anything you can do to accelerate it would genuinely help. If the roadmap cannot move quickly, an account-level enablement for us in the meantime, or accepting the field as an undocumented pass-through, would unblock production.

Hello @ianspektor I’ve escalated this with the team. If they provide a workaround for this, I’ll get back to you with the details.

Thank You

Thank you so much @Shah-Fazal.

When could I expect to get a reply back? I expect the implementation I requested to be a very small engineering pull on your end, but if it isn’t and adding support for it can’t be prioritized, I need to know ASAP to work around this on our end (which is a large change to how we route telephony: moving our numbers off Twilio Elastic SIP Trunking onto Programmable Voice so we can issue the REFER ourselves).

Hey @ianspektor On the core question. The Custom LLM WebSocket transfer_number field, on both response and agent_interrupt, is hardcoded to SIP INVITE mode in our code. There is no WebSocket field, no agent-level flag, and no account-level toggle that flips it to REFER. SIP REFER is only reachable through a Transfer Call tool with Cold Transfer Mode set to sip_refer, invoked via tool_call_invocation, and that tool configuration lives on the Retell LLM or Conversation Flow response engines. It is not exposed on the Custom LLM agent object. So a Custom LLM agent cannot reach sip_refer today by any documented or undocumented route.

  1. Cannot enable sip_refer for the WebSocket transfer path at the account level. It is not a gated field, it is a constant in the handler, so there is nothing to flip on our side.
  2. No REST endpoint accepts Cold Transfer Mode for a live transfer. /v2/update-live-call only supports overriding dynamic variables, metadata, data storage, and live-agent nudges (trigger response and additional context). It cannot initiate a transfer, and there is no other REST verb that specifies transfer mode on an in-flight call.
  3. Register Phone Call with a Dial-to-SIP-URI from your own Twilio does not solve this on its own. Once the media is inside Retell, the WebSocket transfer path is still hardcoded to INVITE regardless of how the call was ingressed. The only way that pattern removes us from the media path is if you keep call control on your Twilio side and, at transfer time, tear down Retell’s leg and bridge the caller to the human directly on Twilio (for example, a Twilio call update that redirects the parent leg to new TwiML). That is call-control moving to you, not a Retell configuration change, so we cannot recommend it as a drop-in fix and cannot promise it avoids other trade-offs without you testing.
  4. On Show Transferee As Caller after the 2026-01-23 change: on the INVITE path it still rewrites the From on the outbound INVITE to the original caller’s number. If your transfers are presenting your Retell/BYOC DID rather than the caller’s, the two likely reasons are that the flag is not set true on the WebSocket frame that carries transfer_number, or your Twilio trunk is rejecting or rewriting the From. Under REFER the flag is ignored because the carrier resolves caller ID itself, but you are not on REFER today so that does not apply here.

Your test result matches what we would expect. REFER only releases the leg directly beneath the referring agent, so the first agent stays in the media path.

Regarding Denoising, we agree with your interpretation. The geographic delta on identical destinations is consistent with hairpin plus transcode, not with the denoiser. We would not ask you to keep changing that setting.

If a Conversation-Flow-driven transfer step, launched via an intermediate action the moment your Custom LLM decides to hand off, is not viable, then the only path that takes Retell out of the media path today is call control moving to your Twilio at transfer time. We are happy to help you scope that if it is worth exploring.

For submitting a feature request, please provide the following so we can forward it to our team:

  • Description of feature
  • use case
  • priority/impact

Thank You

@Shah-Fazal thanks for the thorough answer.

The Conversation-Flow transfer step doesn’t work for us, and your reply says why: the REFER releases only the leg directly beneath the referring agent, so the first agent stays bridged to the caller and the trunk leg never releases. We tested that and reported it in post 9. With BYO numbers it’s worse — the first agent’s INVITE loops back out through our trunk and in again.

If you mean something other than chaining to a second agent — swapping the response engine in place on the same leg, with no new leg — say so and we’ll test it today. Otherwise no configuration inside Retell takes you out of the media path.

That makes the WebSocket field the fix. Everything downstream of it already works: a Conversation Flow agent on the same trunk and the same number completes the REFER in 463 ms and drops Retell out of the path (call_57983cc866e0d1e76579990671c, post 7). From out here it reads as passing an existing enum through to a handler that currently assumes INVITE, rather than building anything new.

Can you get us a yes/no and a date from the team? This is blocking production for several of our clients.

Feature request, already submitted and live: Expose cold_transfer_mode (SIP REFER) on the custom LLM WebSocket frame — prod impact

Description: expose cold_transfer_mode on the Custom LLM WebSocket transfer response and agent_interrupt, so a Custom LLM agent can select sip_refer the way the Transfer Call tool does on Retell LLM and Conversation Flow.

Use case: our own LLM drives the conversation over the custom-LLM WebSocket, so Conversation Flow isn’t open to us. We need cold transfers that take Retell out of the media path.

Priority/impact: blocking production for several of our clients.