Commit Graph

19 Commits

Author SHA1 Message Date
Gabriel Jablonski
e032fc7774
feat(whatsapp): convert inbox between WhatsApp providers (#268)
* feat(whatsapp): allow converting inbox between WhatsApp providers

Adds a Convert flow to switch a WhatsApp inbox between the four
supported providers (default/360dialog, whatsapp_cloud, baileys, zapi)
without losing conversations, agents, or history.

- Channel::Whatsapp#convert_provider! runs inside a transaction:
  disconnects the old provider, clears provider_connection and
  message_templates, assigns the new provider/config, and triggers
  webhook setup plus template resync on the new service.
- New POST /api/v1/accounts/:id/inboxes/:id/convert_provider endpoint
  guarded by InboxPolicy#convert_provider? (admin only).
- UI adds a Convert button on the inbox Settings page with a
  type-to-confirm ConvertInboxModal that lists the effects before
  redirecting to a dedicated route reusing the WhatsApp provider
  wizard in convert mode (phone number locked, current provider
  hidden from the picker).

* chore(whatsapp): polish convert UI colors and expand specs

- Settings: use slate for the Convert trigger and ruby for the modal
  confirm to mirror the delete gate instead of the less conventional
  amber variant.
- Drop the redundant "current provider is hidden from the list"
  sentence from the convert wizard description.
- Add specs for the post-conversion webhook setup path (triggered and
  skipped branches) and the sync_templates error-rescue behaviour.

* fix: address CodeRabbit review on convert-provider flow

- Whitelist provider_config keys in the convert endpoint via permit
  rather than permit!, and default to an empty hash when omitted so
  the request no longer crashes.
- Pre-validate the new provider config before disconnecting the old
  session so a bad target config no longer terminates the existing
  provider; also keep the disconnect bound to the old provider_url.
- Guard ConvertInboxModal's submit handler so pressing Enter cannot
  bypass the type-to-confirm gate, and migrate it to <script setup>.
- Reject invalid ?provider= query values in convert mode so hidden
  providers (Twilio, the current provider) cannot be reached via URL.
- Await the inbox fetch in InboxConvert before running the route guard
  so directly opening the route for a non-WhatsApp inbox redirects.
- Remove the unreachable second CloudWhatsapp branch in Whatsapp.vue.

* fix: address second CodeRabbit round on convert-provider flow

- Unify provider picker validation so create mode also rejects
  unknown ?provider= values, with a single helper that accepts
  available providers plus the whatsapp_manual fallback.
- Simplify the pre-validation rollback in convert_provider!: the
  errors snapshot/merge dance was redundant because assign_attributes
  does not clear errors.
- Follow the repo convention of asserting on error.class.name so the
  rollback spec stays stable under reloading/parallel environments.
- Strengthen the controller success spec with provider_connection and
  message_templates cleanup invariants, and set Content-Type on the
  templates stub so HTTParty parses the empty data array correctly.

* fix: address third CodeRabbit round on convert-provider flow

- Add 360Dialog entry to the Whatsapp provider catalog, keep it hidden
  from the create picker (preserving the existing fork behavior) but
  expose it in the convert picker where it is a valid target. Restore
  URL reachability for ?provider=360dialog in create mode.
- Scope the WHATSAPP_MANUAL allowance to create mode only: the manual
  fallback flow is not reachable in convert mode.
- Redirect to the inboxes list in InboxConvert when the inbox is still
  absent after the store fetch, so the page no longer stays blank.
- Use an explicit allowlist of WhatsApp providers to gate the Convert
  button instead of negating Twilio, so adding a new WhatsApp channel
  type will not silently expose the flow.
- Bind the disabled provider display field with :value instead of
  v-model, since the underlying computed is getter-only.
- Add Content-Type: application/json to the templates stub in the
  model spec so HTTParty parses the empty data array.

* fix: address fourth CodeRabbit round on convert-provider flow

- Reject no-op conversions that target the same provider as the one
  already configured, so the endpoint no longer wipes provider
  connection and message templates on a request that changes nothing.
- Call the provider service's disconnect directly so failures abort
  the conversion instead of being silently swallowed; otherwise the
  old external session could remain live while the inbox flips to
  the new provider.
- Cover both behaviors with specs.

* fix: address fifth CodeRabbit round on convert-provider flow

- Reset the Vuelidate state when closing ConvertInboxModal so reopening
  the gate does not surface stale validation errors.
- Call teardown_webhooks before converting away from whatsapp_cloud so
  the Meta webhook subscription is removed for embedded_signup channels,
  mirroring the destroy-time cleanup (manual-setup channels keep the
  existing no-op behavior). Swallow teardown failures so a flaky Meta
  call does not abort the swap.
- Switch the rollback specs to compare message_templates counts instead
  of the boolean be_present matcher so they remain meaningful if the
  fixture happens to have an empty templates list.

* fix: address sixth CodeRabbit round on convert-provider flow

- Derive the convert header's current-provider label from the shared
  PROVIDER_CATALOG so the picker and header stay in sync.
- Assert the full Cloud provider_config payload and the absence of the
  Baileys-only provider_url key on both the controller success spec
  and the model atomic-swap spec.
- In the sync-error spec, reload and assert that the record was
  actually flipped to the new provider before the sync rescue fires,
  so the test can't pass on a pre-save failure.

* test: pin 422 error payload on convert_provider negative paths

The unsupported-conversion and invalid-config specs only checked the
status code, so they would have stayed green if the 422 started coming
from a different branch. Pin the response body so each example actually
covers the failure case it names.

* fix(baileys): save custom host as provider_url, not url

The Baileys form was writing the custom endpoint to
provider_config['url'] while the backend reads
provider_config['provider_url']. That silently broke the custom-host
feature for newly created or converted Baileys inboxes: they always
fell back to BAILEYS_PROVIDER_DEFAULT_URL. Align the key on both ends.

* fix(whatsapp): skip second validation pass in convert_provider!

The transaction's save! was re-running validate_provider_config after
the old provider's session had already been disconnected, so a transient
Graph API failure on the second check could roll back the swap while
leaving the external session terminated — the exact inconsistency the
pre-flight valid? was meant to rule out.

Capture the validated provider_config snapshot after valid? (so fields
populated by before_validation callbacks like webhook_verify_token are
preserved) and switch the final persist to save!(validate: false) so the
earlier check stays authoritative.

* fix: normalize provider-conversion failures and pass accountId

- The convert_provider action only rescued ActiveRecord::RecordInvalid,
  so disconnect/teardown failures bubbled up as 500 with no stable
  payload. Catch StandardError, log the class + message, and return a
  422 with a generic user-facing message so the dashboard can surface
  the error consistently.
- Nested settings routes live under /accounts/:accountId, so the
  router push from Settings.vue must include accountId alongside
  inboxId. Mirrors how sibling pages navigate to settings_inbox_show.

* fix: report missing :provider as 400 and sync modal v-model

- The generic rescue StandardError on convert_provider was masking
  ActionController::ParameterMissing behind a misleading
  provider-conversion error message. Catch it explicitly before the
  generic rescue and return 400 with the parameter-missing message.
- ConvertInboxModal's closeModal now drives localShow to false so
  parents using v-model:show stay in sync on every close path,
  not only when the explicit onClose listener flips the flag.

* fix(whatsapp): serialize concurrent convert_provider calls with_lock

Without a per-record lock, two admin requests against the same inbox
could both pass the pre-flight validation, race the disconnect/save,
and then run setup_webhooks/sync_templates in arbitrary order, leaving
the persisted provider out of sync with the external configuration.

Wrap the whole convert flow in with_lock so the loser blocks until the
winner commits; the subsequent no-op guard then rejects a second
conversion request targeting the provider the first one just set.

* test: harden convert_provider policy + controller failure specs

- Pass accountId explicitly in InboxConvert redirects so the route
  navigation mirrors how Settings.vue reaches settings_inbox_convert.
- Add a spec that assigns the agent to the inbox and still expects 401,
  so a future regression in InboxPolicy#convert_provider? can no longer
  slip past on the show policy alone.
- Add a spec that stubs convert_provider! to raise StandardError and
  asserts the controller's generic-failure 422 payload, pinning the
  dashboard contract for provider-side failures.

* test: pin convert_provider success response payload

Parse the rendered body and assert provider + provider_config so the
spec catches regressions where the DB is updated correctly but the
serialized response drifts (dashboard store commits response.data).

* fix(whatsapp): reset teardown guard after pre-conversion webhook cleanup

teardown_webhooks memoizes @webhook_teardown_initiated = true to prevent
double execution during destroy. Calling it from convert_provider!
leaves that flag set, so a subsequent destroy! or follow-up conversion
on the same instance would skip webhook removal silently. Reset the
flag in an ensure block so the destroy-time guard stays scoped to
destroy only.

* fix: include accountId in post-conversion redirect params

* test: pin same-provider convert returns 422

* fix(whatsapp): reset template columns when post-conversion sync fails

* fix(convert): enforce provider allowlist in InboxConvert route guard

* test: broaden Cloud templates stub to match account-scoped path

* test(whatsapp): cover cloud to baileys conversion branch
2026-04-18 20:57:27 -03:00
gabrieljablonski
112385fd9e Merge branch 'main' into chore/merge-4.13.0
Resolves 26 conflicts via manual review. Key decisions:

- signature: kept fork's send-time architecture (PR #79), discarded upstream's
  editor-manipulation functions
- WhatsApp incoming: combined fork's two-layer locking (source_id + contact
  phone) with upstream's blocked-contact drop. Fixed pre-existing regression
  where echoes were silently dropped
