langflow-ai/langflow · error · ValueError
webhook url is not allowed: {exc}
Error message
webhook url is not allowed: {exc} What it means
Raised by validate_webhook_url when the framework-level SSRF validation (validate_and_resolve_url) rejects the URL with SSRFProtectionError — e.g. the host is not on the configured allowlist, or it resolves into CGNAT/other space the framework blocks beyond the hard floor. This check supplies the allowlist/global-toggle policy on top of the private-IP floor, and the error chains the original framework message.
Source
Thrown at src/backend/base/langflow/api/v1/a2a_utils.py:135
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()
return (folder.auth_settings or {}).get("auth_type", "none") if folder else "none"
View on GitHub (pinned to 976ec789d2)
Solutions
- Add the webhook host to the server's SSRF allowlist configuration and retry
- Use a webhook host that the framework classifies as globally routable and allowlisted
- If policy allows, relax the framework SSRF setting — but keep the private-IP floor in mind (it is independent)
Example fix
# before: host hooks.vendor.io not allowlisted # after (server env): LANGFLOW_SSRF_ALLOWLIST=hooks.vendor.io
Defensive patterns
Strategy: fallback
Validate before calling
def webhook_host_allowlisted(url: str, allowlist: set[str]) -> bool:
from urllib.parse import urlparse
return (urlparse(url).hostname or "") in allowlist Try / catch
try:
await client.tasks.set_push_notification(task_id, cfg)
except InvalidParamsError as e:
if "not allowed" in str(e):
cfg.url = ALLOWLISTED_HOOK_URL # fall back to the vetted receiver
await client.tasks.set_push_notification(task_id, cfg)
else:
raise Prevention
- Keep the client's webhook host list in sync with the server's SSRF allowlist
- Prefer one canonical allowlisted hooks domain for all A2A callbacks
- On 'not allowed', request an allowlist change instead of retrying — it is a policy denial
When it happens
Trigger: SSRF protection enabled with an allowlist that does not include the webhook host; the URL resolves to 100.64.0.0/10 (CGNAT) or another range the framework flags as non-global even though it is not in the basic private floor.
Common situations: Hardened deployments that set LANGFLOW_SSRF_ALLOWLIST (or equivalent) so only vetted webhook hosts are permitted; webhooks pointing at shared cloud egress IPs that map into CGNAT ranges.
Related errors
- str(exc)
- webhook url resolves to a blocked address: {', '.join(blocke
- webhook url must be http or https
- webhook url has no host
- webhook url has an invalid host: {exc}
AI-assisted analysis of langflow-ai/langflow@976ec789d2 (2026-08-14).
Data as JSON: /api/errors/42e1042dc91832dc.
Report an issue: GitHub.