bytedance/deer-flow · warning · HTTPException

Invalid JSON body

Error message

Invalid JSON body

What it means

Raised by the GitHub webhook receiver with status 400 when the (already signature-verified) request body fails json.loads — the payload is not valid JSON. Because verification happens first, a 400 here with a valid signature usually means GitHub sent an empty or non-JSON body, or the proxy corrupted the body after signature-relevant bytes (empty body maps to {}, so this specifically means non-empty, unparseable bytes).

Source

Thrown at backend/app/gateway/routers/github_webhooks.py:267

                x_github_event,
                x_github_delivery,
            )
            raise HTTPException(status_code=401, detail="Invalid or missing X-Hub-Signature-256")

    if not x_github_event:
        raise HTTPException(status_code=400, detail="Missing X-GitHub-Event header")

    # Parse JSON payload after signature is verified (verify-then-parse).
    try:
        payload: dict[str, Any] = json.loads(body) if body else {}
    except json.JSONDecodeError as exc:
        logger.warning(
            "github_webhook: invalid JSON body (event=%s delivery=%s): %s",
            x_github_event,
            x_github_delivery,
            exc,
        )
        raise HTTPException(status_code=400, detail="Invalid JSON body") from exc

    if x_github_event in _KNOWN_EVENTS:
        logger.info(
            "github_webhook delivery=%s | %s",
            x_github_delivery,
            _summarise_event(x_github_event, payload),
        )
        handled = True
        # Publish inbound messages onto the channel bus so the
        # ChannelManager picks them up and routes them to the right
        # custom agents. No direct agent-run calls here.
        from app.channels.service import get_channel_service

        service = get_channel_service()
        if service is None:
            # Permanent state, not a transient failure: ``channels.github``
            # is not enabled in this deployment. Returning 503 would mark
            # this delivery "failed" and invite a manual "Redeliver" or an

View on GitHub (pinned to 1dd6ba1acb)

Solutions

  1. In GitHub webhook/App settings, set Content type to application/json.
  2. Verify the proxy forwards the body unmodified and handles content-encoding (identity or properly decoded).
  3. For manual tests, validate the file first: python -m json.tool payload.json.

Example fix

# before: GitHub webhook content type = application/x-www-form-urlencoded
# payload arrives as 'payload=%7B...%7D' -> 400 Invalid JSON body

# after: GitHub repo Settings -> Webhooks -> Edit -> Content type = application/json
# then click 'Redeliver' on a recent delivery
Defensive patterns

Strategy: validation

Validate before calling

const okPayload = (raw: string) => { try { JSON.parse(raw); return true; } catch { return false; } };
if (!okPayload(body)) throw new Error('webhook body is not JSON — check GitHub content type');

Prevention

When it happens

Trigger: Sending form-encoded (application/x-www-form-urlencoded) payloads instead of JSON — GitHub does this when the webhook content-type is configured as form; proxies mangling the body (truncation, chunked-encoding bugs); handcrafted test requests with malformed JSON.

Common situations: GitHub webhook 'Content type' set to application/x-www-form-urlencoded in repo settings; gzip/deflate content-encoding not decoded by an intermediary; truncated bodies from MTU/proxy limits.

Understand the failure class

Related errors


AI-assisted analysis of bytedance/deer-flow@1dd6ba1acb (2026-08-14). Data as JSON: /api/errors/21c70ba827f998e3. Report an issue: GitHub.