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
- Set the webhook URL to an http:// or https:// endpoint that can receive POST callbacks
- Trim/validate the URL client-side before registration (scheme check is the cheapest guard)
- 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
- Construct webhook URLs only via URL builders that enforce the scheme
- Reject ws/ftp schemes in your config schema before they reach the API
- Reserve a single well-known https webhook base URL in configuration
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
- webhook url has no host
- 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/f173fc12e75da290.
Report an issue: GitHub.