This guide covers the API security upgrade for Chatwoot integrations. The correct path depends on which Meta app authorizes your outbound WhatsApp requests. Identify that first; do not replace a working credential as a test.
Choose Your Path
| Current authorization | Chatwoot deployment | Required action |
|---|---|---|
| Your company owns the Meta app and Meta access token used by Chatwoot | Cloud or self-hosted | Keep the native WhatsApp Cloud inbox pointed at graph.facebook.com. Do not replace the Meta token with a Dualhook key. |
| Dualhook's Meta app authorizes outbound operations | Chatwoot Cloud | Do not put a dh_live_... key in the native WhatsApp token field. Use a customer-owned Meta app or a fully implemented API-channel bridge. |
| Dualhook's Meta app authorizes outbound operations | Self-hosted Chatwoot | Deploy and test a per-inbox Dualhook provider adapter before changing the credential. Changing only WHATSAPP_CLOUD_BASE_URL is not sufficient. |
If you are unsure which case applies, check who created and controls the permanent
Meta token currently stored in the Chatwoot inbox. A token created from your own
Meta app or system user remains a customer-owned Meta credential. A
connection-scoped dh_live_... key is a Dualhook credential and is valid only at
api.dualhook.com.
Customer-Owned Meta App
This is the safest and lowest-maintenance Chatwoot configuration. If it is already working, the API Upgrade page requires no Chatwoot change. Continue to use:
https://graph.facebook.com
Authorization: Bearer <YOUR_META_ACCESS_TOKEN>
Do not replace the token with dh_live_..., and do not redirect the inbox to
Dualhook. The Dualhook runtime migration applies only to operations authorized
through Dualhook's Meta app.
For a new or migrated native inbox:
- In your Meta business, confirm that your app and system user have access to the WABA and Phone Number ID.
- Create a permanent system-user token with the WhatsApp permissions required by Chatwoot.
- In Chatwoot, use the manual WhatsApp Cloud setup and enter the display phone number, Phone Number ID, WABA ID, and your Meta token.
- Keep the native Meta Graph endpoint. Do not enter a Dualhook runtime key.
- Configure the callback and verification token shown by Chatwoot, or preserve an existing working callback while you test the customer-owned app subscription.
- Test inbound text and media, an in-window reply, a template send, an outbound attachment, delivered/read statuses, and template synchronization.
- Remove an obsolete Dualhook Webhook Override only after the customer-owned app has passed all inbound and outbound tests.
When converting an existing Chatwoot Embedded Signup inbox, prefer Chatwoot's documented migration to manual setup instead of deleting the inbox. Chatwoot states that its migration preserves the inbox and its conversations and settings.
Dualhook Meta App
Outbound requests for this case must use the connection's exact v25.0 runtime routes and a bearer-only credential:
POST https://api.dualhook.com/v25.0/<PHONE_NUMBER_ID>/messages
Authorization: Bearer dh_live_...
The runtime is an allowlisted WhatsApp API, not a general Graph proxy. It does not
accept Meta tokens, an access_token query parameter, appsecret_proof, app
secrets, arbitrary Graph paths, or webhook-management calls. See
Runtime API Authentication and the
Runtime API Endpoint Reference.
Chatwoot Cloud
There is no safe native credential swap. Chatwoot Cloud does not expose an
installation environment variable or custom native WhatsApp provider per inbox.
Pasting dh_live_... into its Meta access-token field would cause at least some
requests to go to Meta with the wrong credential.
Use one of these options:
- Recommended: authorize the number through your own Meta app and use Chatwoot's native manual WhatsApp Cloud inbox.
- Use a customer-controlled bridge with a Chatwoot API inbox. The bridge must implement inbound/outbound mapping, media, templates, delivery statuses, retries, idempotency, and conversation continuity. Chatwoot documents the API inbox and callback model.
An API inbox is not a drop-in conversion of a native WhatsApp inbox. Plan how to retain historical conversations and test the bridge with a controlled number or conversation before changing production webhook routing.
Self-Hosted Chatwoot
Use a per-inbox Dualhook provider adapter. Dualhook does not currently claim
that unmodified Chatwoot is compatible with a dh_live_... credential.
The adapter must:
- Activate only for an explicitly marked Dualhook inbox. Do not redirect every WhatsApp Cloud inbox on the installation.
- Build all runtime calls from
https://api.dualhook.com/v25.0. - Send
dh_live_...only asAuthorization: Bearer; removeaccess_tokenandappsecret_prooffrom URLs, query strings, and bodies. - Route text, template, interactive, and attachment sends through the v25.0
/messagesroute. - Read inbound media metadata from
/<MEDIA_ID>and download the returned Dualhook/<MEDIA_ID>/contentURL with the same bearer key. - Ensure template synchronization uses bearer authentication and resubmits only
Dualhook's safe
afterorbeforecursor. The published v4.16.2 release needs both changes; post-release Chatwoot source already uses bearer authentication and anaftercursor for routine synchronization, but still needs its v14.0 path normalized. Never allow an absolutepaging.nextGraph URL. - Validate the configured Phone Number ID and WABA ID with bounded single-object
reads. Do not call the WABA
/phone_numberscollection. - Make health calls provider-aware, bearer-authenticated, and limited to fields supported by the runtime.
- Adapt Chatwoot CSAT template create, status, and delete operations to the v25.0 template routes.
- Skip Chatwoot-managed phone registration/deregistration, app subscription, webhook callback override, callback clearing, and Embedded Signup token exchange. Those remain Dualhook control-plane operations.
- Keep WhatsApp Calling disabled unless every route used by the deployed Chatwoot version has been separately certified.
Do not use the global WHATSAPP_CLOUD_BASE_URL as the production solution,
especially when the installation also hosts inboxes that legitimately use
customer-owned Meta tokens.
Why the Base URL Alone Is Not Enough
The published Chatwoot v4.16.2 release contains a global
WHATSAPP_CLOUD_BASE_URL, but its WhatsApp implementation is not a single
interchangeable Graph client. Chatwoot's develop branch changed routine
template synchronization shortly after the release without changing the
application version string, so audit the exact commit you deploy:
| Chatwoot behavior | Dualhook requirement | Result without an adapter |
|---|---|---|
| Text/template/interactive sends use v13.0; attachments use v24.0 | Exact v25.0 routes | Requests are rejected or misrouted. |
Inbox validation uses v14.0 and puts access_token in the URL. The published v4.16.2 tag also does this for routine template sync. | Bearer-only dh_live_... | Validation is incompatible and the key may enter URL logs. |
The published v4.16.2 tag follows an absolute template paging.next URL. Post-release source uses the after cursor safely. | Safe cursor pagination only | The release tag needs adaptation; newer source still needs its template path normalized to v25.0. |
Inbox validation calls /<WABA_ID>/phone_numbers | Bounded Phone Number ID and WABA reads | The collection is outside the runtime allowlist. |
Health uses a hardcoded graph.facebook.com base and query token | Dualhook v25.0 plus bearer auth | Health calls still go to Meta. |
| Manual inbox creation attempts registration and webhook setup | Dualhook owns provisioning and Webhook Override | Setup invokes unsupported control-plane routes. |
These behaviors are visible in Chatwoot's official published v4.16.2 source for the WhatsApp Cloud provider, health service, WhatsApp channel model, and webhook setup service. The later template-management-token change moved routine template synchronization to bearer authentication and cursor-only pagination, but it did not fix Dualhook inbox validation, API-version fragmentation, hardcoded health/setup clients, or lifecycle mutations.
Inbound Webhooks
The outbound credential migration does not require an inbound routing change. Meta can continue sending WhatsApp message and status events directly to the existing Chatwoot callback through Dualhook's Webhook Override:
https://<YOUR_CHATWOOT_HOST>/webhooks/whatsapp/<DISPLAY_PHONE_NUMBER>
Keep the callback URL, display phone number, Phone Number ID, and verification token unchanged during an outbound migration. Do not delete and recreate the inbox merely to change its outbound transport.
There is a webhook-authenticity limitation to understand. In Chatwoot v4.16.2,
the WhatsApp webhook controller
requires X-Hub-Signature-256 verification for an Embedded Signup inbox or when
an applicable app secret is available. A manual WhatsApp Cloud inbox without an
app secret can skip POST signature verification. Dualhook will not reveal its
Meta app secret as a workaround. Use TLS, an unguessable verification token,
rate limits, request-size limits, idempotency, and event monitoring, and account
for this gap in your risk review.
Feature Support
| Configuration | Inbound and statuses | Outbound replies | Media | Template sync | Native health | Existing inbox/history |
|---|---|---|---|---|---|---|
| Customer-owned Meta app with native Chatwoot | Yes | Yes | Yes | Yes | Yes | Yes |
Chatwoot Cloud native inbox with dh_live_... | Inbound may continue | No | No | No | No | Preserved but partially broken |
Self-hosted with only WHATSAPP_CLOUD_BASE_URL changed | Inbound may continue | Not reliable | No | No | No | Yes |
| Self-hosted per-inbox Dualhook adapter | Yes | Yes | Yes | Yes | Yes, after adaptation | Yes |
| API inbox plus complete bridge | If implemented | If implemented | If implemented | Must be implemented | Must be implemented | Usually a new inbox |
Safe Migration Order
For a self-hosted adapter or an API-channel bridge:
- Record and back up the existing inbox/channel configuration, IDs, webhook URL, verification token, database, and deployment configuration.
- Build and test the replacement transport before changing any production credential or callback.
- Add tests that fail if a Dualhook inbox contacts
graph.facebook.com, sendsaccess_tokenorappsecret_proof, follows an absolute pagination URL, or invokes registration/webhook-management routes. - Keep the existing Dualhook Webhook Override unchanged.
- For a self-hosted adapter, mark the existing inbox as Dualhook-managed and only then replace its outbound credential with the connection-scoped key.
- Test inbound and outbound text, reply context, interactive messages, template sends, multi-page template sync, inbound and outbound media, delivered/read/ failed statuses, and health.
- Revoke the previous outbound credential only after all tests succeed and no queued job or campaign still depends on it.
Security and Upgrades
- Store
dh_live_...in a server-side secret manager. Chatwoot provider configuration, database replicas, backups, Rails consoles, logs, and support exports must all be treated as secret-bearing systems. - Never send the key to
graph.facebook.com, put it in a URL, expose it to browser code, or share it in screenshots or support messages. - Never request Dualhook's Meta token, Meta app secret, or generated App Secret Proof. Dualhook injects those internally.
- Re-audit the adapter on every Chatwoot upgrade. Search changed WhatsApp code for
graph.facebook.com,access_token, API-version strings,paging.next, setup/ teardown calls, media, health, templates, CSAT, and voice features. - Pin the Chatwoot release until the adapter's contract tests pass against a Dualhook test connection and customer-owned Meta inboxes still call Meta.
Sources
This compatibility review was checked against the published Chatwoot v4.16.2
release, marked as the
latest Chatwoot release
on July 31, 2026, plus post-release source changes through commit bc7ae88d.
The published release tag points to 70e284a; repository HEAD still reported
application version 4.16.2 while containing later changes. Always identify the
exact commit as well as the displayed version. Official sources:
- Chatwoot WhatsApp setup guide
- Chatwoot Embedded Signup-to-manual migration
- Chatwoot API inbox guide
- Chatwoot v4.16.2 WhatsApp Cloud provider
- Chatwoot v4.16.2 health service
- Chatwoot v4.16.2 channel lifecycle
- Chatwoot v4.16.2 webhook setup
- Chatwoot v4.16.2 webhook teardown
- Chatwoot v4.16.2 inbound webhook controller
- Post-release template synchronization change
- Meta WhatsApp Cloud API overview
- Meta Graph Webhooks reference
- Dualhook Runtime API Authentication
- Dualhook Runtime API Endpoint Reference