amir20/dozzle · error

invalid webhook URL

Error message

invalid webhook URL: %w

What it means

NewWebhookDispatcher validates the raw webhook URL with url.Parse before constructing the dispatcher. If Go's URL parser cannot parse the string at all (e.g. control characters, malformed percent-encoding), it returns this wrapped error and no dispatcher is created.

Solutions

  1. Inspect the wrapped parse error for the exact offending position
  2. Re-enter the URL cleanly, trimming whitespace and control characters
  3. Percent-encode special characters properly (use url.PathEscape / QueryEscape on components)
  4. If the URL comes from YAML/env, check for line-folding or BOM artifacts

Example fix

// before
NewWebhookDispatcher("hook", "https://example.com/hook?x=100%", "", nil)
// after
NewWebhookDispatcher("hook", "https://example.com/hook?x=100%25", "", nil)
Defensive patterns

Strategy: validation

Validate before calling

u, err := url.Parse(strings.TrimSpace(rawURL))
if err != nil {
    return fmt.Errorf("webhook URL is not parseable: %w", err)
}

Type guard

func validURL(raw string) bool {
    _, err := url.Parse(strings.TrimSpace(raw))
    return err == nil
}

Try / catch

d, err := NewWebhookDispatcher(name, rawURL, tpl, headers)
if err != nil {
    return fmt.Errorf("webhook config invalid: %w", err)
}

Prevention

When it happens

Trigger: Calling NewWebhookDispatcher (or createDispatcher for a webhook destination) with a rawURL string that net/url.Parse rejects, such as one containing invalid percent escapes or control bytes.

Common situations: Webhook URL pasted into the UI with stray whitespace/newlines or copied from a rich-text source; unescaped '%' characters; config file YAML values containing invisible characters.

Understand the failure class

Background: "Invalid URL" / "URL cannot be empty": fix the malformed or missing URL behind request-construction failures — this error's family across 50 libraries.

Related errors


AI-assisted analysis of amir20/dozzle@d9463cbe21 (2026-09-07). Data as JSON: /api/errors/f387c0b2ed094074. Report an issue: GitHub.

Appendix: source

Thrown at internal/notification/dispatcher/webhook.go:165

// UserAgent is set by the application at startup
var UserAgent = "Dozzle/head"

// WebhookDispatcher sends notifications to a webhook URL
type WebhookDispatcher struct {
	Name         string
	URL          string
	Template     *template.Template
	TemplateText string // Original template string for serialization
	Headers      map[string]string
	client       *http.Client
}

// NewWebhookDispatcher creates a new webhook dispatcher
// If templateStr is empty, the notification will be marshaled as JSON directly
func NewWebhookDispatcher(name, rawURL, templateStr string, headers map[string]string) (*WebhookDispatcher, error) {
	parsed, err := url.Parse(rawURL)
	if err != nil {
		return nil, fmt.Errorf("invalid webhook URL: %w", err)
	}
	if scheme := strings.ToLower(parsed.Scheme); scheme != "http" && scheme != "https" {
		return nil, fmt.Errorf("invalid webhook URL scheme %q: only http and https are allowed", parsed.Scheme)
	}

	w := &WebhookDispatcher{
		Name:         name,
		URL:          rawURL,
		TemplateText: templateStr,
		Headers:      headers,
		client: &http.Client{
			Timeout: 10 * time.Second,
			Transport: &http.Transport{
				DialContext:           safeDialContext,
				TLSHandshakeTimeout:   10 * time.Second,
				ResponseHeaderTimeout: 10 * time.Second,
				ExpectContinueTimeout: 1 * time.Second,
			},

View on GitHub (pinned to d9463cbe21)