This fixes the agent-bot webhook delivery path so transient upstream failures follow the expected delivery lifecycle. Existing fallback behavior is preserved, and fallback actions are applied only after delivery attempts are exhausted. To reproduce, configure an agent-bot webhook endpoint to return 429/500 for message events. Before this fix, failure handling could be applied too early; after this fix, delivery attempts complete first and then existing fallback handling runs. Tested with: - bundle exec rspec spec/jobs/agent_bots/webhook_job_spec.rb spec/lib/webhooks/trigger_spec.rb - bundle exec rubocop spec/jobs/agent_bots/webhook_job_spec.rb spec/lib/webhooks/trigger_spec.rb --------- Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
115 lines
2.9 KiB
Ruby
115 lines
2.9 KiB
Ruby
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
|