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) / 1000

View on GitHub (pinned to 7e80152142)

Solutions

  1. Move large payloads out of headers into the webhook POST body
  2. Strip/encode CR/LF: value.replace(chr(13), '').replace(chr(10), '') or URL/base64-encode the value
  3. 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

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


AI-assisted analysis of unclecode/crawl4ai@7e80152142 (2026-08-14). Data as JSON: /api/errors/f8edf7414a1efeaf. Report an issue: GitHub.