From b815eb9ce0e3cf26c1716f8f62a90f1891d03995 Mon Sep 17 00:00:00 2001 From: Muhsin Keloth Date: Thu, 2 Apr 2026 13:55:05 +0400 Subject: [PATCH] fix(agent-bot): Dispatch webhook event on agent bot assignment (#13975) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When an AgentBot is assigned to a conversation after the first message has already been received, the bot does not respond because it never receives any event. The `message_created` event fires before the bot is assigned, and the bot has no way to know it was assigned. Chatwoot already dispatches a `CONVERSATION_UPDATED` event when `assignee_agent_bot_id` changes, but `AgentBotListener` wasn't listening for it. This fix adds a `conversation_updated` handler so the bot receives a webhook with the conversation context when assigned. ## How to reproduce 1. Customer sends a message → conversation created, `message_created` fires 2. System processes the message (adds labels, custom attributes) 3. System assigns an AgentBot to the conversation via API 4. **Before fix:** Bot receives no event and never responds 5. **After fix:** Bot receives `conversation_updated` event with conversation payload ## What changed - **`AgentBotListener`**: Added `conversation_updated` handler that sends the conversation webhook payload to the assigned bot when the conversation is updated ## How to test 1. Create an AgentBot with an `outgoing_url` pointing to a webhook inspector (e.g. webhook.site) 2. Send a message to create a conversation 3. Assign the AgentBot to the conversation via API: ``` POST /api/v1/accounts/{id}/conversations/{id}/assignments { "assignee_id": , "assignee_type": "AgentBot" } ``` 4. Verify the bot receives a `conversation_updated` event at its webhook URL 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.6 (1M context) --- app/listeners/agent_bot_listener.rb | 8 ++++++ spec/listeners/agent_bot_listener_spec.rb | 33 +++++++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/app/listeners/agent_bot_listener.rb b/app/listeners/agent_bot_listener.rb index ccac8005f..8fdf964fb 100644 --- a/app/listeners/agent_bot_listener.rb +++ b/app/listeners/agent_bot_listener.rb @@ -15,6 +15,14 @@ class AgentBotListener < BaseListener agent_bots_for(inbox, conversation).each { |agent_bot| process_webhook_bot_event(agent_bot, payload) } end + def conversation_updated(event) + conversation = extract_conversation_and_account(event)[0] + inbox = conversation.inbox + event_name = __method__.to_s + payload = conversation.webhook_data.merge(event: event_name) + agent_bots_for(inbox, conversation).each { |agent_bot| process_webhook_bot_event(agent_bot, payload) } + end + def message_created(event) message = extract_message_and_account(event)[0] inbox = message.inbox diff --git a/spec/listeners/agent_bot_listener_spec.rb b/spec/listeners/agent_bot_listener_spec.rb index 56c478a1b..24af37383 100644 --- a/spec/listeners/agent_bot_listener_spec.rb +++ b/spec/listeners/agent_bot_listener_spec.rb @@ -57,6 +57,39 @@ describe AgentBotListener do end end + describe '#conversation_updated' do + let(:event_name) { 'conversation.updated' } + let!(:event) { Events::Base.new(event_name, Time.zone.now, conversation: conversation) } + + context 'when agent bot is not configured' do + it 'does not send webhook' do + expect(AgentBots::WebhookJob).not_to receive(:perform_later) + listener.conversation_updated(event) + end + end + + context 'when agent bot is configured on inbox' do + it 'sends webhook to the inbox agent bot' do + create(:agent_bot_inbox, inbox: inbox, agent_bot: agent_bot) + expect(AgentBots::WebhookJob).to receive(:perform_later).with(agent_bot.outgoing_url, + conversation.webhook_data.merge(event: 'conversation_updated')).once + listener.conversation_updated(event) + end + end + + context 'when conversation is assigned to an agent bot' do + before do + conversation.update!(assignee_agent_bot: agent_bot, assignee: nil) + end + + it 'sends webhook to the assigned agent bot' do + expect(AgentBots::WebhookJob).to receive(:perform_later).with(agent_bot.outgoing_url, + conversation.webhook_data.merge(event: 'conversation_updated')).once + listener.conversation_updated(event) + end + end + end + describe '#webwidget_triggered' do let(:event_name) { 'webwidget.triggered' }