From 20af8504328c50db2618e108f1db4d6cb2826d49 Mon Sep 17 00:00:00 2001 From: Tanmay Deep Sharma Date: Wed, 6 May 2026 16:20:52 +0700 Subject: [PATCH] fix(voice): normalize WhatsApp wa_id before ContactInbox lookup Inbound WhatsApp calls were matching contacts by stripping `+` from the raw from_number, bypassing the BR/AR wa_id normalization that Whatsapp::IncomingMessageServiceHelpers#processed_waid runs on the messaging path. A wa_id whose webhook variant differs from the stored normalized source_id (notably Brazil's added "9") would miss the existing ContactInbox and create a fresh contact/conversation, fragmenting history. Route the voice path through the same Whatsapp::PhoneNumberNormalizationService so the lookup is consistent. --- .../services/voice/inbound_call_builder.rb | 7 +++++- .../voice/inbound_call_builder_spec.rb | 25 +++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/enterprise/app/services/voice/inbound_call_builder.rb b/enterprise/app/services/voice/inbound_call_builder.rb index 1bf953d5a..f3388d4f5 100644 --- a/enterprise/app/services/voice/inbound_call_builder.rb +++ b/enterprise/app/services/voice/inbound_call_builder.rb @@ -61,8 +61,13 @@ class Voice::InboundCallBuilder end # WhatsApp ContactInbox.source_id must be digits-only (the wa_id); Twilio accepts the +. + # Run BR/AR-style wa_id normalization (same path messaging uses) so an inbound call + # finds the existing ContactInbox instead of forking a new contact/conversation. def source_id_for_provider - provider == :whatsapp ? from_number.to_s.delete_prefix('+') : from_number + return from_number unless provider == :whatsapp + + digits = from_number.to_s.delete_prefix('+') + Whatsapp::PhoneNumberNormalizationService.new(inbox).normalize_and_find_contact_by_provider(digits, :cloud) end def resolve_conversation!(contact, contact_inbox) diff --git a/spec/enterprise/services/voice/inbound_call_builder_spec.rb b/spec/enterprise/services/voice/inbound_call_builder_spec.rb index 02f0b3885..3065875f2 100644 --- a/spec/enterprise/services/voice/inbound_call_builder_spec.rb +++ b/spec/enterprise/services/voice/inbound_call_builder_spec.rb @@ -100,6 +100,31 @@ RSpec.describe Voice::InboundCallBuilder do end end + context 'when the WhatsApp wa_id needs Brazil normalization to match an existing ContactInbox' do + let(:whatsapp_channel) do + create(:channel_whatsapp, account: account, provider: 'whatsapp_cloud', + provider_config: { 'phone_number_id' => '123', 'calling_enabled' => true }, + validate_provider_config: false, sync_templates: false) + end + let(:whatsapp_inbox) { whatsapp_channel.inbox } + let!(:stored_contact) { create(:contact, account: account, phone_number: '+5541988887777') } + let!(:stored_contact_inbox) do + create(:contact_inbox, contact: stored_contact, inbox: whatsapp_inbox, source_id: '5541988887777') + end + + it 'reuses the contact via normalized wa_id rather than forking a new ContactInbox' do + call = described_class.perform!( + inbox: whatsapp_inbox, + from_number: '+554188887777', + call_sid: 'wacall_br_1', + provider: :whatsapp + ) + + expect(call.contact).to eq(stored_contact) + expect(call.conversation.contact_inbox).to eq(stored_contact_inbox) + end + end + context 'when the inbox has lock_to_single_conversation enabled' do let!(:contact) { create(:contact, account: account, phone_number: from_number) } let!(:contact_inbox) { create(:contact_inbox, contact: contact, inbox: inbox, source_id: from_number) }