feat(whatsapp): add cloud template management token (#15218)

Chatwoot Cloud customers can now provide a dedicated WhatsApp business
management token when their Embedded Signup credential cannot access
message templates. Once validated, the token is stored securely and used
only for template synchronization.

Existing inboxes continue using their configured WhatsApp API key when
no business management token is present. Sending, receiving, webhooks,
phone-number health, and other WhatsApp operations remain unchanged.

### Things to know

- This option is available only on Chatwoot Cloud.
- Saving the token verifies that `whatsapp_business_management` is
granted through Meta's permissions endpoint; template synchronization
still verifies access to the configured WhatsApp Business Account.
- The token is encrypted using the existing external-credentials
encryption mechanism.
- Self-hosted installations continue using the existing API key flow.

### How to test

1. On Chatwoot Cloud, open a WhatsApp Cloud inbox and go to
**Configuration**.
2. Enter a token with `whatsapp_business_management` access and save it.
3. Confirm the token is accepted and the value is not exposed again in
the UI or API.
4. Select **Sync Templates** and confirm templates are fetched with the
saved business management token.
5. Remove the token and confirm template synchronization falls back to
the inbox API key.
6. Confirm the business management token controls are not shown on a
self-hosted installation.

### What changed

- Added an encrypted `business_management_token` credential to WhatsApp
channels.
- Added Cloud-only endpoints and UI controls to validate the required
permission, save, and remove the token.
- Added template-sync credential selection with API-key fallback.

---------

Co-authored-by: Muhsin <12408980+muhsin-k@users.noreply.github.com>
This commit is contained in:
Muhsin Keloth
2026-07-29 13:37:59 +04:00
committed by GitHub
parent 2e9423cbba
commit 59eac9a7c5
24 changed files with 786 additions and 26 deletions

View File

@@ -3,6 +3,7 @@
# Table name: channel_whatsapp
#
# id :bigint not null, primary key
# business_management_token :text
# message_templates :jsonb
# message_templates_last_updated :datetime
# phone_number :string not null
@@ -27,6 +28,7 @@ class Channel::Whatsapp < ApplicationRecord
self.table_name = 'channel_whatsapp'
EDITABLE_ATTRS = [:phone_number, :provider, { provider_config: {} }].freeze
encrypts :business_management_token if Chatwoot.encryption_configured?
# default at the moment is 360dialog lets change later.
PROVIDERS = %w[default whatsapp_cloud].freeze
@@ -74,6 +76,16 @@ class Channel::Whatsapp < ApplicationRecord
end
end
def template_access_token
return provider_config['api_key'] unless ChatwootApp.chatwoot_cloud? && provider_config['source'] == 'embedded_signup'
business_management_token.presence || provider_config['api_key']
end
def serializable_hash(options = nil)
super.except('business_management_token')
end
# Enables voice: turns calling on at Meta (idempotent), then re-registers webhooks
# with the in-memory calling_enabled flag so the `calls` field is subscribed. The
# flag is persisted only after registration succeeds, so a webhook failure can't