Skip to content

feat(proxy): add configurable webhook proxy bypass#339

Merged
asternic merged 4 commits into
asternic:mainfrom
Alg0rix:feat/webhook-proxy-bypass
Jul 1, 2026
Merged

feat(proxy): add configurable webhook proxy bypass#339
asternic merged 4 commits into
asternic:mainfrom
Alg0rix:feat/webhook-proxy-bypass

Conversation

@Alg0rix

@Alg0rix Alg0rix commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Closes #338

Summary

Since PR #152, when a per-user proxy is configured, the same proxy is applied to both the WhatsApp websocket client and the internal Resty HTTP client used for outgoing webhook deliveries. In some deployments the proxy is a dedicated exit for WhatsApp traffic, while webhooks need to reach the application backend directly.

This PR adds a configurable option so webhook delivery can bypass the configured proxy while the WhatsApp connection still uses it.

Behavior

  • Backward-compatible default: webhook_use_proxy = true — webhooks continue to use the proxy when one is configured
  • When set to false: the per-user webhook resty.Client does not call SetProxy(...), while client.SetProxyAddress / client.SetSOCKSProxy for the WhatsApp connection remain unchanged

Configuration

Scope Setting Default
Global WUZAPI_WEBHOOK_USE_PROXY=true|false true
Per-user (admin API) proxyConfig.webhookUseProxy global default
Per-user (session API) webhook_use_proxy in POST /session/proxy global default

Changes

  • DB migration: webhook_use_proxy BOOLEAN DEFAULT TRUE on users
  • startClient: conditionally applies proxy to webhook client based on per-user setting
  • Admin/session API handlers and dashboard UI toggle
  • Tests for resolveWebhookUseProxy

Related

Test plan

  • go test ./...
  • Verified locally with docker compose up --build
  • Confirmed WhatsApp connection still uses proxy when configured
  • Confirmed webhook client bypasses proxy when webhook_use_proxy=false

Alg0rix added 2 commits June 25, 2026 16:11
Add WUZAPI_WEBHOOK_USE_PROXY and per-user webhook_use_proxy so
webhook deliveries can bypass the WhatsApp proxy while keeping
backward-compatible default true.

