From f4d66566d0c80a7ae19de51abae5afead666c6cb Mon Sep 17 00:00:00 2001 From: Muhsin Keloth Date: Mon, 6 Apr 2026 11:14:09 +0400 Subject: [PATCH] fix(agent-bot): Include `changed_attributes` in conversation_updated webhook (#14001) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `conversation_updated` webhook sent to AgentBots did not include `changed_attributes`, making it impossible for bots to distinguish between different types of conversation updates (e.g. bot assignment vs label change vs status change). This aligns the AgentBot webhook payload with the existing `WebhookListener` behavior, which already includes `changed_attributes`. ## How to reproduce 1. Assign an AgentBot to a conversation 2. Then update the conversation (e.g. add a label) 3. **Before fix:** Both events arrive with identical payload structure — bot cannot tell them apart 4. **After fix:** Each event includes `changed_attributes` showing exactly what changed ## What changed - **`AgentBotListener#conversation_updated`**: Added `changed_attributes` to the webhook payload using `extract_changed_attributes` (same pattern as `WebhookListener`) ## How to test 1. Assign an AgentBot to a conversation via API 2. Check the webhook payload — should include: ```json "changed_attributes": [ { "assignee_agent_bot_id": { "previous_value": null, "current_value": 7 } } ] ``` 3. Update the conversation (e.g. add a label) 4. Check the webhook payload — `changed_attributes` should reflect the label change, not bot assignment 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Muhsin <12408980+muhsin-k@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 (1M context) --- app/listeners/agent_bot_listener.rb | 3 ++- spec/listeners/agent_bot_listener_spec.rb | 23 ++++++++++++++++++----- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/app/listeners/agent_bot_listener.rb b/app/listeners/agent_bot_listener.rb index 8fdf964fb..08ee563af 100644 --- a/app/listeners/agent_bot_listener.rb +++ b/app/listeners/agent_bot_listener.rb @@ -17,9 +17,10 @@ class AgentBotListener < BaseListener def conversation_updated(event) conversation = extract_conversation_and_account(event)[0] + changed_attributes = extract_changed_attributes(event) inbox = conversation.inbox event_name = __method__.to_s - payload = conversation.webhook_data.merge(event: event_name) + payload = conversation.webhook_data.merge(event: event_name, changed_attributes: changed_attributes) agent_bots_for(inbox, conversation).each { |agent_bot| process_webhook_bot_event(agent_bot, payload) } end diff --git a/spec/listeners/agent_bot_listener_spec.rb b/spec/listeners/agent_bot_listener_spec.rb index 24af37383..a9721f9c7 100644 --- a/spec/listeners/agent_bot_listener_spec.rb +++ b/spec/listeners/agent_bot_listener_spec.rb @@ -59,9 +59,10 @@ describe AgentBotListener do 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 + let!(:event) { Events::Base.new(event_name, Time.zone.now, conversation: conversation) } + it 'does not send webhook' do expect(AgentBots::WebhookJob).not_to receive(:perform_later) listener.conversation_updated(event) @@ -69,22 +70,34 @@ describe AgentBotListener do end context 'when agent bot is configured on inbox' do - it 'sends webhook to the inbox agent bot' do + let!(:event) { Events::Base.new(event_name, Time.zone.now, conversation: conversation) } + + it 'sends webhook to the inbox agent bot with changed_attributes' 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 + conversation.webhook_data.merge(event: 'conversation_updated', + changed_attributes: nil)).once listener.conversation_updated(event) end end context 'when conversation is assigned to an agent bot' do + let!(:event) do + Events::Base.new(event_name, Time.zone.now, conversation: conversation, + changed_attributes: { 'assignee_agent_bot_id' => [nil, agent_bot.id] }) + end + before do conversation.update!(assignee_agent_bot: agent_bot, assignee: nil) end - it 'sends webhook to the assigned agent bot' do + it 'sends webhook with changed_attributes to the assigned agent bot' do + expected_changed_attributes = [{ 'assignee_agent_bot_id' => { previous_value: nil, current_value: agent_bot.id } }] expect(AgentBots::WebhookJob).to receive(:perform_later).with(agent_bot.outgoing_url, - conversation.webhook_data.merge(event: 'conversation_updated')).once + conversation.webhook_data.merge( + event: 'conversation_updated', + changed_attributes: expected_changed_attributes + )).once listener.conversation_updated(event) end end