fix(agent-bot): Dispatch webhook event on agent bot assignment (#13975)

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": <bot_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) <noreply@anthropic.com>
This commit is contained in:
Muhsin Keloth
2026-04-02 13:55:05 +04:00
committed by GitHub
parent b3d0af84c4
commit b815eb9ce0
2 changed files with 41 additions and 0 deletions

View File

@@ -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

View File

@@ -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' }