Zoom Phone Warm Transfer Logs Someone Got Worse

We are using the Zoom Phone API to retrieve call history log data for our reports, but so far we’ve spent all our time trying to convert the Zoom data into something that makes sense. Depending on the type of call, some are logged as a single call with multiple parts, while others get logged separately. Warm transfers specifically are a major problem.

If you go on the Zoom admin interface in the Admin → Phone System Management → Logs section and use the “New View” and then click the Event filter dropdown and click the arrow next to Transfer to get the option to check “Warm Transfer”, the most recent events that show are from November 15th. It seems after that point, Zoom changed the way that warm transfers are logged, but never bothered updating the website interface.

We performed a warm transfer on February 18th at 5:20pm Central time to see how it is now logging. The logs show a single inbound log, event “incoming”, call result “answered”, duration 1:03. If you click call path, it shows the initial call, and then has a blue “Continue to trace warm transfer” button. If you click that button, it expands and pulls the call data for the warm transfer, showing that the initial recipient made a call to the transfer recipient.

A web inspector shows that part of the JSON passed to the browser includes:
“actions”: [{
“time”: 1739920863245,
“type”: 10,
“extra_info”: null
}, {
“time”: 1739920869435,
“type”: 11,
“extra_info”: null
}, {
“time”: 1739920877615,
“type”: 10,
“extra_info”: null
}, {
“time”: 1739920898308,
“type”: 61,
“extra_info”: “7472903303009166744”
}],
It appears action type 10 is “Hold”, 11 is “Unhold”, and 61 is “warm transfer”, and extra_info is the call id for that second part of the call.

A call to the API at https://api.zoom.us/v2/phone/call_history does not return any of that information, so from the API it looks like a simple call from an outside phone number to the initial call recipient, and as of November 15th calls contain zero trace in the API logs that a warm transfer even happened, let alone contain any info on how to follow to the next part of the call.

Is anyone actually working on the API? It seems like it is consistently behind the web interface, the REST API doesn’t even match the webhook API (for example, the call ID sent on a webhook cannot be used to lookup call history details at the REST API).

Waiting to hear back on another issue that could be possibly connected to your experience. Thanks for your patience.

Is there any update on this yet?

Hi @paul.woltman ,

I’ve recently heard back on a related issue with call history endpoint not having parity with upcoming deprecated call log endpoints and web portal data.

My colleague has made me aware of an initiative with the Phone API team to reconcile these issues.

Please know I hear and sympathize with your grievances and am working to push for consistent parity experiences across our products with developer resources.

In the interim, I have formally logged your issue with Developer Support for another point of tracking. Please check the email associated with your developer forum account to see the ticket.

Here’s what I’ve communicated on your behalf:

A customer using the Zoom Phone API to retrieve call history data reports inconsistencies in how calls—especially warm transfers—are logged. They note a major discrepancy between the web admin interface and the API, making it difficult to track warm transfers in their reports.

Customer Findings:

  • Prior to November 15, warm transfers were identifiable in API logs. After this date, they are no longer visible.
  • The Zoom admin UI still presents warm transfer details, but the API does not.
  • Web inspector analysis reveals that warm transfer events (action type 61) are present in the JSON data passed to the UI but are missing from the API response.
  • API logs show only the initial incoming call, with no way to follow the transfer path.

Replication Details:

  • A warm transfer was performed on February 18 at 5:20 PM CST.
  • The web UI displayed the warm transfer event and call continuation, but a call to the /v2/phone/call_history API did not contain any related data.

Potential Root Causes:

  1. Data Discrepancy: API responses do not align with the Zoom web UI, suggesting missing or improperly surfaced data in the API layer.
  2. Schema Changes Post-November 15: The API may have undergone an uncommunicated change affecting how warm transfers are logged.
  3. Incomplete API Implementation: The REST API does not provide the same level of detail as the webhook API, leading to usability issues.

Next Steps for Escalation:

  • Engineering Review: Investigate whether warm transfer events were intentionally removed from API responses post-November 15.
  • API Consistency Check: Ensure REST API and webhook API provide aligned call history details.
  • Documentation Update (if applicable): Clarify how warm transfers are expected to be logged and retrieved via API.
  • Customer Follow-Up: Provide a timeline for resolution or a workaround if available.

Hi @paul.woltman , have you seen this?

Yes, I saw that. It doesn’t really apply, as the updated webhook still doesn’t contain the property alphanumeric ID that can be used in the /phone/call_history/ REST API call

1 Like

Thanks again, we’re relaying the feedback @paul.woltman and continuing to to follow up internally.

@gianni.zoom Can you confirm that something changed with the call history log API yesterday (May 19th)?

Starting yesterday, we are now seeing instances of “event”: “warm_transfer” in our call path details. We hadn’t see warm_transfer events show in the api since November 15th.

Unfortunately, it’s still not all that useful - now, if there is a call that gets warm transfered, we see:

  1. Incoming call that is answered by one of our users
  2. An outbound call with an event in the call_path data for “warm_transfer”, which gives the information for the user initiating the warm transfer and the user receiving the warm transfer, but doesn’t actually give any info about the original call, so there’s nothing to directly connect it to the original incoming call
  3. An incoming call with event “incoming” for the recipient of the warm transfer, with info about the user initiating the warm transfer and the recipient of the warm transfer, but again no details on what call is being received

