From 9e2b6fda7240e128397ce18187e16fe26ee6c238 Mon Sep 17 00:00:00 2001
From: dtapps
Date: Fri, 3 Apr 2026 00:05:48 +0800
Subject: [PATCH] feat(whatsapp): add proxy support for native and bridge modes
---
config/config.example.json | 1 +
pkg/channels/whatsapp/whatsapp.go | 13 +++++++++++++
pkg/channels/whatsapp_native/whatsapp_native.go | 12 ++++++++++++
pkg/config/config.go | 1 +
pkg/migrate/sources/openclaw/openclaw_config.go | 1 +
5 files changed, 28 insertions(+)
diff --git a/config/config.example.json b/config/config.example.json
index f0cce6d72..d3763e402 100644
--- a/config/config.example.json
+++ b/config/config.example.json
@@ -127,6 +127,7 @@
"whatsapp": {
"enabled": false,
"bridge_url": "ws://localhost:3001",
+ "proxy": "",
"use_native": false,
"session_store_path": "",
"allow_from": [],
diff --git a/pkg/channels/whatsapp/whatsapp.go b/pkg/channels/whatsapp/whatsapp.go
index 98622fe37..67b02ecf0 100644
--- a/pkg/channels/whatsapp/whatsapp.go
+++ b/pkg/channels/whatsapp/whatsapp.go
@@ -4,6 +4,9 @@ import (
"context"
"encoding/json"
"fmt"
+ "net/http"
+ "net/url"
+ "os"
"sync"
"time"
@@ -56,6 +59,16 @@ func (c *WhatsAppChannel) Start(ctx context.Context) error {
dialer := websocket.DefaultDialer
dialer.HandshakeTimeout = 10 * time.Second
+ if c.config.Proxy != "" {
+ proxyURL, parseErr := url.Parse(c.config.Proxy)
+ if parseErr != nil {
+ return fmt.Errorf("invalid proxy URL %q: %w", c.config.Proxy, parseErr)
+ }
+ dialer.Proxy = http.ProxyURL(proxyURL)
+ } else if os.Getenv("HTTP_PROXY") != "" || os.Getenv("HTTPS_PROXY") != "" {
+ dialer.Proxy = http.ProxyFromEnvironment
+ }
+
conn, resp, err := dialer.Dial(c.url, nil)
if resp != nil {
resp.Body.Close()
diff --git a/pkg/channels/whatsapp_native/whatsapp_native.go b/pkg/channels/whatsapp_native/whatsapp_native.go
index d0a74a405..c50276fe0 100644
--- a/pkg/channels/whatsapp_native/whatsapp_native.go
+++ b/pkg/channels/whatsapp_native/whatsapp_native.go
@@ -11,6 +11,8 @@ import (
"context"
"database/sql"
"fmt"
+ "net/http"
+ "net/url"
"os"
"path/filepath"
"strings"
@@ -124,6 +126,16 @@ func (c *WhatsAppNativeChannel) Start(ctx context.Context) error {
client := whatsmeow.NewClient(deviceStore, waLogger)
+ if c.config.Proxy != "" {
+ proxyURL, parseErr := url.Parse(c.config.Proxy)
+ if parseErr != nil {
+ return fmt.Errorf("invalid proxy URL %q: %w", c.config.Proxy, parseErr)
+ }
+ client.SetProxy(http.ProxyURL(proxyURL))
+ } else if os.Getenv("HTTP_PROXY") != "" || os.Getenv("HTTPS_PROXY") != "" {
+ client.SetProxy(http.ProxyFromEnvironment)
+ }
+
// Create runCtx/runCancel BEFORE registering event handler and starting
// goroutines so that Stop() can cancel them at any time, including during
// the QR-login flow.
diff --git a/pkg/config/config.go b/pkg/config/config.go
index 4e8733cbf..da8ed46d5 100644
--- a/pkg/config/config.go
+++ b/pkg/config/config.go
@@ -336,6 +336,7 @@ type StreamingConfig struct {
type WhatsAppConfig struct {
Enabled bool `json:"enabled" yaml:"-" env:"PICOCLAW_CHANNELS_WHATSAPP_ENABLED"`
BridgeURL string `json:"bridge_url" yaml:"-" env:"PICOCLAW_CHANNELS_WHATSAPP_BRIDGE_URL"`
+ Proxy string `json:"proxy" yaml:"-" env:"PICOCLAW_CHANNELS_WHATSAPP_PROXY"`
UseNative bool `json:"use_native" yaml:"-" env:"PICOCLAW_CHANNELS_WHATSAPP_USE_NATIVE"`
SessionStorePath string `json:"session_store_path" yaml:"-" env:"PICOCLAW_CHANNELS_WHATSAPP_SESSION_STORE_PATH"`
AllowFrom FlexibleStringSlice `json:"allow_from" yaml:"-" env:"PICOCLAW_CHANNELS_WHATSAPP_ALLOW_FROM"`
diff --git a/pkg/migrate/sources/openclaw/openclaw_config.go b/pkg/migrate/sources/openclaw/openclaw_config.go
index 4436c1861..d61b4f333 100644
--- a/pkg/migrate/sources/openclaw/openclaw_config.go
+++ b/pkg/migrate/sources/openclaw/openclaw_config.go
@@ -642,6 +642,7 @@ type ChannelsConfig struct {
type WhatsAppConfig struct {
Enabled bool `json:"enabled"`
BridgeURL string `json:"bridge_url"`
+ Proxy string `json:"proxy"`
AllowFrom []string `json:"allow_from"`
}