Skip to content

[Chatwoot] Verify Chatwoot's webhook signature (X-Chatwoot-Signature) on /webhooks/chatwoot/{session}/{appId} #2287

Description

@warren-commits

Is your feature request related to a problem? Please describe.

The Chatwoot app's inbound route POST /webhooks/chatwoot/{session}/{appId} accepts any request that knows the app id. It takes the recipient and the message content (including attachment data_url) from the posted body and sends from the paired WhatsApp number. It does not check who sent the request.

The app id alone is weak protection:

  • It appears in the Chatwoot API inbox's webhook URL, which any Chatwoot user with inbox settings access can read.
  • It appears in WAHA's logs.

So any process or container that can reach WAHA and has seen the app id can send any message to any recipient from the paired number. It bypasses Chatwoot entirely. Putting WAHA behind a network boundary helps, but does not remove the risk from anything inside that boundary.

Describe the solution you'd like

Chatwoot already signs API-inbox webhooks. From v4.x each Channel::Api has a secret, and every inbox webhook delivery carries:

  • X-Chatwoot-Timestamp: <unix seconds>
  • X-Chatwoot-Signature: sha256=<hex HMAC-SHA256(secret, "<timestamp>.<raw body>")>

(See lib/webhooks/trigger.rb#request_headers, and app/listeners/webhook_listener.rb, which passes inbox.channel.secret.)

Proposal:

  1. An optional config.webhookSecret field on the Chatwoot app (the inbox's secret from Chatwoot).
  2. When it is set, the controller computes the HMAC over the raw request body. It rejects the request with 401 when the signature is missing or does not match (constant-time compare), or when the timestamp is outside a small window (for example ±5 minutes) to stop replay.
  3. When the field is not set, today's behaviour stays unchanged, so nothing breaks for existing users. A startup warning that the route is unauthenticated would help.

Describe alternatives you've considered

  • An unguessable app id. It is still readable in the Chatwoot UI and in logs, so it is not a real secret.
  • Network isolation or a reverse proxy that checks the HMAC. This works, but everyone has to rebuild it outside WAHA. The proxy also needs the raw body and the secret, so it belongs in the app.
  • WAHA's existing webhook hmac.key. This signs WAHA → receiver traffic only. It does not authenticate Chatwoot → WAHA.

Additional context

Seen on WAHA 2026.9.1 (GOWS) with Chatwoot v4.18.0, both self-hosted. grep -ri x-chatwoot-signature /app/dist in the WAHA image returns nothing. Happy to test a build.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions