{"record":{"id":"bddeb81890b97f15","repo":"unclecode/crawl4ai","slug":"invalid-webhook-header-name-name-r","errorCode":null,"errorMessage":"invalid webhook header name: {name!r}","messagePattern":"invalid webhook header name: (.+?)","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"warning","filePath":"deploy/docker/webhook.py","lineNumber":72,"sourceCode":"_WEBHOOK_DENY_HEADERS = {\n    \"host\", \"content-length\", \"transfer-encoding\", \"connection\",\n    \"content-type\", \"proxy-authorization\", \"authorization\", \"cookie\",\n    \"expect\", \"upgrade\", \"te\", \"trailer\",\n}\n_MAX_WEBHOOK_HEADERS = 20\n_MAX_WEBHOOK_HEADER_VALUE = 2048\n\n\ndef sanitize_webhook_headers(headers: Optional[Dict[str, str]]) -> Dict[str, str]:\n    \"\"\"Validate user-supplied webhook headers; raise ValueError on any bad one.\"\"\"\n    if not headers:\n        return {}\n    if len(headers) > _MAX_WEBHOOK_HEADERS:\n        raise ValueError(\"too many webhook headers\")\n    clean: Dict[str, str] = {}\n    for name, value in headers.items():\n        if not isinstance(name, str) or not _WEBHOOK_HEADER_NAME.match(name):\n            raise ValueError(f\"invalid webhook header name: {name!r}\")\n        if name.lower() in _WEBHOOK_DENY_HEADERS:\n            raise ValueError(f\"webhook header not allowed: {name}\")\n        sval = str(value)\n        if len(sval) > _MAX_WEBHOOK_HEADER_VALUE or any(c in sval for c in \"\\r\\n\\x00\"):\n            raise ValueError(f\"invalid value for webhook header {name}\")\n        clean[name] = sval\n    return clean\n\n\nclass WebhookDeliveryService:\n    \"\"\"Handles webhook delivery with exponential backoff retry logic.\"\"\"\n\n    def __init__(self, config: Dict):\n        \"\"\"\n        Initialize the webhook delivery service.\n\n        Args:\n            config: Application configuration dictionary containing webhook settings","sourceCodeStart":54,"sourceCodeEnd":90,"githubUrl":"https://github.com/unclecode/crawl4ai/blob/7e801521428ee12509994d39151006f64055ebe3/deploy/docker/webhook.py#L54-L90","documentation":"ValueError from sanitize_webhook_headers when a header name is not a string or fails the _WEBHOOK_HEADER_NAME regex (valid RFC 7230 token characters only). The offending name is echoed in quotes for debugging.","triggerScenarios":"A webhook headers key containing spaces ('X My Header'), non-ASCII, brackets, or empty string; or a non-str key (int, None) coming from JSON with odd keys or programmatic dict building.","commonSituations":"Client copies browser request headers including formatting quirks; using 'X-Custom Header:' style labels; keys generated from f-strings with stray whitespace or newlines.","solutions":["Use standard token names: letters, digits, and hyphens (e.g. X-Custom-Header)","Sanitize on the client: re.sub(r'[^A-Za-z0-9_-]', '-', name) before sending","Assert len(name) > 0 and name == name.strip() when generating names dynamically"],"exampleFix":"# before\n\"headers\": {\"X Crawl Source\": \"batch-1\"}\n\n# after\n\"headers\": {\"X-Crawl-Source\": \"batch-1\"}","handlingStrategy":"validation","validationCode":"import re\nTOKEN = re.compile(r\"^[A-Za-z0-9!#$%&'*+.^_`|~-]+$\")\n\ndef valid_header_names(headers: dict) -> bool:\n    return all(isinstance(k, str) and TOKEN.match(k) for k in headers)","typeGuard":"def is_valid_webhook_header_set(h) -> bool:\n    return (isinstance(h, dict) and len(h) <= 20\n            and all(isinstance(k, str) and TOKEN.match(k) for k in h))","tryCatchPattern":"try:\n    sanitize_webhook_headers(headers)\nexcept ValueError as e:\n    if \"header name\" in str(e):\n        headers = {re.sub(r\"[^A-Za-z0-9-]\", \"-\", k): v for k, v in headers.items()}","preventionTips":["Generate header names from a fixed vocabulary (X-*) rather than free-form text","Strip whitespace and reject empty keys when building header dicts programmatically"],"tags":["crawl4ai","webhook","validation","http-headers"],"backgroundTag":null,"analyzedSha":"7e801521428ee12509994d39151006f64055ebe3","analyzedAt":"2026-08-14T20:46:20.673Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}