Includes DB migration, API handlers, dashboard UI, and tests.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request adds support for routing webhook deliveries through a per-user proxy, introducing a new webhook_use_proxy setting across the database, API, and frontend. The review feedback identifies several key areas for improvement: a bug in SetProxy where omitting the setting overwrites existing configurations with the global default, an N+1 query performance bottleneck in ListUsers, unhandled database errors in GetStatus and startClient, and missing OpenAPI documentation for the new parameter in POST /session/proxy.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread handlers.go
Comment on lines +6134 to +6140
webhookUseProxy := resolveWebhookUseProxy(t.WebhookUseProxy)
_, err = s.db.Exec(
"UPDATE users SET proxy_url = $1, webhook_use_proxy = $2 WHERE id = $3",
t.ProxyURL,
webhookUseProxy,
txtid,
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Bug: Omitted webhook_use_proxy resets to global default

When calling SetProxy, if the client does not provide the webhook_use_proxy parameter (i.e., t.WebhookUseProxy is nil), resolveWebhookUseProxy(t.WebhookUseProxy) will return the global default (*globalWebhookUseProxy, which is true by default).

This will silently overwrite and destroy any custom webhook_use_proxy configuration previously saved by the user in the database.

Recommended Solution

Only update webhook_use_proxy in the database if it is explicitly provided in the request. If it is omitted, preserve the existing value.

		var webhookUseProxy bool
		if t.WebhookUseProxy != nil {
			webhookUseProxy = *t.WebhookUseProxy
			_, err = s.db.Exec(
				"UPDATE users SET proxy_url = $1, webhook_use_proxy = $2 WHERE id = $3",
				t.ProxyURL,
				webhookUseProxy,
				txtid,
			)
		} else {
			err = s.db.QueryRow("SELECT COALESCE(webhook_use_proxy, true) FROM users WHERE id = $1", txtid).Scan(&webhookUseProxy)
			if err != nil {
				webhookUseProxy = *globalWebhookUseProxy
			}
			_, err = s.db.Exec("UPDATE users SET proxy_url = $1 WHERE id = $2", t.ProxyURL, txtid)
		}

Comment thread handlers.go Outdated
Comment on lines +5300 to +5307
err = s.db.QueryRow(
"SELECT COALESCE(webhook_use_proxy, true) FROM users WHERE id = $1",
user.Id,
).Scan(&webhookUseProxy)
if err != nil && err != sql.ErrNoRows {
log.Warn().Err(err).Str("user_id", user.Id).Msg("Failed to query webhook_use_proxy for user")
webhookUseProxy = *globalWebhookUseProxy
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Performance Bottleneck: N+1 Query Problem

In ListUsers, executing a separate database query (s.db.QueryRow) for each user to retrieve webhook_use_proxy introduces an N+1 query problem. This will severely degrade performance as the number of users grows.

Recommended Solution

Instead of querying the database inside the loop, add the webhook_use_proxy field to the usersStruct struct and select it directly in the main SELECT queries on lines 5250 and 5254.

For example, update usersStruct:

type usersStruct struct {
    // ... existing fields ...
    WebhookUseProxy sql.NullBool `db:"webhook_use_proxy"`
}

And update the queries to include COALESCE(webhook_use_proxy, true) AS webhook_use_proxy, then read it directly from user.WebhookUseProxy.Bool inside the loop.

Comment thread handlers.go Outdated
Comment on lines +770 to +774
var webhookUseProxy bool
s.db.QueryRow(
"SELECT proxy_url, COALESCE(webhook_use_proxy, true) FROM users WHERE id = $1",
txtid,
).Scan(&proxyURL, &webhookUseProxy)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Unhandled database error and uninitialized fallback

In GetStatus, s.db.QueryRow(...).Scan(...) is executed without checking for errors. If a database error occurs or the user is not found, webhookUseProxy will remain uninitialized (defaulting to false), which incorrectly signals that the proxy should be bypassed, even though the global default is true.

Recommended Solution

Initialize webhookUseProxy with the global default value before scanning, and log any database errors (excluding sql.ErrNoRows if appropriate).

Suggested change
var webhookUseProxy bool
s.db.QueryRow(
"SELECT proxy_url, COALESCE(webhook_use_proxy, true) FROM users WHERE id = $1",
txtid,
).Scan(&proxyURL, &webhookUseProxy)
webhookUseProxy := *globalWebhookUseProxy
err := s.db.QueryRow(
"SELECT proxy_url, COALESCE(webhook_use_proxy, true) FROM users WHERE id = $1",
txtid,
).Scan(&proxyURL, &webhookUseProxy)
if err != nil && err != sql.ErrNoRows {
log.Warn().Err(err).Str("user_id", txtid).Msg("Failed to query proxy settings for user")
}

Comment thread wmiau.go
Comment on lines +475 to +478
err = s.db.QueryRow(
"SELECT proxy_url, COALESCE(webhook_use_proxy, true) FROM users WHERE id=$1",
userID,
).Scan(&proxyURL, &webhookUseProxy)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Observability: Unlogged database error in startClient

If the database query or scan fails in startClient, the error is silently ignored, and proxy configuration is skipped. This makes troubleshooting proxy issues extremely difficult if there is a database connection problem or schema mismatch.

Recommended Solution

Log the error if the query or scan fails.

	err = s.db.QueryRow(
		"SELECT proxy_url, COALESCE(webhook_use_proxy, true) FROM users WHERE id=$1",
		userID,
	).Scan(&proxyURL, &webhookUseProxy)
	if err != nil && err != sql.ErrNoRows {
		log.Error().Err(err).Str("user_id", userID).Msg("Failed to query proxy settings from database")
	}

Comment thread handlers.go
Enable bool `json:"enable"` // Whether to enable or disable proxy
ProxyURL string `json:"proxy_url"` // Format: "socks5://user:pass@host:port" or "http://host:port"
Enable bool `json:"enable"` // Whether to enable or disable proxy
WebhookUseProxy *bool `json:"webhook_use_proxy,omitempty"`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Documentation: Missing webhook_use_proxy in OpenAPI spec for POST /session/proxy

You have added webhook_use_proxy to the proxyStruct used by SetProxy (which handles POST /session/proxy), but this new parameter is not documented in the OpenAPI specification (static/api/spec.yml) under the /session/proxy path.

Recommended Solution

Update static/api/spec.yml under /session/proxy -> post -> requestBody -> properties to include webhook_use_proxy:

                webhook_use_proxy:
                  type: boolean
                  description: Whether webhook deliveries use the configured proxy
                  example: true

Preserve webhook_use_proxy when omitted from SetProxy, load the field in
ListUsers without N+1 queries, initialize safe defaults on query errors,
log proxy lookup failures in startClient, and document webhook_use_proxy
in the OpenAPI spec.
@Alg0rix

Alg0rix commented Jun 25, 2026

Copy link
Copy Markdown
Contributor Author

Addressed the review feedback in c46fd0c:

  • SetProxy: webhook_use_proxy is only updated when explicitly provided; omitted requests preserve the stored value
  • ListUsers: webhook_use_proxy is selected in the main query (no N+1)
  • GetStatus / startClient: safe global default before scan, with error logging on DB failures
  • OpenAPI: documented webhook_use_proxy on POST /session/proxy

go test ./... passes locally.

- Unify SetProxy enable/disable into single UPDATE paths, preserving
  existing webhook_use_proxy when omitted.
- Remove applyRestyProxy one-line wrapper.
- Combine GetStatus proxy/S3/HMAC queries into one round-trip.
@asternic
asternic merged commit 89c4af0 into asternic:main Jul 1, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature request: configurable option to bypass proxy for webhook delivery

2 participants