{"record":{"id":"16c507ac2de41aae","repo":"amir20/dozzle","slug":"invalid-webhook-url-scheme-q-only-http-and-https","errorCode":null,"errorMessage":"invalid webhook URL scheme %q: only http and https are allowed","messagePattern":"invalid webhook URL scheme %q: only http and https are allowed","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/notification/dispatcher/webhook.go","lineNumber":168,"sourceCode":"// WebhookDispatcher sends notifications to a webhook URL\ntype WebhookDispatcher struct {\n\tName         string\n\tURL          string\n\tTemplate     *template.Template\n\tTemplateText string // Original template string for serialization\n\tHeaders      map[string]string\n\tclient       *http.Client\n}\n\n// NewWebhookDispatcher creates a new webhook dispatcher\n// If templateStr is empty, the notification will be marshaled as JSON directly\nfunc NewWebhookDispatcher(name, rawURL, templateStr string, headers map[string]string) (*WebhookDispatcher, error) {\n\tparsed, err := url.Parse(rawURL)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"invalid webhook URL: %w\", err)\n\t}\n\tif scheme := strings.ToLower(parsed.Scheme); scheme != \"http\" && scheme != \"https\" {\n\t\treturn nil, fmt.Errorf(\"invalid webhook URL scheme %q: only http and https are allowed\", parsed.Scheme)\n\t}\n\n\tw := &WebhookDispatcher{\n\t\tName:         name,\n\t\tURL:          rawURL,\n\t\tTemplateText: templateStr,\n\t\tHeaders:      headers,\n\t\tclient: &http.Client{\n\t\t\tTimeout: 10 * time.Second,\n\t\t\tTransport: &http.Transport{\n\t\t\t\tDialContext:           safeDialContext,\n\t\t\t\tTLSHandshakeTimeout:   10 * time.Second,\n\t\t\t\tResponseHeaderTimeout: 10 * time.Second,\n\t\t\t\tExpectContinueTimeout: 1 * time.Second,\n\t\t\t},\n\t\t},\n\t}\n","sourceCodeStart":150,"sourceCodeEnd":186,"githubUrl":"https://github.com/amir20/dozzle/blob/d9463cbe21874e44ab79db6fa63e746ca7d22928/internal/notification/dispatcher/webhook.go#L150-L186","documentation":"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.","triggerScenarios":"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).","commonSituations":"Users pasting a hostname without the https:// prefix; attempts to target local files or non-HTTP services; typo'd schemes (hxxp, webhooks://).","solutions":["Prefix the URL with https:// (or http:// only for intentionally insecure internal targets)","If the scheme is empty because the scheme was omitted, add it explicitly","Use a standard HTTPS webhook endpoint from the target service's docs"],"exampleFix":"// before\nNewWebhookDispatcher(\"hook\", \"example.com/webhook\", \"\", nil)\n// after\nNewWebhookDispatcher(\"hook\", \"https://example.com/webhook\", \"\", nil)","handlingStrategy":"validation","validationCode":"u, _ := url.Parse(rawURL)\nif s := strings.ToLower(u.Scheme); s != \"http\" && s != \"https\" {\n    return errors.New(\"webhook URL must use http or https\")\n}","typeGuard":"func isHTTPScheme(raw string) bool {\n    u, err := url.Parse(raw)\n    if err != nil { return false }\n    s := strings.ToLower(u.Scheme)\n    return s == \"http\" || s == \"https\"\n}","tryCatchPattern":"d, err := NewWebhookDispatcher(name, rawURL, tpl, headers)\nif err != nil && strings.Contains(err.Error(), \"scheme\") {\n    // surface scheme requirement to the user\n}","preventionTips":["Always include an explicit https:// prefix in stored webhook URLs","Use a URL input field with scheme validation in configuration UIs","Treat missing-scheme hostnames as invalid before saving"],"tags":["url","validation","scheme","webhook","ssrf"],"backgroundTag":"invalid-url-format","analyzedSha":"d9463cbe21874e44ab79db6fa63e746ca7d22928","analyzedAt":"2026-09-07T10:08:55.855Z","contentChangedAt":"2026-09-07T10:08:55.855Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}