unclecode/crawl4ai · warning · ValueError
invalid value for webhook header {name}
Error message
invalid value for webhook header {name} What it means
ValueError from sanitize_webhook_headers when a header value is over 2048 chars (_MAX_WEBHOOK_HEADER_VALUE) or contains CR, LF, or NUL. The CR/LF check blocks header/response-splitting injection; the length check bounds request size.
Source
Thrown at deploy/docker/webhook.py:77
_MAX_WEBHOOK_HEADERS = 20
_MAX_WEBHOOK_HEADER_VALUE = 2048
def sanitize_webhook_headers(headers: Optional[Dict[str, str]]) -> Dict[str, str]:
"""Validate user-supplied webhook headers; raise ValueError on any bad one."""
if not headers:
return {}
if len(headers) > _MAX_WEBHOOK_HEADERS:
raise ValueError("too many webhook headers")
clean: Dict[str, str] = {}
for name, value in headers.items():
if not isinstance(name, str) or not _WEBHOOK_HEADER_NAME.match(name):
raise ValueError(f"invalid webhook header name: {name!r}")
if name.lower() in _WEBHOOK_DENY_HEADERS:
raise ValueError(f"webhook header not allowed: {name}")
sval = str(value)
if len(sval) > _MAX_WEBHOOK_HEADER_VALUE or any(c in sval for c in "\r\n\x00"):
raise ValueError(f"invalid value for webhook header {name}")
clean[name] = sval
return clean
class WebhookDeliveryService:
"""Handles webhook delivery with exponential backoff retry logic."""
def __init__(self, config: Dict):
"""
Initialize the webhook delivery service.
Args:
config: Application configuration dictionary containing webhook settings
"""
self.config = config.get("webhooks", {})
self.max_attempts = self.config.get("retry", {}).get("max_attempts", 5)
self.initial_delay = self.config.get("retry", {}).get("initial_delay_ms", 1000) / 1000
self.max_delay = self.config.get("retry", {}).get("max_delay_ms", 32000) / 1000View on GitHub (pinned to 7e80152142)
Solutions
- Move large payloads out of headers into the webhook POST body
- Strip/encode CR/LF: value.replace(chr(13), '').replace(chr(10), '') or URL/base64-encode the value
- For long tokens, shorten (use an opaque ID the receiver resolves) or split across two custom headers
Example fix
# before
"headers": {"X-Payload": json.dumps(big_obj)} # newlines + >2048
# after
# big_obj travels in the webhook body; header only carries a short signature
"headers": {"X-Signature": hmac_sha256_hex(secret, body)[:64]} Defensive patterns
Strategy: validation
Validate before calling
def header_values_ok(headers: dict) -> bool:
return all(isinstance(v, str) and len(v) <= 2048
and not any(c in v for c in chr(13) + chr(10) + chr(0))
for v in headers.values()) Type guard
def is_valid_webhook_value(v) -> bool:
return (isinstance(v, str) and 0 < len(v) <= 2048
and not any(c in v for c in chr(13) + chr(10) + chr(0))) Try / catch
try:
sanitize_webhook_headers(headers)
except ValueError as e:
if "invalid value" in str(e):
headers = {k: v.replace(chr(13), "").replace(chr(10), "")[:2048] for k, v in headers.items()}
sanitize_webhook_headers(headers) Prevention
- Never place multi-line text (JSON, stack traces) into a header without flattening/encoding
- Keep tokens short: reference server-side secrets by ID instead of shipping full JWTs in headers
When it happens
Trigger: A webhook header value containing a literal newline or carriage return (multi-line values, stack traces, pretty-printed JSON), a NUL byte, or more than 2048 characters (long JWTs, base64 blobs, entire payloads stuffed into a header).
Common situations: Putting a signed payload or a long bearer/JWT token into a custom header; embedding multi-line error text or templates with newlines; binary data base64-encoded beyond the cap.
Related errors
- too many webhook headers
- URL must have a valid hostname
- invalid webhook header name: {name!r}
- webhook header not allowed: {name}
- Hook '{hook_name}' must be a callable function, got {type(ho
AI-assisted analysis of unclecode/crawl4ai@7e80152142 (2026-08-14).
Data as JSON: /api/errors/f8edf7414a1efeaf.
Report an issue: GitHub.