Also confirmed that in the webhook, we now see phone.caller_call_history_completed events with a call_log child object that has a warm_transfer event. Again, no details as to the call that is being warm transferred, just an indication that a user performed a warm transfer and we have to manually hunt down the original call.

The Phone System Management → Logs page on the Zoom admin interface has also been updated - prior to November 15th, you could filter to warm transfer calls using the Event filter → Transfer → Warm Transfer. After November 15th, no calls show when you use that filter, but now there is a filter under Event filter → Ringing → Warm Transfer that shows warm transfers that happened starting May 19, 2025.

The web interface gives you the option to “Continue to trace warm transfer” that is able to link the original call with the warm transfer calls. This data still doesn’t exist in the API or webhook data.

@paul.woltman , just sent you a private message asking for a few details.

Any useful insights here? I am struggling to link transferred calls together via the api and webhook data.

I don’t think Zoom is particularly interested in actually fixing this. Also, we started working with Zoom and the API in mid-2024, and they’ve changed the way these calls get logged three times.

With that said, as of 5/19/2025 have stayed the same. This is what I am doing to make something out of this mayhem:

  1. We retrieve all calls via the /v2/phone/call_history call and save it in our database (I wrote a method in our software to call out and retrieve the last hours worth of calls, triggered by the Zoom webhook for phone.caller_call_history_completed and phone.callee_call_history_completed, but you could also just run the call_history API call periodically to get your missing data

  2. We do some parsing (converting timestamps to what we need, removing any duplicates,etc)

  3. I do a SQL query to find internal calls: group by zoom_call_id and start_time, where the zoom_call_id doesn’t contain an underscore (which gets used when there’s a standard transfer), where the call_path array length = 1 and the connect_type = internal, having count(*) > 1, and grab the zoom_call_id from those results

  4. We do the same thing, but now grouping by zoom_call_id and start_time, since there are calls that don’t get answered and therefore don’t have an answer_time

  5. Now that we have the list of zoom_call_ids that represent internal calls, I iterate over that list of ids and query for logs with a zoom_call_id that matches

  6. From that result, I get the inbound call (direction = ‘inbound’) and outbound call (direction = ‘outbound’)

  7. Make sure that I have both an inbound and outbound call record, then check the call path length to make sure both have an array size of 1

  8. Ignore any calls where the inbound call talk_time - the outbound call talk_time is less than 5 seconds

  9. Now, the meat of it: if the inbound call call_path’s first event is ‘incoming’ and the outgoing call’s first call_path event is ‘warm_transfer’ it’s a warm transfer that was logged on/after 5/19/2025 (prior to that date, the outgoing call would show ‘outgoing’ for the initial event’

  10. Now that we have identified a warm_transfer call, I go back and query our list of zoom phone logs for any calls where the end_time = the outbound call’s end_time, where the call’s zoom_call_id != the outbound call’s zoom_call_id (since the original call that got transferred will have a different zoom_call_id)

  11. Assuming we find a result, and the result is a single log, we can assume it’s the original call, and we then look at that call’s call_path looking for the transfer. In Ruby, it looks like this:

    transferred_call_paths = original_call**.details_json[‘call_path’].select{|cp|** cp**[‘callee_ext_number’]** == outbound_call**.summary_json[‘caller_ext_number’]** && cp**[‘end_time’]** == outbound_call**.summary_json[‘end_time’]** && cp**[‘hold_time’]** > 0**}** rescue nil

    We are looking for the portion of the call_path where the callee_ext_number equals the outbound_call caller_ext_number, and the en_time matches the outbound_call end_time, and the hold_time is greater than 0, since all warm_transfers will involve putting the original call on hold while the “consultation” call happens

  12. Assuming we find a single match in the call_path, we then calculate the warm_transfer_duration as being our inbound_call talk_time - the outbound_call talk_time, and the warm_transfer_hold_time is the inbound_call hold_time - the outbound_call hold_time.

  13. We can now build our three records: 1) The inbound_consultive_phone_log (this is the received by the person who later receives the warm transferred call). 2) The outbound_consultive_phone_log (this is the call made to tell the recipient they have a call being transferred to them). 3) The warm_transferred_phone_log (this is the missing part of the call; start_time = the outbound_call’s end_time, the end_time is the inbound_call’s end_time, talk_duration is the warm_transfer_duration, hold_time is the warm_transfer_hold_time, ring_time is 0

  14. So, in general, you can find the missing call by searching all of our call logs looking for calls where you have identical zoom_call_ids, where the outbound_call has a warm_transfer event, and then search the logs for calls where the end_time = the outbound_call end_time and the zoom_call_id doesn’t match the outbound_call zoom_call_id

Like I said, mayhem

Thanks for the response @paul.woltman . Mayhem seems like the right word. If only they could add something like a “transferred” and an “original_call_id” to the call log data and this would all be simple to put together.

Hi Zoom Team. Id like to re-open this issue as I think we are having something similar. In some scenarios 1 call, when transferred, has 2 call IDs in the Zoom Admin Portal, but every API call is only getting the initial recording before the transfer. Even the Call ID, which in the Zoom UI provides the correct recording, when using the API, we get a different recording.

Can someone from Zoom please assist as we have tried every ID, every endpoint but nothing seems to get the correct recording.

Thanks

Stephen

Hi @paul.woltman
Thanks for this detailed and valuable feedback. I will review it and pass it down to our PM and will get back to you soon