diff --git a/app/controllers/api/v1/accounts/agent_bots_controller.rb b/app/controllers/api/v1/accounts/agent_bots_controller.rb index c2f919659..de3d10081 100644 --- a/app/controllers/api/v1/accounts/agent_bots_controller.rb +++ b/app/controllers/api/v1/accounts/agent_bots_controller.rb @@ -34,6 +34,10 @@ class Api::V1::Accounts::AgentBotsController < Api::V1::Accounts::BaseController @agent_bot.reload end + def reset_secret + @agent_bot.reset_secret! + end + private def agent_bot diff --git a/app/controllers/api/v1/accounts/inboxes_controller.rb b/app/controllers/api/v1/accounts/inboxes_controller.rb index 4ca9a6af8..c7d3e2737 100644 --- a/app/controllers/api/v1/accounts/inboxes_controller.rb +++ b/app/controllers/api/v1/accounts/inboxes_controller.rb @@ -66,6 +66,12 @@ class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController head :ok end + def reset_secret + return head :not_found unless @inbox.api? + + @inbox.channel.reset_secret! + end + def destroy ::DeleteObjectJob.perform_later(@inbox, Current.user, request.ip) if @inbox.present? render status: :ok, json: { message: I18n.t('messages.inbox_deletetion_response') } diff --git a/app/javascript/dashboard/api/agentBots.js b/app/javascript/dashboard/api/agentBots.js index de887f415..a16b252de 100644 --- a/app/javascript/dashboard/api/agentBots.js +++ b/app/javascript/dashboard/api/agentBots.js @@ -25,6 +25,10 @@ class AgentBotsAPI extends ApiClient { resetAccessToken(botId) { return axios.post(`${this.url}/${botId}/reset_access_token`); } + + resetSecret(botId) { + return axios.post(`${this.url}/${botId}/reset_secret`); + } } export default new AgentBotsAPI(); diff --git a/app/javascript/dashboard/api/inboxes.js b/app/javascript/dashboard/api/inboxes.js index 079f21815..cc564fe96 100644 --- a/app/javascript/dashboard/api/inboxes.js +++ b/app/javascript/dashboard/api/inboxes.js @@ -48,6 +48,10 @@ class Inboxes extends CacheEnabledApiClient { template, }); } + + resetSecret(inboxId) { + return axios.post(`${this.url}/${inboxId}/reset_secret`); + } } export default new Inboxes(); diff --git a/app/javascript/dashboard/i18n/locale/en/agentBots.json b/app/javascript/dashboard/i18n/locale/en/agentBots.json index dc92016ab..c17ec60d0 100644 --- a/app/javascript/dashboard/i18n/locale/en/agentBots.json +++ b/app/javascript/dashboard/i18n/locale/en/agentBots.json @@ -63,6 +63,16 @@ "ERROR_MESSAGE": "Could not update bot. Please try again." } }, + "SECRET": { + "LABEL": "Webhook Secret", + "COPY": "Copy secret to clipboard", + "COPY_SUCCESS": "Secret copied to clipboard", + "TOGGLE": "Toggle secret visibility", + "CREATED_DESC": "Use the secret below to verify webhook signatures. Please copy it now, you can also find it later in the bot settings.", + "DONE": "Done", + "RESET_SUCCESS": "Webhook secret regenerated successfully", + "RESET_ERROR": "Unable to regenerate webhook secret. Please try again" + }, "ACCESS_TOKEN": { "TITLE": "Access Token", "DESCRIPTION": "Copy the access token and save it securely", diff --git a/app/javascript/dashboard/i18n/locale/en/inboxMgmt.json b/app/javascript/dashboard/i18n/locale/en/inboxMgmt.json index d0d1573b3..51f855689 100644 --- a/app/javascript/dashboard/i18n/locale/en/inboxMgmt.json +++ b/app/javascript/dashboard/i18n/locale/en/inboxMgmt.json @@ -86,6 +86,14 @@ "PLACEHOLDER": "Please enter your Webhook URL", "ERROR": "Please enter a valid URL" }, + "CHANNEL_WEBHOOK_SECRET": { + "LABEL": "Webhook Secret", + "COPY": "Copy secret to clipboard", + "COPY_SUCCESS": "Secret copied to clipboard", + "TOGGLE": "Toggle secret visibility", + "RESET_SUCCESS": "Webhook secret regenerated successfully", + "RESET_ERROR": "Unable to regenerate webhook secret. Please try again" + }, "CHANNEL_DOMAIN": { "LABEL": "Website Domain", "PLACEHOLDER": "Enter your website domain (eg: acme.com)" diff --git a/app/javascript/dashboard/routes/dashboard/settings/agentBots/components/AgentBotModal.vue b/app/javascript/dashboard/routes/dashboard/settings/agentBots/components/AgentBotModal.vue index be4deb337..50baafc6a 100644 --- a/app/javascript/dashboard/routes/dashboard/settings/agentBots/components/AgentBotModal.vue +++ b/app/javascript/dashboard/routes/dashboard/settings/agentBots/components/AgentBotModal.vue @@ -47,6 +47,7 @@ const formState = reactive({ const [showAccessToken, toggleAccessToken] = useToggle(); const accessToken = ref(''); +const botSecret = ref(''); const v$ = useVuelidate( { @@ -179,15 +180,21 @@ const handleSubmit = async () => { : t('AGENT_BOTS.EDIT.API.SUCCESS_MESSAGE'); useAlert(alertKey); - // Show access token after creation + // Show access token and secret after creation if (isCreate) { - const { access_token: responseAccessToken, id } = response || {}; + const { + access_token: responseAccessToken, + secret: responseSecret, + id, + } = response || {}; if (id && responseAccessToken) { accessToken.value = responseAccessToken; + botSecret.value = responseSecret || ''; toggleAccessToken(true); } else { accessToken.value = ''; + botSecret.value = ''; dialogRef.value.close(); } } else { @@ -212,14 +219,16 @@ const initializeForm = () => { thumbnail, bot_config: botConfig, access_token: botAccessToken, + secret: botSecretValue, } = props.selectedBot; formState.botName = name || ''; formState.botDescription = description || ''; formState.botUrl = botUrl || botConfig?.webhook_url || ''; formState.botAvatarUrl = thumbnail || ''; - if (botAccessToken && props.type === MODAL_TYPES.EDIT) { - accessToken.value = botAccessToken; + if (props.type === MODAL_TYPES.EDIT) { + if (botAccessToken) accessToken.value = botAccessToken; + if (botSecretValue) botSecret.value = botSecretValue; } } else { resetForm(); @@ -231,6 +240,24 @@ const onCopyToken = async value => { useAlert(t('AGENT_BOTS.ACCESS_TOKEN.COPY_SUCCESSFUL')); }; +const onCopySecret = async value => { + await copyTextToClipboard(value || botSecret.value); + useAlert(t('AGENT_BOTS.SECRET.COPY_SUCCESS')); +}; + +const onResetSecret = async () => { + const response = await store.dispatch( + 'agentBots/resetSecret', + props.selectedBot.id + ); + if (response) { + botSecret.value = response.secret; + useAlert(t('AGENT_BOTS.SECRET.RESET_SUCCESS')); + } else { + useAlert(t('AGENT_BOTS.SECRET.RESET_ERROR')); + } +}; + const onResetToken = async () => { const response = await store.dispatch( 'agentBots/resetAccessToken', @@ -247,6 +274,7 @@ const onResetToken = async () => { const closeModal = () => { if (!showAccessToken.value) v$.value?.$reset(); accessToken.value = ''; + botSecret.value = ''; toggleAccessToken(false); }; @@ -318,6 +346,20 @@ defineExpose({ dialogRef }); /> +
+ + +
+
+
+

+ {{ $t('AGENT_BOTS.SECRET.CREATED_DESC') }} +

+ + +
+
+ + + + { + try { + const response = await AgentBotsAPI.resetSecret(botId); + commit(types.EDIT_AGENT_BOT, response.data); + return response.data; + } catch (error) { + throwErrorMessage(error); + return null; + } + }, }; export const mutations = { diff --git a/app/javascript/dashboard/store/modules/inboxes.js b/app/javascript/dashboard/store/modules/inboxes.js index 3f374dce2..ce24ef653 100644 --- a/app/javascript/dashboard/store/modules/inboxes.js +++ b/app/javascript/dashboard/store/modules/inboxes.js @@ -366,6 +366,16 @@ export const actions = { ); return response.data; }, + resetSecret: async ({ commit }, inboxId) => { + try { + const response = await InboxesAPI.resetSecret(inboxId); + commit(types.default.EDIT_INBOXES, response.data); + return response.data; + } catch (error) { + throwErrorMessage(error); + return null; + } + }, }; export const mutations = { diff --git a/app/jobs/agent_bots/webhook_job.rb b/app/jobs/agent_bots/webhook_job.rb index d752034a7..4ba67c9bf 100644 --- a/app/jobs/agent_bots/webhook_job.rb +++ b/app/jobs/agent_bots/webhook_job.rb @@ -2,11 +2,13 @@ class AgentBots::WebhookJob < WebhookJob queue_as :high retry_on RestClient::TooManyRequests, RestClient::InternalServerError, wait: 3.seconds, attempts: 3 do |job, error| url, payload, webhook_type = job.arguments - Webhooks::Trigger.new(url, payload, webhook_type || :agent_bot_webhook).handle_failure(error) + kwargs = job.arguments.last.is_a?(Hash) ? job.arguments.last : {} + Webhooks::Trigger.new(url, payload, webhook_type || :agent_bot_webhook, secret: kwargs[:secret], + delivery_id: kwargs[:delivery_id]).handle_failure(error) end - def perform(url, payload, webhook_type = :agent_bot_webhook) - super(url, payload, webhook_type) + def perform(url, payload, webhook_type = :agent_bot_webhook, secret: nil, delivery_id: nil) + super(url, payload, webhook_type, secret: secret, delivery_id: delivery_id) rescue RestClient::TooManyRequests, RestClient::InternalServerError => e Rails.logger.warn("[AgentBots::WebhookJob] attempt #{executions} failed #{e.class.name} payload=#{payload.to_json}") raise diff --git a/app/listeners/agent_bot_listener.rb b/app/listeners/agent_bot_listener.rb index 08ee563af..1492f50ac 100644 --- a/app/listeners/agent_bot_listener.rb +++ b/app/listeners/agent_bot_listener.rb @@ -76,6 +76,7 @@ class AgentBotListener < BaseListener def process_webhook_bot_event(agent_bot, payload) return if agent_bot.outgoing_url.blank? - AgentBots::WebhookJob.perform_later(agent_bot.outgoing_url, payload) + AgentBots::WebhookJob.perform_later(agent_bot.outgoing_url, payload, :agent_bot_webhook, + secret: agent_bot.secret, delivery_id: SecureRandom.uuid) end end diff --git a/app/listeners/webhook_listener.rb b/app/listeners/webhook_listener.rb index 762eaa6ee..835d03661 100644 --- a/app/listeners/webhook_listener.rb +++ b/app/listeners/webhook_listener.rb @@ -122,7 +122,7 @@ class WebhookListener < BaseListener return if inbox.channel.webhook_url.blank? WebhookJob.perform_later(inbox.channel.webhook_url, payload, :api_inbox_webhook, - delivery_id: SecureRandom.uuid) + secret: inbox.channel.secret, delivery_id: SecureRandom.uuid) end def deliver_webhook_payloads(payload, inbox) diff --git a/app/models/agent_bot.rb b/app/models/agent_bot.rb index b839f21b4..63f71615d 100644 --- a/app/models/agent_bot.rb +++ b/app/models/agent_bot.rb @@ -8,6 +8,7 @@ # description :string # name :string # outgoing_url :string +# secret :string # created_at :datetime not null # updated_at :datetime not null # account_id :bigint @@ -21,6 +22,8 @@ class AgentBot < ApplicationRecord include AccessTokenable include Avatarable + include WebhookSecretable + scope :accessible_to, lambda { |account| account_id = account&.id where(account_id: [nil, account_id]) @@ -63,3 +66,5 @@ class AgentBot < ApplicationRecord account.nil? end end + +AgentBot.include_mod_with('Audit::AgentBot') diff --git a/app/models/channel/api.rb b/app/models/channel/api.rb index 17270e560..96d0eb8a5 100644 --- a/app/models/channel/api.rb +++ b/app/models/channel/api.rb @@ -7,6 +7,7 @@ # hmac_mandatory :boolean default(FALSE) # hmac_token :string # identifier :string +# secret :string # webhook_url :string # created_at :datetime not null # updated_at :datetime not null @@ -26,6 +27,7 @@ class Channel::Api < ApplicationRecord has_secure_token :identifier has_secure_token :hmac_token + include WebhookSecretable validate :ensure_valid_agent_reply_time_window validates :webhook_url, length: { maximum: Limits::URL_LENGTH_LIMIT } diff --git a/app/models/concerns/webhook_secretable.rb b/app/models/concerns/webhook_secretable.rb new file mode 100644 index 000000000..b60c9b825 --- /dev/null +++ b/app/models/concerns/webhook_secretable.rb @@ -0,0 +1,13 @@ +module WebhookSecretable + extend ActiveSupport::Concern + + included do + has_secure_token :secret + encrypts :secret if Chatwoot.encryption_configured? + end + + def reset_secret! + regenerate_secret + reload + end +end diff --git a/app/models/webhook.rb b/app/models/webhook.rb index 9586e1053..9ee62f11e 100644 --- a/app/models/webhook.rb +++ b/app/models/webhook.rb @@ -22,8 +22,7 @@ class Webhook < ApplicationRecord belongs_to :account belongs_to :inbox, optional: true - has_secure_token :secret - encrypts :secret if Chatwoot.encryption_configured? + include WebhookSecretable validates :account_id, presence: true validates :url, uniqueness: { scope: [:account_id] }, format: URI::DEFAULT_PARSER.make_regexp(%w[http https]) diff --git a/app/policies/agent_bot_policy.rb b/app/policies/agent_bot_policy.rb index 7461f6b2d..516f8dee8 100644 --- a/app/policies/agent_bot_policy.rb +++ b/app/policies/agent_bot_policy.rb @@ -26,4 +26,8 @@ class AgentBotPolicy < ApplicationPolicy def reset_access_token? @account_user.administrator? end + + def reset_secret? + @account_user.administrator? + end end diff --git a/app/policies/inbox_policy.rb b/app/policies/inbox_policy.rb index cace85e5e..d77b183ee 100644 --- a/app/policies/inbox_policy.rb +++ b/app/policies/inbox_policy.rb @@ -65,4 +65,8 @@ class InboxPolicy < ApplicationPolicy def health? @account_user.administrator? end + + def reset_secret? + @account_user.administrator? + end end diff --git a/app/views/api/v1/accounts/agent_bots/reset_secret.json.jbuilder b/app/views/api/v1/accounts/agent_bots/reset_secret.json.jbuilder new file mode 100644 index 000000000..f647ac383 --- /dev/null +++ b/app/views/api/v1/accounts/agent_bots/reset_secret.json.jbuilder @@ -0,0 +1 @@ +json.partial! 'api/v1/models/agent_bot', formats: [:json], resource: AgentBotPresenter.new(@agent_bot) diff --git a/app/views/api/v1/accounts/inboxes/reset_secret.json.jbuilder b/app/views/api/v1/accounts/inboxes/reset_secret.json.jbuilder new file mode 100644 index 000000000..2ad94ff82 --- /dev/null +++ b/app/views/api/v1/accounts/inboxes/reset_secret.json.jbuilder @@ -0,0 +1 @@ +json.partial! 'api/v1/models/inbox', formats: [:json], resource: @inbox diff --git a/app/views/api/v1/models/_agent_bot.json.jbuilder b/app/views/api/v1/models/_agent_bot.json.jbuilder index 2547bb2a8..d5dbc91b4 100644 --- a/app/views/api/v1/models/_agent_bot.json.jbuilder +++ b/app/views/api/v1/models/_agent_bot.json.jbuilder @@ -7,4 +7,5 @@ json.bot_type resource.bot_type json.bot_config resource.bot_config json.account_id resource.account_id json.access_token resource.access_token if resource.access_token.present? +json.secret resource.secret if !resource.system_bot? && Current.account_user&.administrator? json.system_bot resource.system_bot? diff --git a/app/views/api/v1/models/_inbox.json.jbuilder b/app/views/api/v1/models/_inbox.json.jbuilder index f0b5b1ea8..619e6a28c 100644 --- a/app/views/api/v1/models/_inbox.json.jbuilder +++ b/app/views/api/v1/models/_inbox.json.jbuilder @@ -113,6 +113,7 @@ end ## API Channel Attributes if resource.api? json.hmac_token resource.channel.try(:hmac_token) if Current.account_user&.administrator? + json.secret resource.channel.try(:secret) if Current.account_user&.administrator? json.webhook_url resource.channel.try(:webhook_url) json.inbox_identifier resource.channel.try(:identifier) json.additional_attributes resource.channel.try(:additional_attributes) diff --git a/config/routes.rb b/config/routes.rb index 58c574efe..3e868d6d8 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -88,6 +88,7 @@ Rails.application.routes.draw do resources :agent_bots, only: [:index, :create, :show, :update, :destroy] do delete :avatar, on: :member post :reset_access_token, on: :member + post :reset_secret, on: :member end resources :contact_inboxes, only: [] do collection do @@ -221,6 +222,7 @@ Rails.application.routes.draw do post :sync_templates, on: :member get :health, on: :member post :register_webhook, on: :member + post :reset_secret, on: :member if ChatwootApp.enterprise? resource :conference, only: %i[create destroy], controller: 'conference' do get :token, on: :member diff --git a/db/migrate/20260324070820_add_secret_to_agent_bots.rb b/db/migrate/20260324070820_add_secret_to_agent_bots.rb new file mode 100644 index 000000000..69e1bb314 --- /dev/null +++ b/db/migrate/20260324070820_add_secret_to_agent_bots.rb @@ -0,0 +1,5 @@ +class AddSecretToAgentBots < ActiveRecord::Migration[7.1] + def change + add_column :agent_bots, :secret, :string + end +end diff --git a/db/migrate/20260324070828_add_secret_to_channel_api.rb b/db/migrate/20260324070828_add_secret_to_channel_api.rb new file mode 100644 index 000000000..217e4f1c8 --- /dev/null +++ b/db/migrate/20260324070828_add_secret_to_channel_api.rb @@ -0,0 +1,5 @@ +class AddSecretToChannelApi < ActiveRecord::Migration[7.1] + def change + add_column :channel_api, :secret, :string + end +end diff --git a/db/migrate/20260324070835_backfill_agent_bot_and_channel_api_secrets.rb b/db/migrate/20260324070835_backfill_agent_bot_and_channel_api_secrets.rb new file mode 100644 index 000000000..ee89b8c02 --- /dev/null +++ b/db/migrate/20260324070835_backfill_agent_bot_and_channel_api_secrets.rb @@ -0,0 +1,15 @@ +class BackfillAgentBotAndChannelApiSecrets < ActiveRecord::Migration[7.1] + def up + AgentBot.where(secret: nil).find_each do |agent_bot| + agent_bot.update!(secret: SecureRandom.urlsafe_base64(24)) + end + + Channel::Api.where(secret: nil).find_each do |channel| + channel.update!(secret: SecureRandom.urlsafe_base64(24)) + end + end + + def down + # no-op: removing the columns in the previous migrations handles cleanup + end +end diff --git a/db/schema.rb b/db/schema.rb index c8af2be3e..d0993a55b 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -131,6 +131,7 @@ ActiveRecord::Schema[7.1].define(version: 2026_03_24_102005) do t.bigint "account_id" t.integer "bot_type", default: 0 t.jsonb "bot_config", default: {} + t.string "secret" t.index ["account_id"], name: "index_agent_bots_on_account_id" end @@ -413,6 +414,7 @@ ActiveRecord::Schema[7.1].define(version: 2026_03_24_102005) do t.string "hmac_token" t.boolean "hmac_mandatory", default: false t.jsonb "additional_attributes", default: {} + t.string "secret" t.index ["hmac_token"], name: "index_channel_api_on_hmac_token", unique: true t.index ["identifier"], name: "index_channel_api_on_identifier", unique: true end diff --git a/enterprise/app/models/enterprise/audit/agent_bot.rb b/enterprise/app/models/enterprise/audit/agent_bot.rb new file mode 100644 index 000000000..255af7fa1 --- /dev/null +++ b/enterprise/app/models/enterprise/audit/agent_bot.rb @@ -0,0 +1,7 @@ +module Enterprise::Audit::AgentBot + extend ActiveSupport::Concern + + included do + audited associated_with: :account, except: [:secret] + end +end diff --git a/enterprise/app/models/enterprise/channelable.rb b/enterprise/app/models/enterprise/channelable.rb index 6fcae73d8..100f1024d 100644 --- a/enterprise/app/models/enterprise/channelable.rb +++ b/enterprise/app/models/enterprise/channelable.rb @@ -17,7 +17,7 @@ module Enterprise::Channelable auditable_id = inbox.id auditable_type = 'Inbox' - audited_changes = saved_changes.except('updated_at') + audited_changes = saved_changes.except('updated_at', 'secret') return if audited_changes.blank? diff --git a/spec/listeners/agent_bot_listener_spec.rb b/spec/listeners/agent_bot_listener_spec.rb index a9721f9c7..6ab4d3b84 100644 --- a/spec/listeners/agent_bot_listener_spec.rb +++ b/spec/listeners/agent_bot_listener_spec.rb @@ -25,8 +25,10 @@ describe AgentBotListener do context 'when agent bot is configured' do it 'sends message to 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, - message.webhook_data.merge(event: 'message_created')).once + expect(AgentBots::WebhookJob).to receive(:perform_later).with( + agent_bot.outgoing_url, message.webhook_data.merge(event: 'message_created'), + :agent_bot_webhook, secret: agent_bot.secret, delivery_id: instance_of(String) + ).once listener.message_created(event) end @@ -48,8 +50,14 @@ describe AgentBotListener do it 'sends message to both bots exactly once' do payload = message.webhook_data.merge(event: 'message_created') - expect(AgentBots::WebhookJob).to receive(:perform_later).with(agent_bot.outgoing_url, payload).once - expect(AgentBots::WebhookJob).to receive(:perform_later).with(conversation_bot.outgoing_url, payload).once + expect(AgentBots::WebhookJob).to receive(:perform_later).with( + agent_bot.outgoing_url, payload, :agent_bot_webhook, + secret: agent_bot.secret, delivery_id: instance_of(String) + ).once + expect(AgentBots::WebhookJob).to receive(:perform_later).with( + conversation_bot.outgoing_url, payload, :agent_bot_webhook, + secret: conversation_bot.secret, delivery_id: instance_of(String) + ).once listener.message_created(event) end @@ -74,9 +82,11 @@ describe AgentBotListener do 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', - changed_attributes: nil)).once + expect(AgentBots::WebhookJob).to receive(:perform_later).with( + agent_bot.outgoing_url, + conversation.webhook_data.merge(event: 'conversation_updated', changed_attributes: nil), + :agent_bot_webhook, secret: agent_bot.secret, delivery_id: instance_of(String) + ).once listener.conversation_updated(event) end end @@ -93,11 +103,14 @@ describe AgentBotListener 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', - changed_attributes: expected_changed_attributes - )).once + expect(AgentBots::WebhookJob).to receive(:perform_later).with( + agent_bot.outgoing_url, + conversation.webhook_data.merge( + event: 'conversation_updated', + changed_attributes: expected_changed_attributes + ), + :agent_bot_webhook, secret: agent_bot.secret, delivery_id: instance_of(String) + ).once listener.conversation_updated(event) end end @@ -121,7 +134,8 @@ describe AgentBotListener do expect(AgentBots::WebhookJob).to receive(:perform_later) .with( agent_bot.outgoing_url, - conversation.contact_inbox.webhook_data.merge(event: 'webwidget_triggered', event_info: { country: 'US' }) + conversation.contact_inbox.webhook_data.merge(event: 'webwidget_triggered', event_info: { country: 'US' }), + :agent_bot_webhook, secret: agent_bot.secret, delivery_id: instance_of(String) ).once listener.webwidget_triggered(event) diff --git a/spec/listeners/webhook_listener_spec.rb b/spec/listeners/webhook_listener_spec.rb index 51dae239b..a7a64f175 100644 --- a/spec/listeners/webhook_listener_spec.rb +++ b/spec/listeners/webhook_listener_spec.rb @@ -59,7 +59,7 @@ describe WebhookListener do api_event = Events::Base.new(event_name, Time.zone.now, message: api_message) expect(WebhookJob).to receive(:perform_later).with( channel_api.webhook_url, api_message.webhook_data.merge(event: 'message_created'), - :api_inbox_webhook, delivery_id: instance_of(String) + :api_inbox_webhook, secret: channel_api.secret, delivery_id: instance_of(String) ).once listener.message_created(api_event) end @@ -112,7 +112,7 @@ describe WebhookListener do expect(WebhookJob).to receive(:perform_later).with( channel_api.webhook_url, api_conversation.webhook_data.merge(event: 'conversation_created'), - :api_inbox_webhook, delivery_id: instance_of(String) + :api_inbox_webhook, secret: channel_api.secret, delivery_id: instance_of(String) ).once listener.conversation_created(api_event) end @@ -348,7 +348,7 @@ describe WebhookListener do expect(WebhookJob).to receive(:perform_later).with( channel_api.webhook_url, payload, :api_inbox_webhook, - delivery_id: instance_of(String) + secret: channel_api.secret, delivery_id: instance_of(String) ).once listener.conversation_typing_on(api_event) end