fix: include account data in webhook payloads (#12445)

Conversation and inbox webhook payloads now include the account object
(`{ id, name }`) so integrations can reliably identify the account
without depending on nested message data.

## Closes

Closes #11753
Closes #12442

## Why

Message, contact, and contact inbox webhook payloads already expose
`account`. Conversation and inbox webhook payloads were outliers, which
forced webhook consumers to infer account context from nested messages
or other fields that may not always be present.

## What changed

- Adds `account: { id, name }` to conversation webhook payloads.
- Adds webhook-specific inbox payload data with `account: { id, name }`
for inbox created/updated events.
- Keeps non-webhook conversation and inbox push payload behavior
unchanged.

## How to test

- Configure an account webhook for `conversation_created`, trigger a new
conversation, and confirm the webhook payload includes `account.id` and
`account.name`.
- Configure an account webhook for `inbox_created`, create a new inbox,
and confirm the webhook payload includes `account.id` and
`account.name`.
- Configure a webhook agent bot and update a conversation status, then
confirm the bot webhook payload includes the same account object.

---------

Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
Co-authored-by: Sojan Jose <sojan@pepalo.com>
This commit is contained in:
Tony Vincent
2026-06-09 12:12:46 +05:30
committed by GitHub
co-authored by Muhsin Keloth Sojan Jose
parent d1c482cb64
commit af1dfc21f6
6 changed files with 57 additions and 6 deletions
+2 -2
View File
@@ -68,7 +68,7 @@ class WebhookListener < BaseListener
def inbox_created(event)
inbox, account = extract_inbox_and_account(event)
inbox_webhook_data = Inbox::EventDataPresenter.new(inbox).push_data
inbox_webhook_data = Inbox::EventDataPresenter.new(inbox).webhook_data
payload = inbox_webhook_data.merge(event: __method__.to_s)
deliver_account_webhooks(payload, account)
end
@@ -78,7 +78,7 @@ class WebhookListener < BaseListener
changed_attributes = extract_changed_attributes(event)
return if changed_attributes.blank?
inbox_webhook_data = Inbox::EventDataPresenter.new(inbox).push_data
inbox_webhook_data = Inbox::EventDataPresenter.new(inbox).webhook_data
payload = inbox_webhook_data.merge(event: __method__.to_s, changed_attributes: changed_attributes)
deliver_account_webhooks(payload, account)
end
@@ -23,7 +23,10 @@ class Conversations::EventDataPresenter < SimpleDelegator
# Like #push_data but with message text normalized for external integrations (webhooks).
def webhook_data
push_data.merge(messages: webhook_push_messages)
push_data.merge(
account: account.webhook_data,
messages: webhook_push_messages
)
end
private
@@ -32,4 +32,8 @@ class Inbox::EventDataPresenter < SimpleDelegator
channel: channel
}
end
def webhook_data
push_data.merge(account: account.webhook_data)
end
end
+19 -1
View File
@@ -82,7 +82,7 @@ describe AgentBotListener do
create(:agent_bot_inbox, inbox: inbox, agent_bot: agent_bot)
expect(AgentBots::WebhookJob).to receive(:perform_later).with(
agent_bot.outgoing_url,
hash_including(event: 'conversation_status_changed', changed_attributes: anything),
hash_including(event: 'conversation_status_changed', account: account.webhook_data, changed_attributes: anything),
:agent_bot_webhook,
hash_including(secret: agent_bot.secret)
).once
@@ -158,6 +158,24 @@ describe AgentBotListener do
end
end
describe '#conversation_resolved' do
let(:event_name) { 'conversation.resolved' }
let!(:event) { Events::Base.new(event_name, Time.zone.now, conversation: conversation) }
context 'when agent bot is configured' do
it 'sends account details in the conversation payload' do
create(:agent_bot_inbox, inbox: inbox, agent_bot: agent_bot)
expect(AgentBots::WebhookJob).to receive(:perform_later).with(
agent_bot.outgoing_url,
hash_including(event: 'conversation_resolved', account: account.webhook_data),
:agent_bot_webhook,
hash_including(secret: agent_bot.secret)
).once
listener.conversation_resolved(event)
end
end
end
describe '#webwidget_triggered' do
let(:event_name) { 'webwidget.triggered' }
+24 -2
View File
@@ -101,6 +101,17 @@ describe WebhookListener do
).once
listener.conversation_created(conversation_created_event)
end
it 'includes account details in the conversation payload' do
webhook = create(:webhook, inbox: inbox, account: account)
expect(WebhookJob).to receive(:perform_later).with(
webhook.url,
hash_including(account: account.webhook_data),
:account_webhook,
hash_including(secret: webhook.secret)
).once
listener.conversation_created(conversation_created_event)
end
end
context 'when inbox is an API Channel' do
@@ -250,7 +261,7 @@ describe WebhookListener do
context 'when webhook is configured' do
it 'triggers webhook' do
inbox_data = Inbox::EventDataPresenter.new(inbox).push_data
inbox_data = Inbox::EventDataPresenter.new(inbox).webhook_data
webhook = create(:webhook, account: account, subscriptions: ['inbox_created'])
expect(WebhookJob).to receive(:perform_later).with(
webhook.url, inbox_data.merge(event: 'inbox_created'), :account_webhook,
@@ -258,6 +269,17 @@ describe WebhookListener do
).once
listener.inbox_created(inbox_created_event)
end
it 'includes account details in the inbox payload' do
webhook = create(:webhook, account: account, subscriptions: ['inbox_created'])
expect(WebhookJob).to receive(:perform_later).with(
webhook.url,
hash_including(account: account.webhook_data),
:account_webhook,
hash_including(secret: webhook.secret)
).once
listener.inbox_created(inbox_created_event)
end
end
end
@@ -287,7 +309,7 @@ describe WebhookListener do
it 'triggers webhook' do
webhook = create(:webhook, account: account, subscriptions: ['inbox_updated'])
inbox_data = Inbox::EventDataPresenter.new(inbox).push_data
inbox_data = Inbox::EventDataPresenter.new(inbox).webhook_data
changed_attributes_data = [{ 'name' => { 'previous_value': 'Inbox 1', 'current_value': inbox.name } }]
expect(WebhookJob).to receive(:perform_later).with(
@@ -46,6 +46,10 @@ RSpec.describe Conversations::EventDataPresenter do
end
describe '#webhook_data' do
it 'includes account details for webhook consumers' do
expect(presenter.webhook_data[:account]).to eq(conversation.account.webhook_data)
end
it 'normalizes hard-break backslashes in message content' do
message = create(:message, conversation: conversation, account: conversation.account,
message_type: :outgoing, content: "Hello\\\nWorld")