Fix Wuzapi webhook handling

This commit is contained in:
Rodrigo Borba 2026-02-26 10:10:09 -03:00
parent 14dbc0f423
commit 58f5ae6157
4 changed files with 71 additions and 5 deletions

View File

@ -30,19 +30,33 @@ class Webhooks::WhatsappController < ActionController::API
end
def valid_token?(token)
channel = Channel::Whatsapp.find_by(phone_number: params[:phone_number])
channel = find_channel_by_phone_number(params[:phone_number])
whatsapp_webhook_verify_token = channel.provider_config['webhook_verify_token'] if channel.present?
token == whatsapp_webhook_verify_token if whatsapp_webhook_verify_token.present?
end
def inactive_whatsapp_number?
phone_number = params[:phone_number]
phone_number = normalize_phone(params[:phone_number])
return false if phone_number.blank?
inactive_numbers = GlobalConfig.get_value('INACTIVE_WHATSAPP_NUMBERS').to_s
return false if inactive_numbers.blank?
inactive_numbers_array = inactive_numbers.split(',').map(&:strip)
inactive_numbers_array.include?(phone_number)
inactive_numbers_array.map { |number| normalize_phone(number) }.include?(phone_number)
end
def find_channel_by_phone_number(phone_number)
raw_phone = phone_number.to_s.strip
digits_only = normalize_phone(raw_phone)
return if raw_phone.blank? && digits_only.blank?
Channel::Whatsapp.find_by(phone_number: raw_phone) ||
Channel::Whatsapp.find_by(phone_number: "+#{digits_only}") ||
Channel::Whatsapp.where("regexp_replace(phone_number, '[^0-9]', '', 'g') = ?", digits_only).first
end
def normalize_phone(phone_number)
phone_number.to_s.gsub(/\D/, '')
end
end

View File

@ -79,7 +79,7 @@ class Webhooks::WhatsappEventsJob < ApplicationJob
return unless params[:phone_number]
Channel::Whatsapp.find_by(phone_number: params[:phone_number])
find_channel_by_phone_number(params[:phone_number])
end
def channel_is_inactive?(channel)
@ -96,8 +96,18 @@ class Webhooks::WhatsappEventsJob < ApplicationJob
# we will give priority to the phone_number in the payload
phone_number = "+#{params[:entry].first[:changes].first.dig(:value, :metadata, :display_phone_number)}"
phone_number_id = params[:entry].first[:changes].first.dig(:value, :metadata, :phone_number_id)
channel = Channel::Whatsapp.find_by(phone_number: phone_number)
channel = find_channel_by_phone_number(phone_number)
# validate to ensure the phone number id matches the whatsapp channel
channel if channel && channel.provider_config['phone_number_id'] == phone_number_id
end
def find_channel_by_phone_number(phone_number)
raw_phone = phone_number.to_s.strip
digits_only = raw_phone.gsub(/\D/, '')
return if raw_phone.blank? && digits_only.blank?
Channel::Whatsapp.find_by(phone_number: raw_phone) ||
Channel::Whatsapp.find_by(phone_number: "+#{digits_only}") ||
Channel::Whatsapp.where("regexp_replace(phone_number, '[^0-9]', '', 'g') = ?", digits_only).first
end
end

View File

@ -46,6 +46,16 @@ RSpec.describe 'Webhooks::WhatsappController', type: :request do
expect(response).to have_http_status(:unprocessable_entity)
expect(response.parsed_body['error']).to eq('Inactive WhatsApp number')
end
it 'returns service unavailable even when incoming phone has different formatting' do
allow(Rails.logger).to receive(:warn)
allow(GlobalConfig).to receive(:get_value).with('INACTIVE_WHATSAPP_NUMBERS').and_return('+55 (61) 91234-5678')
post '/webhooks/whatsapp/5561912345678', params: { content: 'hello' }
expect(Rails.logger).to have_received(:warn).with('Rejected webhook for inactive WhatsApp number: 5561912345678')
expect(response).to have_http_status(:unprocessable_entity)
end
end
context 'when INACTIVE_WHATSAPP_NUMBERS config is not set' do

View File

@ -109,6 +109,38 @@ RSpec.describe Webhooks::WhatsappEventsJob do
end
end
context 'when phone number format differs in webhook payload' do
it 'finds channel by normalized phone number and processes event' do
wuzapi_channel = create(
:channel_whatsapp,
provider: 'wuzapi',
phone_number: '+55 (61) 99186-8492',
sync_templates: false,
validate_provider_config: false
)
wuzapi_params = {
phone_number: '5561991868492',
type: 'Message',
event: {
Info: {
ID: '3A721AC0A215F23ACFA9',
IsFromMe: false,
IsGroup: false,
Type: 'text',
Sender: '556182098580@s.whatsapp.net',
Timestamp: Time.current.iso8601
},
Message: { conversation: 'Ola terse' }
}
}
allow(Whatsapp::IncomingMessageWuzapiService).to receive(:new).and_return(process_service)
expect(Whatsapp::IncomingMessageWuzapiService).to receive(:new).with(inbox: wuzapi_channel.inbox, params: wuzapi_params)
job.perform_now(wuzapi_params)
end
end
context 'when whatsapp business params' do
it 'enqueue Whatsapp::IncomingMessageWhatsappCloudService based on the number in payload' do
other_channel = create(:channel_whatsapp, phone_number: '+1987654', provider: 'whatsapp_cloud', sync_templates: false,