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

  1. Use the double-slash authority form: http://host:port/path
  2. Build URLs with a library (urllib.parse.urlunparse, httpx.URL, yarl) rather than string concatenation
  3. 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

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


AI-assisted analysis of langflow-ai/langflow@976ec789d2 (2026-08-14). Data as JSON: /api/errors/c25c74504c172988. Report an issue: GitHub.