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.
This commit is contained in:
Tanmay Deep Sharma
2026-05-06 16:20:52 +07:00
parent f712796df3
commit 20af850432
2 changed files with 31 additions and 1 deletions
@@ -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)
@@ -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) }