amir20/dozzle · error

invalid webhook URL scheme

Error message

invalid webhook URL scheme %q: only http and https are allowed

What it means

NewWebhookDispatcher only allows http and https schemes (checked case-insensitively after url.Parse succeeds). Any other scheme, including dangerous ones like file://, ftp://, or gopher://, is rejected with this error to prevent SSRF-style abuse and misconfiguration.

Solutions

  1. Prefix the URL with https:// (or http:// only for intentionally insecure internal targets)
  2. If the scheme is empty because the scheme was omitted, add it explicitly
  3. Use a standard HTTPS webhook endpoint from the target service's docs

Example fix

// before
NewWebhookDispatcher("hook", "example.com/webhook", "", nil)
// after
NewWebhookDispatcher("hook", "https://example.com/webhook", "", nil)
Defensive patterns

Strategy: validation

Validate before calling

u, _ := url.Parse(rawURL)
if s := strings.ToLower(u.Scheme); s != "http" && s != "https" {
    return errors.New("webhook URL must use http or https")
}

Type guard

func isHTTPScheme(raw string) bool {
    u, err := url.Parse(raw)
    if err != nil { return false }
    s := strings.ToLower(u.Scheme)
    return s == "http" || s == "https"
}

Try / catch

d, err := NewWebhookDispatcher(name, rawURL, tpl, headers)
if err != nil && strings.Contains(err.Error(), "scheme") {
    // surface scheme requirement to the user
}

Prevention

When it happens

Trigger: Calling NewWebhookDispatcher with a URL whose scheme is not http/https, e.g. "ftp://host/hook", "file:///tmp/x", or a URL with no scheme like "example.com/hook" (empty scheme).

Common situations: Users pasting a hostname without the https:// prefix; attempts to target local files or non-HTTP services; typo'd schemes (hxxp, webhooks://).

Understand the failure class

Background: "Invalid URL" errors: why new URL(), URI.parse, and reqwest::Url reject your string — missing scheme, whitespace, and bad path format — this error's family across 39 libraries.

Related errors


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

Appendix: source

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

// 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)