diff --git a/app/controllers/api/v1/accounts/concerns/whatsapp_health_management.rb b/app/controllers/api/v1/accounts/concerns/whatsapp_health_management.rb
new file mode 100644
index 000000000..795d7f2a9
--- /dev/null
+++ b/app/controllers/api/v1/accounts/concerns/whatsapp_health_management.rb
@@ -0,0 +1,55 @@
+module Api::V1::Accounts::Concerns::WhatsappHealthManagement
+ extend ActiveSupport::Concern
+
+ included do
+ skip_before_action :check_authorization, only: [:health, :register_webhook]
+ before_action :check_admin_authorization?, only: [:register_webhook]
+ before_action :validate_whatsapp_cloud_channel, only: [:health, :register_webhook]
+ end
+
+ def sync_templates
+ return render status: :unprocessable_entity, json: { error: 'Template sync is only available for WhatsApp channels' } unless whatsapp_channel?
+
+ trigger_template_sync
+ render status: :ok, json: { message: 'Template sync initiated successfully' }
+ rescue StandardError => e
+ render status: :internal_server_error, json: { error: e.message }
+ end
+
+ def health
+ health_data = Whatsapp::HealthService.new(@inbox.channel).fetch_health_status
+ render json: health_data
+ rescue StandardError => e
+ Rails.logger.error "[INBOX HEALTH] Error fetching health data: #{e.message}"
+ render json: { error: e.message }, status: :unprocessable_entity
+ end
+
+ def register_webhook
+ Whatsapp::WebhookSetupService.new(@inbox.channel).register_callback
+
+ render json: { message: 'Webhook registered successfully' }, status: :ok
+ rescue StandardError => e
+ Rails.logger.error "[INBOX WEBHOOK] Webhook registration failed: #{e.message}"
+ render json: { error: e.message }, status: :unprocessable_entity
+ end
+
+ private
+
+ def validate_whatsapp_cloud_channel
+ return if @inbox.channel.is_a?(Channel::Whatsapp) && @inbox.channel.provider == 'whatsapp_cloud'
+
+ render json: { error: 'Health data only available for WhatsApp Cloud API channels' }, status: :bad_request
+ end
+
+ def whatsapp_channel?
+ @inbox.whatsapp? || (@inbox.twilio? && @inbox.channel.whatsapp?)
+ end
+
+ def trigger_template_sync
+ if @inbox.whatsapp?
+ Channels::Whatsapp::TemplatesSyncJob.perform_later(@inbox.channel)
+ elsif @inbox.twilio? && @inbox.channel.whatsapp?
+ Channels::Twilio::TemplatesSyncJob.perform_later(@inbox.channel)
+ end
+ end
+end
diff --git a/app/controllers/api/v1/accounts/inboxes_controller.rb b/app/controllers/api/v1/accounts/inboxes_controller.rb
index 322c7c7fe..4ca9a6af8 100644
--- a/app/controllers/api/v1/accounts/inboxes_controller.rb
+++ b/app/controllers/api/v1/accounts/inboxes_controller.rb
@@ -4,8 +4,9 @@ class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController
before_action :fetch_agent_bot, only: [:set_agent_bot]
before_action :validate_limit, only: [:create]
# we are already handling the authorization in fetch inbox
- before_action :check_authorization, except: [:show, :health]
- before_action :validate_whatsapp_cloud_channel, only: [:health]
+ before_action :check_authorization, except: [:show]
+
+ include Api::V1::Accounts::Concerns::WhatsappHealthManagement
def index
@inboxes = policy_scope(Current.account.inboxes.order_by_name.includes(:channel, { avatar_attachment: [:blob] }))
@@ -70,23 +71,6 @@ class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController
render status: :ok, json: { message: I18n.t('messages.inbox_deletetion_response') }
end
- def sync_templates
- return render status: :unprocessable_entity, json: { error: 'Template sync is only available for WhatsApp channels' } unless whatsapp_channel?
-
- trigger_template_sync
- render status: :ok, json: { message: 'Template sync initiated successfully' }
- rescue StandardError => e
- render status: :internal_server_error, json: { error: e.message }
- end
-
- def health
- health_data = Whatsapp::HealthService.new(@inbox.channel).fetch_health_status
- render json: health_data
- rescue StandardError => e
- Rails.logger.error "[INBOX HEALTH] Error fetching health data: #{e.message}"
- render json: { error: e.message }, status: :unprocessable_entity
- end
-
private
def fetch_inbox
@@ -98,12 +82,6 @@ class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController
@agent_bot = AgentBot.find(params[:agent_bot]) if params[:agent_bot]
end
- def validate_whatsapp_cloud_channel
- return if @inbox.channel.is_a?(Channel::Whatsapp) && @inbox.channel.provider == 'whatsapp_cloud'
-
- render json: { error: 'Health data only available for WhatsApp Cloud API channels' }, status: :bad_request
- end
-
def create_channel
return unless allowed_channel_types.include?(permitted_params[:channel][:type])
@@ -200,18 +178,6 @@ class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController
def get_channel_attributes(channel_type)
channel_type.constantize.const_defined?(:EDITABLE_ATTRS) ? channel_type.constantize::EDITABLE_ATTRS.presence : []
end
-
- def whatsapp_channel?
- @inbox.whatsapp? || (@inbox.twilio? && @inbox.channel.whatsapp?)
- end
-
- def trigger_template_sync
- if @inbox.whatsapp?
- Channels::Whatsapp::TemplatesSyncJob.perform_later(@inbox.channel)
- elsif @inbox.twilio? && @inbox.channel.whatsapp?
- Channels::Twilio::TemplatesSyncJob.perform_later(@inbox.channel)
- end
- end
end
Api::V1::Accounts::InboxesController.prepend_mod_with('Api::V1::Accounts::InboxesController')
diff --git a/app/javascript/dashboard/api/inboxHealth.js b/app/javascript/dashboard/api/inboxHealth.js
index 181b041ba..b8f69fcfe 100644
--- a/app/javascript/dashboard/api/inboxHealth.js
+++ b/app/javascript/dashboard/api/inboxHealth.js
@@ -9,6 +9,10 @@ class InboxHealthAPI extends ApiClient {
getHealthStatus(inboxId) {
return axios.get(`${this.url}/${inboxId}/health`);
}
+
+ registerWebhook(inboxId) {
+ return axios.post(`${this.url}/${inboxId}/register_webhook`);
+ }
}
export default new InboxHealthAPI();
diff --git a/app/javascript/dashboard/i18n/locale/en/inboxMgmt.json b/app/javascript/dashboard/i18n/locale/en/inboxMgmt.json
index 8b982dc21..0455bdfa1 100644
--- a/app/javascript/dashboard/i18n/locale/en/inboxMgmt.json
+++ b/app/javascript/dashboard/i18n/locale/en/inboxMgmt.json
@@ -685,6 +685,16 @@
"SANDBOX": "Sandbox",
"LIVE": "Live"
}
+ },
+ "WEBHOOK": {
+ "TITLE": "Webhook Configuration",
+ "DESCRIPTION": "Webhook URL is required for your WhatsApp Business Account to receive messages from customers",
+ "ACTION_REQUIRED": "Webhook not configured",
+ "REGISTER_BUTTON": "Register Webhook",
+ "REGISTER_SUCCESS": "Webhook registered successfully",
+ "REGISTER_ERROR": "Failed to register webhook. Please try again.",
+ "CONFIGURED_SUCCESS": "Webhook configured successfully",
+ "URL_MISMATCH": "Webhook URL mismatch"
}
},
"SETTINGS": "Settings",
diff --git a/app/javascript/dashboard/routes/dashboard/settings/inbox/Settings.vue b/app/javascript/dashboard/routes/dashboard/settings/inbox/Settings.vue
index a3c0bceed..0ff1ac61e 100644
--- a/app/javascript/dashboard/routes/dashboard/settings/inbox/Settings.vue
+++ b/app/javascript/dashboard/routes/dashboard/settings/inbox/Settings.vue
@@ -99,6 +99,7 @@ export default {
healthData: null,
isLoadingHealth: false,
healthError: null,
+ isRegisteringWebhook: false,
widgetBubblePosition: 'right',
widgetBubbleType: 'standard',
widgetBubbleLauncherTitle: '',
@@ -424,6 +425,23 @@ export default {
this.isLoadingHealth = false;
}
},
+ async registerWebhook() {
+ if (!this.inbox) return;
+
+ try {
+ this.isRegisteringWebhook = true;
+ await InboxHealthAPI.registerWebhook(this.inbox.id);
+ useAlert(this.$t('INBOX_MGMT.ACCOUNT_HEALTH.WEBHOOK.REGISTER_SUCCESS'));
+ await this.fetchHealthData();
+ } catch (error) {
+ useAlert(
+ error.message ||
+ this.$t('INBOX_MGMT.ACCOUNT_HEALTH.WEBHOOK.REGISTER_ERROR')
+ );
+ } finally {
+ this.isRegisteringWebhook = false;
+ }
+ },
handleFeatureFlag(e) {
this.selectedFeatureFlags = this.toggleInput(
this.selectedFeatureFlags,
@@ -1162,7 +1180,11 @@ export default {