From 257a9129d7b136fe380d0a8e666740f429cb27be Mon Sep 17 00:00:00 2001 From: Tanmay Deep Sharma Date: Fri, 1 May 2026 17:33:05 +0700 Subject: [PATCH] fix(voice): create missed-call record when terminate webhook arrives before connect --- .../services/whatsapp/incoming_call_service.rb | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/enterprise/app/services/whatsapp/incoming_call_service.rb b/enterprise/app/services/whatsapp/incoming_call_service.rb index 3b4d17378..709862230 100644 --- a/enterprise/app/services/whatsapp/incoming_call_service.rb +++ b/enterprise/app/services/whatsapp/incoming_call_service.rb @@ -50,7 +50,10 @@ class Whatsapp::IncomingCallService end def handle_terminate(payload) - call = Call.whatsapp.find_by(provider_call_id: payload[:id]) + # Webhooks can arrive out of order (terminate before connect under tunnel/network + # delays). Materialise a missed-call record so the contact's "called and hung up" + # still surfaces in the dashboard instead of being silently dropped. + call = Call.whatsapp.find_by(provider_call_id: payload[:id]) || build_missed_inbound_call(payload) return unless call duration = payload[:duration]&.to_i @@ -60,6 +63,18 @@ class Whatsapp::IncomingCallService broadcast(call, 'voice_call.ended', status: call.display_status, duration_seconds: call.duration_seconds) end + # No connect was ever processed (webhook reordering or never delivered) — build a + # bare Call+Message for the contact so the UI shows the missed-call bubble. + def build_missed_inbound_call(payload) + Voice::InboundCallBuilder.perform!( + inbox: inbox, from_number: "+#{payload[:from]}", call_sid: payload[:id], + provider: :whatsapp, + extra_meta: { 'ice_servers' => Call.default_ice_servers } + ) + rescue ActiveRecord::RecordNotUnique + Call.whatsapp.find_by(provider_call_id: payload[:id]) + end + # accepted_by_agent_id is the initiating agent on outbound calls, so it only signals "answered" for inbound. def answered?(call, duration) call.in_progress? || duration.to_i.positive? || (call.incoming? && call.accepted_by_agent_id.present?)