Fixes https://linear.app/chatwoot/issue/PLA-99/whatsapp-messages-dropped-for-brazilargentina-numbers-due-to-phone Fixes https://github.com/chatwoot/chatwoot/issues/14492 Meta's WhatsApp Cloud API includes `display_phone_number` in webhook payloads, but its format can differ from the number stored in Chatwoot's channel record. In Brazil, Meta omits the mobile 9 prefix. For example, it sends 55419XXXXXXX (12 digits) instead of 554199XXXXXXX (13 digits). In Argentina, Meta adds an extra 9 after the country code. For example, it sends 549XXXXXXXXXX instead of 54XXXXXXXXXX. The whatsapp event job uses `display_phone_number` for an exact-match channel lookup. When the formats do not match, the lookup returns nil and the incoming message is silently dropped, logging: `Inactive WhatsApp channel: unknown - <phone_number>.` The fix extends `get_channel_from_wb_payload` to fall back to normalized phone number matching using the existing PhoneNumberNormalizationService normalizers (Brazil, Argentina), which were previously only used for contact-level lookups. --------- Co-authored-by: Sojan Jose <sojan@pepalo.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Muhsin <12408980+muhsin-k@users.noreply.github.com>
67 lines
2.1 KiB
Ruby
67 lines
2.1 KiB
Ruby
class Webhooks::WhatsappController < ActionController::API
|
|
include MetaTokenVerifyConcern
|
|
|
|
before_action :verify_meta_signature!, only: :process_payload
|
|
|
|
def process_payload
|
|
if inactive_whatsapp_number?
|
|
Rails.logger.warn("Rejected webhook for inactive WhatsApp number: #{params[:phone_number]}")
|
|
render json: { error: 'Inactive WhatsApp number' }, status: :unprocessable_entity
|
|
return
|
|
end
|
|
|
|
Webhooks::WhatsappEventsJob.perform_later(params.to_unsafe_hash)
|
|
head :ok
|
|
end
|
|
|
|
private
|
|
|
|
def valid_token?(token)
|
|
channel = Channel::Whatsapp.find_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 meta_app_secrets
|
|
[
|
|
*channel_meta_app_secrets(whatsapp_channel),
|
|
GlobalConfigService.load('WHATSAPP_APP_SECRET', nil)
|
|
]
|
|
end
|
|
|
|
def whatsapp_channel
|
|
@whatsapp_channel ||= whatsapp_business_payload_channel || Channel::Whatsapp.find_by(phone_number: params[:phone_number])
|
|
end
|
|
|
|
def meta_signature_verification_required?
|
|
return true if whatsapp_channel.blank?
|
|
return false unless whatsapp_channel.provider == 'whatsapp_cloud'
|
|
return true if channel_meta_app_secrets(whatsapp_channel).present?
|
|
|
|
whatsapp_channel.provider_config['source'] == 'embedded_signup'
|
|
end
|
|
|
|
def whatsapp_business_payload_channel
|
|
return unless params[:object] == 'whatsapp_business_account'
|
|
|
|
metadata = params.dig(:entry, 0, :changes, 0, :value, :metadata)
|
|
return if metadata.blank?
|
|
|
|
Whatsapp::WebhookChannelFinderService.new(
|
|
display_phone_number: metadata[:display_phone_number],
|
|
phone_number_id: metadata[:phone_number_id]
|
|
).perform
|
|
end
|
|
|
|
def inactive_whatsapp_number?
|
|
phone_number = 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)
|
|
end
|
|
end
|