class Webhooks::Trigger SUPPORTED_ERROR_HANDLE_EVENTS = %w[message_created message_updated].freeze def initialize(url, payload, webhook_type, secret: nil, delivery_id: nil) @url = url @payload = payload @webhook_type = webhook_type @secret = secret @delivery_id = delivery_id end def self.execute(url, payload, webhook_type, secret: nil, delivery_id: nil) new(url, payload, webhook_type, secret: secret, delivery_id: delivery_id).execute end def execute perform_request rescue RestClient::TooManyRequests, RestClient::InternalServerError => e raise if @webhook_type == :agent_bot_webhook handle_failure(e) rescue StandardError => e handle_failure(e) end def handle_failure(error) handle_error(error) Rails.logger.warn "Exception: Invalid webhook URL #{@url} : #{error.message}" end private def perform_request body = @payload.to_json RestClient::Request.execute( method: :post, url: @url, payload: body, headers: request_headers(body), timeout: webhook_timeout ) end def request_headers(body) headers = { content_type: :json, accept: :json } headers['X-Chatwoot-Delivery'] = @delivery_id if @delivery_id.present? if @secret.present? ts = Time.now.to_i.to_s headers['X-Chatwoot-Timestamp'] = ts headers['X-Chatwoot-Signature'] = "sha256=#{OpenSSL::HMAC.hexdigest('SHA256', @secret, "#{ts}.#{body}")}" end headers end def handle_error(error) return unless SUPPORTED_ERROR_HANDLE_EVENTS.include?(@payload[:event]) return unless message case @webhook_type when :agent_bot_webhook update_conversation_status(message) when :api_inbox_webhook update_message_status(error) end end def update_conversation_status(message) conversation = message.conversation return unless conversation&.pending? return if conversation&.account&.keep_pending_on_bot_failure conversation.open! create_agent_bot_error_activity(conversation) end def create_agent_bot_error_activity(conversation) content = I18n.t('conversations.activity.agent_bot.error_moved_to_open') Conversations::ActivityMessageJob.perform_later(conversation, activity_message_params(conversation, content)) end def activity_message_params(conversation, content) { account_id: conversation.account_id, inbox_id: conversation.inbox_id, message_type: :activity, content: content } end def update_message_status(error) Messages::StatusUpdateService.new(message, 'failed', error.message).perform end def message return if message_id.blank? if defined?(@message) @message else @message = Message.find_by(id: message_id) end end def message_id @payload[:id] end def webhook_timeout raw_timeout = GlobalConfig.get_value('WEBHOOK_TIMEOUT') timeout = raw_timeout.presence&.to_i timeout&.positive? ? timeout : 5 end end