langflow-ai/langflow · error · ValueError

webhook url has an invalid host: {exc}

Error message

webhook url has an invalid host: {exc}

What it means

Raised by validate_webhook_url when httpx.URL rejects the webhook host as IDNA-invalid (httpx.InvalidURL), translated to ValueError because callers only guard ValueError. Without the translation an exotic/illegal hostname would escape as a 500 or crash the dispatch loop instead of failing closed as an unsafe webhook. It complements the urlparse check by validating the exact ASCII host httpx would connect to.

Source

Thrown at src/backend/base/langflow/api/v1/a2a_utils.py:132

        # 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)
    except httpx.InvalidURL as exc:
        # Callers only guard ValueError; without translating this an IDNA-invalid host would 500 the
        # caller (or escape dispatch) instead of failing closed as an unsafe webhook.
        msg = f"webhook url has an invalid host: {exc}"
        raise ValueError(msg) from exc
    except SSRFProtectionError as exc:
        msg = f"webhook url is not allowed: {exc}"
        raise ValueError(msg) from exc
    # Fall back to the floor IPs so dispatch can still DNS-pin with the global toggle off.
    return validated_ips or floor_ips


async def folder_auth_type(flow: Flow, session: AsyncSession) -> str:
    """Read the flow's folder ``auth_type`` (``"none"`` | ``"apikey"`` | ``"oauth"``).

    The single source of truth for what the card advertises (resolve_card_security)
    and what the JSON-RPC route enforces, so the two can't drift. Plaintext read,
    no decrypt. No folder / missing folder -> ``"none"`` (public).
    """
    if flow.folder_id is None:
        return "none"
    # Query the folder explicitly; lazy-loading flow.folder would raise in async.
    folder = (await session.exec(select(Folder).where(Folder.id == flow.folder_id))).first()

View on GitHub (pinned to 976ec789d2)

Solutions

  1. Fix the hostname to a valid DNS name: replace underscores with hyphens, remove stray unicode characters
  2. Validate with httpx.URL(url) client-side — if it raises InvalidURL the server will reject it too
  3. Register the webhook under a clean canonical name (CNAME to the odd internal host if needed)

Example fix

# before
url = "https://my_hook.example.com/cb"
# after
url = "https://my-hook.example.com/cb"
Defensive patterns

Strategy: validation

Validate before calling

import httpx

def webhook_host_idna_safe(url: str) -> bool:
    try:
        httpx.URL(url)
        return True
    except httpx.InvalidURL:
        return False

Try / catch

try:
    await client.tasks.set_push_notification(task_id, cfg)
except InvalidParamsError as e:
    if "invalid host" in str(e):
        cfg.url = sanitize_hostname(cfg.url)  # underscores->hyphens, strip unicode
        await client.tasks.set_push_notification(task_id, cfg)
    else:
        raise

Prevention

When it happens

Trigger: A webhook URL whose hostname contains characters that cannot be IDNA-encoded — e.g. embedded underscores ('my_hook.example.com' is actually accepted by some resolvers but rejected here), labels >63 chars, prohibited code points, or stray unicode punctuation in the host.

Common situations: Hostnames with underscores common in internal naming (service_name.internal); copy-pasting a URL with a smart quote/zero-width character; very long auto-generated subdomains exceeding per-label limits.

Related errors


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