- InstallationConfig: upstream's simplified coder (validated against legacy
  YAML-in-jsonb data)
- schema.rb: regenerated, stripped kanban tables from other branches,
  restored f_unaccent SQL function
2026-04-17 16:23:47 -03:00
Shivam Mishra
95463230cb
feat: sign webhooks for API channel and agentbots (#13892)
Account webhooks sign outgoing payloads with HMAC-SHA256, but agent bot
and API inbox webhooks were delivered unsigned. This PR adds the same
signing to both.

Each model gets a dedicated `secret` column rather than reusing the
agent bot's `access_token` (for API auth back into Chatwoot) or the API
inbox's `hmac_token` (for inbound contact identity verification). These
serve different trust boundaries and shouldn't be coupled — rotating a
signing secret shouldn't invalidate API access or contact verification.

The existing `Webhooks::Trigger` already signs when a secret is present,
so the backend change is just passing `secret:` through to the jobs.
Shared token logic is extracted into a `WebhookSecretable` concern
included by `Webhook`, `AgentBot`, and `Channel::Api`. The frontend
reuses the existing `AccessToken` component for secret display. Secrets
are admin-only and excluded from enterprise audit logs.

### How to test

Point an agent bot or API inbox webhook URL at a request inspector. Send
a message and verify `X-Chatwoot-Signature` and `X-Chatwoot-Timestamp`
headers are present. Reset the secret from settings and confirm
subsequent deliveries use the new value.

---------

Co-authored-by: Sojan Jose <sojan@pepalo.com>
2026-04-06 15:28:25 +05:30
gabrieljablonski
d3ce2a4cf9 Merge branch 'main' into chore/merge-upstream-4.12.0 2026-03-19 23:24:55 -03:00
Muhsin Keloth
6be95e79f8
feat(csat): Add WhatsApp utility template analyzer with rewrite guidance (#13575)
CSAT templates for WhatsApp are submitted as Utility, but Meta may
reclassify them as Marketing based on content, which can significantly
increase messaging costs.
This PR introduces a Captain-powered CSAT template analyzer for
WhatsApp/Twilio WhatsApp that predicts utility fit, explains likely
risks, and suggests safer rewrites before submission. The flow is manual
(button-triggered), Captain-gated, and applies rewrites only on explicit
user action. It also updates UX copy to clearly set expectations: the
system submits as Utility, Meta makes the final categorization decision.

Fixes
https://linear.app/chatwoot/issue/CW-6424/ai-powered-whatsapp-template-classifier-for-csat-submissions


https://github.com/user-attachments/assets/8fd1d6db-2f91-447c-9771-3de271b16fd9
2026-02-24 15:11:04 +04:00
Gabriel Jablonski
3b8a38b153
feat: Implement existing template linking for CSAT surveys (#218)
* feat: Implement existing template linking for CSAT surveys

- Added functionality to link existing CSAT templates for WhatsApp channels.
- Introduced a new component for selecting existing templates.
- Updated the dashboard settings page to support template mode switching between creating new and using existing templates.
- Enhanced the CSAT template management service to handle linking existing templates and fetching available templates.
- Updated API routes to include linking and fetching available templates.
- Added tests for the new linking functionality and template availability checks.

* feat: Enhance CSAT template handling and validation across services and components

* feat: Refactor body variable extraction for CSAT templates and update related validations

* feat: Add linked_at field to CSAT template responses and update related handling

* feat: Add tests for ConversationDrop date formatting and CSAT template body variable handling
2026-02-18 18:00:29 -03:00
gabrieljablonski
6ab1898992 Merge branch 'main' into chore/merge-upstream-4.10 2026-01-16 14:01:53 -03:00
Muhsin Keloth
3e5b2979eb
feat: Add support for sending CSAT surveys via templates (Whatsapp Cloud) (#12787)
This PR enables sending CSAT surveys on WhatsApp using approved WhatsApp
message templates, ensuring survey delivery even after the 24-hour
session window.

The system now automatically creates, updates, and monitors WhatsApp
CSAT templates without manual intervention.

<img width="1664" height="1792" alt="approved"
src="https://github.com/user-attachments/assets/c6efd61e-1d01-4738-abb6-0afc0dace975"
/>

#### Why this change

Previously, WhatsApp CSAT messages failed outside the 24-hour customer
window.

With this update:

- CSAT surveys are delivered reliably using WhatsApp templates
- Template creation happens automatically in the background
- Users can modify survey content and recreate templates easily
- Clear UI states show template approval status

#### Screens & States

<details>
<summary>Default — No template configured yet</summary>
<img width="1662" height="1788" alt="default"
src="https://github.com/user-attachments/assets/ed26d71b-cf7c-4a26-a2af-da88772c847c"
/>
</details>
<details>
<summary>Pending — Template submitted, awaiting Meta approval</summary>
<img width="1658" height="1816" alt="pending"
src="https://github.com/user-attachments/assets/923b789b-d91b-4364-905d-e56a2b65331a"
/>
</details>
<details>
<summary>Approved — Survey will be sent when conversation
resolves</summary>
<img width="1664" height="1792" alt="approved"
src="https://github.com/user-attachments/assets/c6efd61e-1d01-4738-abb6-0afc0dace975"
/>
</details>
<details>
<summary>Rejected — Template rejected by Meta</summary>
<img width="1672" height="1776" alt="rejected"
src="https://github.com/user-attachments/assets/f69a9b0e-be27-4e67-a993-7b8149502c4f"
/>
</details>
<details>
<summary>Not Found — Template missing in Meta Platform</summary>
<img width="1660" height="1784" alt="not-exist"
src="https://github.com/user-attachments/assets/a2a4b4f7-b01a-4424-8fcb-3ed84256e057"
/>
</details>
<details>
<summary>Edit Template — Delete & recreate template on change</summary>
<img width="2342" height="1778" alt="edit-survey"
src="https://github.com/user-attachments/assets/0f999285-0341-4226-84e9-31f0c6446924"
/>
</details>

#### Test Cases


**1. First-time CSAT setup on WhatsApp inbox**

- Enable CSAT
- Enter message + button text
- Save
- Expected: Template created automatically, UI shows pending state

**2. CSAT toggle without changing text**

- Existing approved template
- Toggle CSAT OFF → ON (no text change)
- Expected: No confirmation alert, no template recreation

**3. Editing only survey rules**

- Modify labels or rule conditions only
- Expected: No confirmation alert, template remains unchanged

**4. Template text change**

- Change survey message or button text
- Save
- Expected:
    - Confirmation dialog shown
    - On confirm → previous template deleted, new one created
    - On cancel → revert to previous values

**5. Language change**

- Change template language (e.g., en → es)
- Expected: Confirmation dialog + new template on confirm

 **6. Sending survey**

- Template approved → always send template
- Template pending → send free-form within 24 hours only
- Template rejected/missing → fallback to free-form (if within window)
- Outside 24 hours & no approved template → activity log only

**7. Non-WhatsApp inbox**

- Enable CSAT for email/web inbox
- Expected: No template logic triggered


Fixes
https://linear.app/chatwoot/issue/CW-6188/support-for-sending-csat-surveys-via-approved-whatsapp

---------

Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
Co-authored-by: Vinay Keerthi <11478411+stonecharioteer@users.noreply.github.com>
Co-authored-by: iamsivin <iamsivin@gmail.com>
2026-01-06 11:46:00 +04:00
gabrieljablonski
801033bd5f Merge branch 'main' into chore/merge-upstream-4.5.0 2025-08-20 11:20:31 -03:00
Muhsin Keloth
ab1ba1c4c7
feat: Add manual WhatsApp templates sync with UI (#12007)
Fixes https://github.com/chatwoot/chatwoot/issues/9600

**Summary**
- Added manual WhatsApp templates sync functionality accessible via UI
- Added refresh button in both inbox settings and template picker modal
- Enhanced template picker to always show for WhatsApp Cloud channels
(even when empty)


**Preview**

<img width="1456" height="798" alt="CleanShot 2025-07-22 at 14 15 28@2x"
src="https://github.com/user-attachments/assets/8a04ff26-61fa-42ee-a1b8-5e06433ae6e0"
/>


<img width="2376" height="1574" alt="CleanShot 2025-07-22 at 14 19
24@2x"
src="https://github.com/user-attachments/assets/1c772034-04ff-484d-88dd-ca8123e93065"
/>

---------

Co-authored-by: iamsivin <iamsivin@gmail.com>
2025-07-23 12:22:51 +04:00
Cayo P. R. Oliveira
acd1e56a28 feat: baileys provider for whatsapp (#7)
* feat: baileys provider and placeholder for link device modal

* chore: drop qrcode.vue in favor of just img tag

* chore: update modal props

* feat: setup channel provider connection

* chore: update .env.example with Baileys API default configuration

* feat: add support for Baileys provider in WhatsApp events processing

* chore: rename Baileys API default host variable to DEFAULT_BAILEYS_URL

* feat: add setup and disconnect methods for Baileys channel provider in inboxes controller that will be implemented

* feat: add CHANNEL_CONNECTION_UPDATE event and include it in broadcast data preparation

* refactor: simplify channel retrieval logic in WhatsappEventsJob

* refactor: revert CHANNEL_UPDATE_EVENTS constant from ActionCableBroadcastJob

* feat: add 'baileys' as a provider option in Whatsapp channel model

* feat: add provider_connection field to Whatsapp channel model and migration

* refactor: remove unnecessary  CHANNEL_CONNECTION_UPDATE event type

* feat: implement channel provider connection with baileys API

* feat: add inbox association to Whatsapp channel model and update webhook URL handling

* feat: enhance Baileys service to handle webhook multiple event types

* refactor: simplify webhook verification logic in Baileys service

* feat: add setup channel provider call, and refactor some logic

* chore: adapt logic to new API

* refactor: fix typo

* refactor: fix import

* refactor: fix typo

* chore: add fixme comment about race condition

* fix: remove double disconnect call

* feat: implement message processing for incoming WhatsApp messages

* refactor: streamline message type determination and improve readability

* chore: increase cache key granularity

provider connection info might be updated multiple times within 1 second, so updates might be lost due to cache key not being updated. changing cache key to milliseconds solves this

* feat: add `is-loading` to buttons

* feat: update send_message method to use 'to' parameter and improve error handling

* refactor: simplify test setup and update API key in specs

* chore: add setup and disconnect channel provider specs

* test: fix spec after increase cache key granularity

* feat: handle reconnecting state on modal

* style: centered error text

* feat: advanced options on create inbox

* feat: handle new reconnecting on backend

* refactor: update inbox controller specs and leave a FIXME note

* test: add specs for Whatsapp::IncomingMessageBaileysService

* feat: add baileys configuration page

* feat: link device button when disconnected on conversation

* chore: refactor .env.example

* feat: add TODO for unimplemented methods in IncomingMessageBaileysService

* fix: correct method name and update environment variable references in WhatsappBaileysService

* refactor: simplify channel lookup by removing redundant method and handling phone number check directly

* chore: add TODO for unimplemented event processing methods in IncomingMessageBaileysService

* fix: update environment variable references in WhatsappBaileysService tests

* chore(webhook): add pt-BR translations

* chore: add pt-br translations

* chore: inboxname component margin

* refactor: inboxname computed prop

* feat: enhance WhatsApp provider connection handling and message processing

* test: inbox controller

* chore: improve baileys connection and messages handling

* test: incoming message service baileys

* refactor: update provider config validation and improve test setup for WhatsApp Baileys service

* fix: ensure only text messages are sent and update message source ID

* fix: create message

* fix: only update message on success

* test: fix broken specs

* chore: raise error on unsupported message content type

* feat: hide provider connection data from non-admins

* fix: update advanced options

* chore: move class definition

* fix: issue with send_message not returning id

---------

Co-authored-by: gabrieljablonski <contact@gabrieljablonski.com>
2025-04-03 23:28:38 -03:00
Shivam Mishra
00ee0478eb
feat: IndexedDB based caching for labels, inboxes and teams [CW-50] (#6710)
* feat: allow caching of labels in the account scope

* feat: send cache keys in account json response

* feat: kickstart web worker

* feat: setup basic architecture for workers

* feat: install idb

* feat: add datamanger

* fix: typos

* refactor: rename method

* feat: make init db a manual step

* refactor: separate accountIdFromRoute

* feat: cache enabled API client

* feat: enable caching for inboxes and labels

* feat: enable cache for team

* feat: manage exceptions for team

* feat: add team to data manager

* feat: add a generic listener

* refactor: send only cache keys

* refactor: separate validate method

* feat: add listeners

* feat: add event for revalidate

* feat: add cache keys endpoint

* refactor: fetch cache keys instead of full account data

* fix: key pattern

* feat: don't fetch account for cache_keys

* fix: cache key base class

* refactor: cache keys helper

* feat: add helper

* fix: cache-key update logic

* feat: delete indexeddb on logout

* feat: remove worker.js

* refactor: move data-manager

* refactor: name of file

* feat: add test for DataManager

* refactor: add fake idb to jest setup

* test: cache keys helper

* test: cache keys helper

* test: cache_keys in accounts controller

* refactor: remove cache_keys context

* feat: add policy for cache-keys
2023-03-27 12:16:25 +05:30
Pranav Raj S
16bfd68d95
chore: Allow admins to choose the agent bot from the UI (#5895) 2022-11-18 08:54:32 -08:00
Pranav Raj S
56cd9bc0b8
chore: Add Assignable Agents API (#4722) 2022-05-23 19:24:07 +05:30
Aswin Dev P.S
1ff9939a80
feat: Ability to remove inbox avatar (#2885)
* Delete inbox avatar

1) New API endpoint added for deleting inbox avatar.
2) Delete avatar button in the inbox settings page.

Co-authored-by: Sojan Jose <sojan@pepalo.com>
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
2021-08-31 15:12:05 +05:30
Muhsin Keloth
18cea3b0ac
chore: Add inbox id in get campaigns API (#2278) 2021-05-17 21:38:12 +05:30
Sivin Varghese
285f7bbeb5
Enhancement: Ability to assign administrators as conversation assignees (#2142)
* Enhancement: Ability to assign administrators as conversation assignee

Co-authored-by: Nithin David Thomas <webofnithin@gmail.com>
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
2021-04-22 23:17:29 +05:30
Sojan Jose
19ab0fe108
Chore: Scope URLs with account_id (#601)
* Chore: Enable Users to create multiple accounts

Addresses: #402
- migrations to split roles and other attributes from users table
- make changes in code to accommodate this change

Co-authored-by: Pranav Raj Sreepuram <pranavrajs@gmail.com>
2020-03-09 23:27:10 +05:30
Pranav Raj S
5ddc46c474 Refactor: Inbox store, remove inboxes from sidebar (#387)
* Refactor: Inbox store, remove inboxes from sidebar

* Add a new page for inbox settings

* Show inboxes on sidebar

* Add inbox_members API

* Disable similar-code check

* Fix codeclimate scss issues

* Add widget_color update API and actions

* Add specs for inbox store

* Fix Facebook auth flow

* Fix agent loading, inbox name
2019-12-28 21:56:42 +05:30