langflow-ai/langflow · error · ValueError
webhook url has no host
Error message
webhook url has no host
What it means
Raised by validate_webhook_url when urlparse finds no hostname in the webhook URL. This catches malformed URLs where everything lands in scheme/path — the classic example being 'http:/host/hook' (single slash), which urlparse parses with netloc='' and hostname=None. It fires after the scheme check, so the scheme was http/https but the authority component is missing.
Source
Thrown at src/backend/base/langflow/api/v1/a2a_utils.py:109
component, which would otherwise reopen private/metadata webhooks): resolve the
host and reject if any IP is blocked. On top of the floor, run the shared SSRF
framework (``validate_and_resolve_url``) for the allowlist / CGNAT / ``is_global``
extras and pinned IPs. The returned IPs let the dispatch client pin DNS (closing
the rebind window). ``LANGFLOW_A2A_ALLOW_PRIVATE_WEBHOOKS`` skips the IP check for
a trusted internal network (returns ``[]``: nothing to pin).
Raises ``ValueError`` when the URL is unsafe. Returns the validated IPs (framework
IPs, falling back to the floor-resolved IPs when the global toggle is off), or
``[]`` when private webhooks are allowed. Used at registration (set_info) and
re-run at dispatch.
"""
parsed = urlparse(url)
if parsed.scheme not in ("http", "https"):
msg = "webhook url must be http or https"
raise ValueError(msg)
if not parsed.hostname:
msg = "webhook url has no host"
raise ValueError(msg)
if get_settings_service().settings.a2a_allow_private_webhooks:
return []
try:
# Resolve/validate the SAME host httpx connects to (IDNA/punycode raw_host), not the
# unicode urlparse hostname, so an IDN webhook is pinned/resolved by the exact ASCII host
# the connection uses (else the pin silently misses: TOCTOU rebind for IDN hosts). httpx.URL
# raises InvalidURL (not ValueError) for an IDNA-invalid host, so keep it inside the try.
host = webhook_pin_host(url)
# Hard floor: reject private/metadata IPs even when global SSRF protection is off
# (validate_and_resolve_url returns [] with NO enforcement in that case).
# resolve_hostname handles IP-literal hosts too; the blocking resolve runs off-loop.
floor_ips = await asyncio.to_thread(resolve_hostname, host)
blocked = [ip for ip in floor_ips if is_ip_blocked(ip)]
if blocked:
msg = f"webhook url resolves to a blocked address: {', '.join(blocked)}"
raise ValueError(msg)
# Then the framework check for allowlist / CGNAT / is_global extras + pinned IPs.
_url, validated_ips = await asyncio.to_thread(validate_and_resolve_url, url)View on GitHub (pinned to 976ec789d2)
Solutions
- Use the double-slash authority form: http://host:port/path
- Build URLs with a library (urllib.parse.urlunparse, httpx.URL, yarl) rather than string concatenation
- Assert parsed.hostname is truthy client-side before submitting the config
Example fix
# before
url = f"http:/{host}:{port}/hook"
# after
url = f"http://{host}:{port}/hook" Defensive patterns
Strategy: validation
Validate before calling
from urllib.parse import urlparse
def webhook_has_host(url: str) -> bool:
return bool(urlparse(url).hostname) Type guard
def has_valid_authority(url: object) -> bool:
if not isinstance(url, str):
return False
p = urlparse(url)
return p.scheme in ("http", "https") and bool(p.hostname) Prevention
- Never build URLs by string concatenation; use urlunparse/URL builders
- Lint config files for 'http:/' single-slash typos (a cheap regex CI check)
- Run the same urlparse assertions client-side that the server runs
When it happens
Trigger: Push-notification config with url='http:/host/hook' (one slash), 'https://' with nothing after, or a URL built by string concatenation that dropped the '//'.
Common situations: Template/format-string bugs producing http:/{host}/hook; manual typing with a single slash; URL built from a base that already ended in a slash plus naive joining.
Related errors
- webhook url must be http or https
- webhook url has an invalid host: {exc}
- str(exc)
- webhook url resolves to a blocked address: {', '.join(blocke
- webhook url is not allowed: {exc}
AI-assisted analysis of langflow-ai/langflow@976ec789d2 (2026-08-14).
Data as JSON: /api/errors/c25c74504c172988.
Report an issue: GitHub.