{"record":{"id":"2b3932b17b96e1ff","repo":"n8n-io/n8n","slug":"webhook-url-must-use-https-got-url-protocol","errorCode":null,"errorMessage":"Webhook URL must use HTTPS. Got: ${url.protocol}","messagePattern":"Webhook URL must use HTTPS\\. Got: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/@n8n/ai-workflow-builder.ee/evaluations/cli/webhook.ts","lineNumber":115,"sourceCode":"\t\treturn true;\n\t}\n\n\treturn false;\n}\n\n/**\n * Validate webhook URL for security (hostname-based checks only).\n * - Must be HTTPS\n * - Must not target localhost or private/internal IP addresses (SSRF prevention)\n *\n * Note: This performs synchronous hostname string validation.\n * For full SSRF protection, use validateWebhookUrlWithDns() which also resolves DNS.\n */\nexport function validateWebhookUrl(webhookUrl: string): void {\n\tconst url = new URL(webhookUrl);\n\n\tif (url.protocol !== 'https:') {\n\t\tthrow new Error(`Webhook URL must use HTTPS. Got: ${url.protocol}`);\n\t}\n\n\tconst hostname = url.hostname.toLowerCase();\n\n\tif (\n\t\thostname === 'localhost' ||\n\t\thostname === '127.0.0.1' ||\n\t\thostname === '::1' ||\n\t\thostname === '[::1]'\n\t) {\n\t\tthrow new Error('Webhook URL cannot target localhost');\n\t}\n\n\tif (isPrivateIp(hostname)) {\n\t\tthrow new Error('Webhook URL cannot target private/internal IP addresses');\n\t}\n\n\tconst blockedHostnames = ['internal', 'intranet', 'corp', 'private', 'local'];","sourceCodeStart":97,"sourceCodeEnd":133,"githubUrl":"https://github.com/n8n-io/n8n/blob/5ac6606e81f67bb9534255570cd4e86fd8101eee/packages/@n8n/ai-workflow-builder.ee/evaluations/cli/webhook.ts#L97-L133","documentation":"`validateWebhookUrl` parses the `--webhook-url` value with `new URL(...)` and rejects any scheme other than `https:`. The check is the first line of SSRF/transport defense for the webhook result-sender: plaintext `http://` URLs would expose the (signed) payload and HMAC headers to the network. The thrown message echoes the offending protocol so the cause is obvious.","triggerScenarios":"Passing `--webhook-url http://example.com/hook` (or any non-https URL) to the eval CLI, or programmatically calling `validateWebhookUrl('http://...')`. The URL parses successfully (so `new URL` does not throw) but `url.protocol` is not `'https:'`.","commonSituations":"Pointing at a local/test receiver over plain HTTP, a typo (`http:/` vs `https:/`), or a config file that stores the URL without a scheme upgrade after migrating to TLS.","solutions":["Change the URL scheme to `https://`.","If the receiver is local-only, put it behind a TLS-terminating proxy or a self-signed cert and use an `https://` URL (and accept that localhost is still blocked separately).","For development, use a tunnel (ngrok/cloudflared) that exposes your local receiver over HTTPS."],"exampleFix":"// before\nvalidateWebhookUrl('http://hooks.example.com/eval');\n// after\nvalidateWebhookUrl('https://hooks.example.com/eval');","handlingStrategy":"validation","validationCode":"function assertHttpsWebhook(url: string): void {\n  let parsed: URL;\n  try { parsed = new URL(url); } catch { throw new Error(`invalid webhook URL: ${url}`); }\n  if (parsed.protocol !== 'https:') {\n    throw new Error(`webhook URL must be https, got ${parsed.protocol}`);\n  }\n}\nassertHttpsWebhook(webhookUrl);","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Make `https://` a required prefix in any config field that stores a webhook URL.","Run all webhook receivers behind TLS; never keep a plaintext HTTP endpoint 'just for dev'.","Add a CI lint rule that rejects `http://` webhook URLs in checked-in config."],"tags":["webhook","ssrf","security","validation","tls"],"backgroundTag":null,"analyzedSha":"5ac6606e81f67bb9534255570cd4e86fd8101eee","analyzedAt":"2026-08-12T05:26:35.080Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}