Updates dashboard, widget, and backend locale files with the latest
translation sync from Crowdin.
## Closes
N/A
## What changed
- Refreshes translated dashboard JSON locale files across supported
languages.
- Adds the latest backend Help Center/public portal locale strings.
- Keeps the branch current with `develop` and resolves the `ar`, `fr`,
and `pt_BR` locale conflicts.
## Validation
- Parsed all changed JSON and YAML locale files successfully.
- Checked for leftover merge conflict markers.
- Ran `git diff --check`.
---------
Co-authored-by: Sojan Jose <sojan@pepalo.com>
> Reopened from #13613, now from a personal fork
(`gabrieljablonski/chatwoot`) so maintainers can push edits —
organization-owned forks don't support "Allow edits from maintainers".
The previous PR is closed in favor of this one; same commits, same diff.
## Description
This PR adds support for sending voice messages (voice notes) through
the WhatsApp Cloud API. When agents record audio in Chatwoot, it is now
transcoded in the browser from WebM/Opus to OGG/Opus and sent with the
`voice: true` flag, so it appears as a native voice note bubble on
WhatsApp — not as a file/document attachment.
Closes#13283
**Key Changes:**
- Added `webmOpusToOgg.js` — a pure JS EBML parser + OGG page builder
that remuxes browser-recorded WebM/Opus audio into OGG/Opus entirely
client-side, with no server-side dependencies.
- Updated `AudioRecorder.vue` to use an explicit `mimeType` hint, proper
resource cleanup, and an `AUDIO_EXTENSION_MAP` for correct file
extensions.
- Renamed `mp3ConversionUtils.js` → `audioConversionUtils.js` and added
OGG conversion support via the new remuxer.
- Updated `ReplyBox.vue` to request OGG format for WhatsApp channels,
pass `isVoiceMessage` per-attachment, and handle recording errors with a
user-facing alert.
- Updated `MessageBuilder` to read the `is_voice_message` param and
persist it in attachment metadata.
- Updated `WhatsappCloudService` to:
- Normalize `audio/opus` → `audio/ogg` content type on ActiveStorage
blobs (works around Marcel gem re-detection).
- Send the `voice: true` flag when the attachment is a voice message
with `audio/ogg` content type.
- Use WhatsApp Cloud API `v24.0` for the attachment endpoint.
- Added `AUDIO_CONVERSION_FAILED` i18n key.
**How it works:**
1. The browser records audio as WebM/Opus (Chrome/Firefox default).
2. `audioConversionUtils.js` remuxes it to OGG/Opus using the pure-JS
`webmOpusToOgg` remuxer — no server transcoding needed.
3. The OGG file is uploaded with `is_voice_message: true` in the form
payload.
4. `MessageBuilder` persists `is_voice_message` in the attachment's
`meta` hash.
5. `WhatsappCloudService` normalizes the blob content type if needed,
then sends the attachment with `voice: true` so WhatsApp renders it as a
voice note.
## Type of change
- [X] New feature (non-breaking change which adds functionality)
## How Has This Been Tested?
1. Record a voice message in a WhatsApp Cloud conversation.
2. Verify the audio is transcoded to OGG (check file extension in the
attachment preview).
3. Verify the message arrives on WhatsApp as a voice note bubble (not a
document/file).
4. Send an image or document attachment and verify it still works as
before (no `voice` flag).
5. Send a regular (non-voice) audio file and verify it arrives without
the voice flag.
---------
Co-authored-by: Muhsin Keloth <muhsinkeramam@gmail.com>
Co-authored-by: Muhsin <12408980+muhsin-k@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
## Summary
One-off SMS and WhatsApp campaigns now show a `Processing` state while
the audience send is in progress. The campaign moves to `Completed`
after processing finishes, and already-processing campaigns are skipped
by the scheduler to avoid duplicate sends.
## Closes
- [CW-6037: feat: Introduce an in-progress status for
campaigns](https://linear.app/chatwoot/issue/CW-6037/feat-introduce-an-in-progress-status-for-campaigns)
## Screenshot
SMS campaign card showing the new `Processing` status.
<img width="3840" height="2160" alt="framed-campaign-processing-status"
src="https://github.com/user-attachments/assets/de7913b5-65fb-4121-9034-24a568eb0382"
/>
## What changed
- Added `processing` as a campaign status.
- Mark one-off campaigns as `processing` under a row lock before the
send service runs.
- Complete SMS, Twilio SMS, and WhatsApp one-off campaigns after
audience processing finishes.
- Keep campaigns in `processing` if an unexpected service error escapes,
so the scheduler does not automatically resend the audience.
- Added the `Processing` label for SMS and WhatsApp campaign cards.
## Known operational behavior
If a worker is interrupted or an unexpected service error escapes after
a campaign is marked `processing`, the campaign can remain in
`processing`. This is intentional for now to avoid automatic
full-audience resends. Installation admins can decide whether to mark
the campaign completed or restart it manually from the Rails console
after checking what was sent.
## How to test
- Create a one-off SMS or WhatsApp campaign scheduled for now.
- Run the scheduled job or trigger the campaign job.
- Confirm the campaign card shows `Processing` while the audience is
being processed. For small audiences, refresh during processing or use a
larger audience so the state is observable.
- Confirm the campaign moves to `Completed` after audience processing
finishes.
- Confirm an already-processing campaign is not enqueued again by the
scheduled job.
## Linear ticket
https://linear.app/chatwoot/issue/CW-7187/voice-calls-followup-tasks
## Description
Improvements to the WhatsApp voice-calling experience plus a cheaper,
more accurate audio-transcription model.
- First-time callers now get a real name. An inbound WhatsApp call
creates the contact from the caller's WhatsApp profile name instead of
the bare phone number.
- Clear, consistent call attribution. Call bubbles show a unified
"Handled by {agent}"
- Cleaner call widget. The dismiss (✕) button is shown only for incoming
calls
- WhatsApp calling for manual inboxes. voice_calling_supported? now
covers any whatsapp_cloud inbox
- Transcription: whisper-1 → gpt-4o-mini-transcribe.
## Type of change
- [ ] New feature (non-breaking change which adds functionality)
## Checklist:
- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my code
- [ ] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] Any dependent changes have been merged and published in downstream
modules
Adds bulk label removal alongside the existing assign-label action for
conversations and contacts, so teams can clean up labels across selected
records without opening each item individually.
For conversations, the remove dropdown is scoped to labels that are
actually applied across the current selection — so agents no longer see
(or accidentally "remove") labels that aren't on any of the selected
items. For contacts, the dropdown still lists all account labels for
now; label data isn't carried on the contact list payload today, so
scoping the contact remove menu cleanly is being tracked as a follow-up.
## Closes
N/A
## How to test
- Open the conversation list, select multiple conversations, open
**Remove labels**, and confirm the dropdown only lists labels that are
applied to at least one selected conversation. Pick a label and confirm
it's removed from the selection.
- Open Contacts, select multiple contacts, use **Remove Labels**, choose
a label, and confirm the selected contacts are refreshed without that
label.
- Verify **Assign Labels** still works for conversations and contacts,
and continues to show every available label.
## What changed
- Adds an `action` prop to the shared `BulkLabelActions` dropdown so it
can render in `assign` or `remove` mode.
- Wires conversation bulk remove to the existing `labels.remove` backend
path and filters the dropdown to the union of labels applied across the
selected conversations.
- Adds contact bulk remove support through
`Contacts::BulkRemoveLabelsService`, routed by
`Contacts::BulkActionService`.
- Raises contact label save failures instead of reporting a successful
bulk action when a contact update is invalid.
## Follow-ups
- Scope the contact remove dropdown to applied labels (needs a
lightweight endpoint, or eventually `cached_label_list` on `Contact`).
## Verification
Conversation bulk remove selector:
<img width="1680" height="1050" alt="Conversation bulk remove label
selector"
src="https://github.com/user-attachments/assets/2dba4a06-c497-45e1-85b0-e700164b6b2f"
/>
Contact bulk remove selector:
<img width="1680" height="1050" alt="Contact bulk remove label selector"
src="https://github.com/user-attachments/assets/b3b89959-5978-4064-b5f9-82b1a3e571dc"
/>
Video proof:
https://github.com/user-attachments/assets/fffafe19-4e1c-4e2a-a135-c7182c06bb4d
---------
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
Co-authored-by: iamsivin <iamsivin@gmail.com>
In WhatsApp coexistence setups (Business App + Cloud API on the same
number), some inbound customer messages arrive from Meta as `type:
unsupported` with error `131060` ("This message is unavailable") and no
content — typically the first message of a Click-to-WhatsApp /
Instagram-ad conversation, or a message synced from a companion device.
Chatwoot was dropping these webhooks entirely, so no contact,
conversation, or message was created. The conversation only surfaced
once an agent replied (via an `smb_message_echoes` event), starting
"headless" with zero customer context.
This change persists a placeholder message for these events so the
contact and conversation are created, and renders it with the dedicated
unsupported-message bubble that points agents to the WhatsApp app —
where the original message is still visible.
Fixes
https://linear.app/chatwoot/issue/CW-7166/whatsapp-coexistence-inbound-messages-are-silently-dropped
and https://github.com/chatwoot/chatwoot/issues/13464
<img width="3448" height="1604" alt="CleanShot 2026-05-22 at 17 49
35@2x"
src="https://github.com/user-attachments/assets/0a90ec84-9085-4cba-883d-08d9de33fa3c"
/>
## How to reproduce
1. Connect a WhatsApp Cloud (coexistence) inbox.
2. Receive an inbound message that Meta delivers as `type: unsupported`
with error `131060` (e.g. a Click-to-WhatsApp ad message, or a message
handled on a companion/primary device that fails to sync to the API).
3. **Before:** nothing is created — the conversation only appears after
an agent replies, with no record of the customer's first message.
4. **After:** the contact and conversation are created with an incoming
placeholder message rendered as the amber "unsupported" bubble: _"This
message is unsupported. You can view this message on the WhatsApp app."
---------
Co-authored-by: Muhsin <12408980+muhsin-k@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: Sojan Jose <sojan@pepalo.com>
## Summary
Frontend for WhatsApp Cloud Calling: header / contact-panel call
buttons, ringing widget, accept/reject/hangup, mute, in-bubble audio
player + transcript, recording-on-hangup upload, mid-call reload
warning. WebRTC is browser-direct to Meta — no media server bridge.
## Closes
- https://linear.app/chatwoot/issue/PLA-150
## How to test
Requires backend support — the controller, services, model changes, and
routes ship in **#14334** (`feature/pla-150`). Merge / deploy that first
(or simultaneously); the FE alone won't function without those
endpoints.
Then on staging, for a WhatsApp Cloud + embedded-signup inbox with the
new \`Configuration → Enable voice calling\` toggle ON and webhook
registered:
1. **Outbound** — open a conversation, click the phone icon in the
conversation header (or contact panel), grant mic, your phone rings,
answer, audio both ways, hang up. Recording + transcript land in the
bubble within ~10s.
2. **Inbound** — call the business number from your phone. The
FloatingCallWidget appears bottom-right with caller name. Click accept,
audio both ways, hang up. Recording + transcript appear.
3. **Mute** — during an active WhatsApp call, click the mic icon next to
hangup. Speech stops reaching Meta until you click again.
4. **Mid-call reload guard** — try `Cmd-R` during an active call;
browser shows a confirm prompt.
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: iamsivin <iamsivin@gmail.com>
Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
Chatwoot now lets external apps know when an inbox loses its connection
and needs re-authentication. When a channel's authorization expires (for
example, an email inbox disconnects), Chatwoot fires an `inbox_updated`
webhook reflecting the new `reauthorization_required` status, and fires
it again once the inbox is re-authenticated. Integrators can keep their
own view of which inboxes are healthy without polling the API.
This is gated behind the `ENABLE_INBOX_EVENTS` installation flag — the
**Inbox updated** webhook subscription only appears in the dashboard
when that flag is enabled, so no event is offered that the backend
wouldn't dispatch.
Fixes
https://linear.app/chatwoot/issue/CW-7148/emit-inbox-webhook-when-an-inbox-is-disconnected
## How to test
1. Set `ENABLE_INBOX_EVENTS=true` and restart the app.
2. In **Settings → Integrations → Webhooks**, add a webhook and
subscribe to **Inbox updated**.
3. Disconnect an inbox — let an email/Instagram channel hit its
auth-error threshold, or run `inbox.channel.prompt_reauthorization!` in
a console.
4. The endpoint receives an `inbox_updated` event whose
`changed_attributes` shows `reauthorization_required` flipping to
`true`.
5. Re-authenticate the inbox (or run `inbox.channel.reauthorized!`) —
the endpoint receives the `true → false` transition.
6. Confirm the **Inbox updated** option is hidden when
`ENABLE_INBOX_EVENTS` is unset.
---------
Co-authored-by: Muhsin <12408980+muhsin-k@users.noreply.github.com>
# Pull Request Template
## Description
- Validates openai key while configuring hooks
- added backfill logic
Fixes # (issue)
## Type of change
- [x] New feature (non-breaking change which adds functionality)
## How Has This Been Tested?
Please describe the tests that you ran to verify your changes. Provide
instructions so we can reproduce. Please also list any relevant details
for your test configuration.
locally
<img width="1710" height="1234" alt="CleanShot 2026-04-15 at 16 15
02@2x"
src="https://github.com/user-attachments/assets/3d319fe0-19f9-4fd0-9308-74987daac2e1"
/>
<img width="2884" height="1136" alt="CleanShot 2026-05-11 at 19 22
53@2x"
src="https://github.com/user-attachments/assets/5eae8650-985b-4c4a-af42-35f7175ff52d"
/>
## Checklist:
- [x] My code follows the style guidelines of this project
- [x] I have performed a self-review of my code
- [x] I have commented on my code, particularly in hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [x] My changes generate no new warnings
- [x] I have added tests that prove my fix is effective or that my
feature works
- [x] New and existing unit tests pass locally with my changes
- [x] Any dependent changes have been merged and published in downstream
modules
---------
Co-authored-by: Vishnu Narayanan <iamwishnu@gmail.com>