Adds support for capturing Click-to-WhatsApp ad referral metadata from incoming WhatsApp messages. This stores Meta Cloud API `referral` payloads on the incoming message `content_attributes` and normalizes Twilio `Referral*` callback fields into the same shape. The UI display is intentionally deferred until Instagram, Messenger, and TikTok referral payloads are captured as well, so we can design one cross-channel referral surface instead of a WhatsApp-only sidebar block. Why message-level storage Meta sends CTWA referral details as part of the inbound message payload, not as a stable conversation-level webhook. The referral represents the exact ad click that produced that specific customer message, and later messages in the same conversation may not carry the same context. Storing the normalized referral on the message preserves the original webhook semantics, avoids adding conversation-level attribution that could become stale or ambiguous, and keeps support for both Cloud API and Twilio payloads aligned behind `content_attributes.referral`. Fixes https://linear.app/chatwoot/issue/CW-7090/surface-meta-ctwa-referral-on-incoming-whatsapp-messages-cloud-api Closes https://github.com/chatwoot/chatwoot/issues/13995, https://github.com/chatwoot/chatwoot/issues/12560, https://github.com/chatwoot/chatwoot/issues/13006 Related community PRs - https://github.com/chatwoot/chatwoot/pull/13130 - https://github.com/chatwoot/chatwoot/pull/14180 - https://github.com/chatwoot/chatwoot/pull/14121 Related follow-ups - https://linear.app/chatwoot/issue/CW-7301/capture-instagram-ad-referral-metadata-on-incoming-messages - https://linear.app/chatwoot/issue/CW-7302/capture-messenger-ad-referral-metadata-on-facebook-page-conversations - https://linear.app/chatwoot/issue/CW-7303/capture-tiktok-ad-referral-metadata-from-im-referral-msg-events How to test 1. Send or replay a WhatsApp Cloud API inbound message that contains a `messages[0].referral` payload from a Click-to-WhatsApp ad. 2. Confirm the generated incoming message stores the payload under `content_attributes.referral`. 3. Repeat with a Twilio WhatsApp callback containing `Referral*` fields and confirm the stored message has the same normalized `content_attributes.referral` structure. --------- Co-authored-by: Muhsin <12408980+muhsin-k@users.noreply.github.com>
92 lines
2.3 KiB
Ruby
92 lines
2.3 KiB
Ruby
module Whatsapp::IncomingMessageServiceHelpers
|
|
def download_attachment_file(attachment_payload)
|
|
Down.download(inbox.channel.media_url(attachment_payload[:id]), headers: inbox.channel.api_headers)
|
|
end
|
|
|
|
def conversation_params
|
|
{
|
|
account_id: @inbox.account_id,
|
|
inbox_id: @inbox.id,
|
|
contact_id: @contact.id,
|
|
contact_inbox_id: @contact_inbox.id
|
|
}
|
|
end
|
|
|
|
def processed_params
|
|
@processed_params ||= params
|
|
end
|
|
|
|
def account
|
|
@account ||= inbox.account
|
|
end
|
|
|
|
def message_type
|
|
messages_data.first[:type]
|
|
end
|
|
|
|
def message_content(message)
|
|
# TODO: map interactive messages back to button messages in chatwoot
|
|
message.dig(:text, :body) ||
|
|
message.dig(:button, :text) ||
|
|
message.dig(:interactive, :button_reply, :title) ||
|
|
message.dig(:interactive, :list_reply, :title) ||
|
|
message.dig(:name, :formatted_name)
|
|
end
|
|
|
|
def file_content_type(file_type)
|
|
return :image if %w[image sticker].include?(file_type)
|
|
return :audio if %w[audio voice].include?(file_type)
|
|
return :video if ['video'].include?(file_type)
|
|
return :location if ['location'].include?(file_type)
|
|
return :contact if ['contacts'].include?(file_type)
|
|
|
|
:file
|
|
end
|
|
|
|
def unprocessable_message_type?(message_type)
|
|
%w[reaction ephemeral request_welcome].include?(message_type)
|
|
end
|
|
|
|
def processed_waid(waid)
|
|
Whatsapp::PhoneNumberNormalizationService.new(inbox).normalize_and_find_contact_by_provider(waid, :cloud)
|
|
end
|
|
|
|
def whatsapp_phone_number(identifier)
|
|
identifier = identifier.to_s
|
|
return if identifier.blank?
|
|
return unless identifier.match?(/\A\d{1,15}\z/)
|
|
|
|
identifier
|
|
end
|
|
|
|
def error_webhook_event?(message)
|
|
message.key?('errors')
|
|
end
|
|
|
|
def log_error(message)
|
|
Rails.logger.warn "Whatsapp Error: #{message['errors'][0]['title']} - contact: #{message['from']}"
|
|
end
|
|
|
|
def process_in_reply_to(message)
|
|
@in_reply_to_external_id = message['context']&.[]('id')
|
|
end
|
|
|
|
def referral_attributes(message)
|
|
return {} if outgoing_echo
|
|
|
|
message[:referral]&.to_h&.deep_stringify_keys || {}
|
|
end
|
|
|
|
def find_message_by_source_id(source_id)
|
|
return unless source_id
|
|
|
|
@message = Message.find_by(source_id: source_id)
|
|
end
|
|
|
|
def lock_message_source_id!
|
|
return false if messages_data.blank?
|
|
|
|
Whatsapp::MessageDedupLock.new(messages_data.first[:id]).acquire!
|
|
end
|
|
end
|