chatwoot/config/initializers/facebook_messenger.rb
Sony Mathew a9ac1c633d
fix: added HMAC validation for Whatsapp and Instagram webhooks (#14280)
## Description
* Added Meta webhook HMAC validation in meta_token_verify_concern.rb.
* Wired it into instagram_controller.rb and whatsapp_controller.rb.
* WhatsApp now verifies X-Hub-Signature-256 with WHATSAPP_APP_SECRET.
* Instagram now verifies with either FB_APP_SECRET or
INSTAGRAM_APP_SECRET.
* Updated request specs so missing/invalid signatures return 401 and
valid signatures still enqueue jobs.


Fixes # (issue):
[CW-6786](https://linear.app/chatwoot/issue/CW-6786/ghsa-7rw7-pc8v-mrr3-unauthenticated-message-injection-via-missing)

## Type of change

Please delete options that are not relevant.

- [x] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality not to work as expected)
- [ ] This change requires a documentation update

## How Has This Been Tested?

* Updated the controller specs and ran them successfully.
* The original issue is no longer reproducible.


## Checklist:

- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [ ] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules

---------

Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
2026-05-05 15:01:11 +05:30

70 lines
2.2 KiB
Ruby

# ref: https://github.com/jgorset/facebook-messenger#make-a-configuration-provider
class ChatwootFbProvider < Facebook::Messenger::Configuration::Providers::Base
CHANNEL_APP_SECRET_KEYS = %w[app_secret app_secret_key client_secret api_secret].freeze
def valid_verify_token?(_verify_token)
GlobalConfigService.load('FB_VERIFY_TOKEN', '')
end
def app_secret_for(page_id)
channel_app_secret_for(page_id).presence || GlobalConfigService.load('FB_APP_SECRET', '')
end
def access_token_for(page_id)
Channel::FacebookPage.where(page_id: page_id).last.page_access_token
end
private
def channel_app_secret_for(page_id)
channel = Channel::FacebookPage.where(page_id: page_id).last
return if channel.blank?
channel_app_secret_candidates(channel).first
end
def channel_app_secret_candidates(channel)
secrets = []
secrets << channel.app_secret if channel.respond_to?(:app_secret)
secrets.concat(provider_config_app_secrets(channel))
secrets.compact_blank.uniq
end
def provider_config_app_secrets(channel)
return [] unless channel.respond_to?(:provider_config)
provider_config = channel.provider_config.to_h.with_indifferent_access
CHANNEL_APP_SECRET_KEYS.filter_map { |key| provider_config[key].presence }
end
def bot
Chatwoot::Bot
end
end
Rails.application.reloader.to_prepare do
Facebook::Messenger.configure do |config|
config.provider = ChatwootFbProvider.new
end
Facebook::Messenger::Bot.on :message do |message|
Webhooks::FacebookEventsJob.perform_later(message.to_json)
end
Facebook::Messenger::Bot.on :delivery do |delivery|
Rails.logger.info "Recieved delivery status #{delivery.to_json}"
Webhooks::FacebookDeliveryJob.perform_later(delivery.to_json)
end
Facebook::Messenger::Bot.on :read do |read|
Rails.logger.info "Recieved read status #{read.to_json}"
Webhooks::FacebookDeliveryJob.perform_later(read.to_json)
end
Facebook::Messenger::Bot.on :message_echo do |message|
# Add delay to prevent race condition where echo arrives before send message API completes
# This avoids duplicate messages when echo comes early during API processing
Webhooks::FacebookEventsJob.set(wait: 2.seconds).perform_later(message.to_json)
end
end