langflow-ai/langflow · error · ValueError

webhook url must be http or https

Error message

webhook url must be http or https

What it means

Raised by validate_webhook_url in a2a_utils.py when an A2A push-notification webhook URL's scheme is anything other than http or https. The scheme check runs before DNS resolution, so this is a pure URL-format rejection; the ValueError is surfaced to the client as a JSON-RPC InvalidParamsError at registration (set_info) or logged-and-dropped at dispatch.

Source

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

    The A2A endpoint is public, so the webhook target is caller-controlled. Require
    http/https, then enforce a hard IP floor that does NOT depend on the global
    ``LANGFLOW_SSRF_PROTECTION_ENABLED`` toggle (ops disable it for the API Request
    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)}"

View on GitHub (pinned to 976ec789d2)

Solutions

  1. Set the webhook URL to an http:// or https:// endpoint that can receive POST callbacks
  2. Trim/validate the URL client-side before registration (scheme check is the cheapest guard)
  3. For local dev use http://<lan-ip>:port, not ws:// — push notifications are HTTP POSTs, not sockets

Example fix

# before
url = "ws://receiver.internal:9000/hook"
# after
url = "http://receiver.internal:9000/hook"
Defensive patterns

Strategy: validation

Validate before calling

from urllib.parse import urlparse

def webhook_scheme_ok(url: str) -> bool:
    return urlparse(url).scheme in ("http", "https")

Type guard

def is_http_url(url: object) -> bool:
    return isinstance(url, str) and urlparse(url).scheme in ("http", "https") and bool(urlparse(url).netloc)

Try / catch

try:
    await client.tasks.set_push_notification(task_id, cfg)
except InvalidParamsError as e:
    if "must be http or https" in str(e):
        cfg.url = re.sub(r"^\w+://", "https://", cfg.url)
        await client.tasks.set_push_notification(task_id, cfg)
    else:
        raise

Prevention

When it happens

Trigger: tasks/pushNotificationConfig/set (or message/send with push config) with url like ftp://host/hook, ws://host/hook, or file:///path — any scheme outside {http, https}.

Common situations: Copying a WebSocket endpoint from the A2A streaming docs into the push config; trailing whitespace or a missing '//' making urlparse parse 'http:/host' into an unexpected scheme/path; templating bugs that drop the scheme entirely.

Related errors


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