{"record":{"id":"b1c55a0a39d8af8e","repo":"multica-ai/multica","slug":"empty-body","errorCode":null,"errorMessage":"empty body","messagePattern":"empty body","errorType":"validation","errorClass":null,"httpStatus":400,"severity":"warning","filePath":"server/internal/handler/autopilot_webhook.go","lineNumber":116,"sourceCode":"// normalizeWebhookPayload parses an incoming webhook body and returns a\n// WebhookEnvelope. Rules:\n//\n//  1. Body must be a valid JSON object or array. Scalars / invalid JSON\n//     return an error so the handler can respond 400.\n//  2. If the body is an object containing a string `event` and any\n//     `eventPayload`, those are preserved as-is.\n//  3. Otherwise `event` is inferred from headers/body fields, and the entire\n//     original body becomes `eventPayload`.\n//  4. The default event is `webhook.received`.\n//\n// Inference order:\n//\n//\tX-GitHub-Event (combined with body.action when present),\n//\tX-Gitlab-Event, X-Event-Type, body.event, body.type, body.action.\nfunc normalizeWebhookPayload(body []byte, headers http.Header) (WebhookEnvelope, error) {\n\tbody = stripBOM(body)\n\tif len(body) == 0 {\n\t\treturn WebhookEnvelope{}, errors.New(\"empty body\")\n\t}\n\n\t// First, validate JSON shape (object or array). Reject scalars early —\n\t// `\"hello\"` is technically valid JSON but has no useful interpretation\n\t// as a webhook payload and would land in the agent prompt as a bare\n\t// string.\n\tvar asAny any\n\tif err := json.Unmarshal(body, &asAny); err != nil {\n\t\treturn WebhookEnvelope{}, fmt.Errorf(\"invalid json: %w\", err)\n\t}\n\tswitch asAny.(type) {\n\tcase map[string]any, []any:\n\t\t// ok\n\tdefault:\n\t\treturn WebhookEnvelope{}, errors.New(\"body must be a JSON object or array\")\n\t}\n\n\tnow := time.Now().UTC().Format(time.RFC3339)","sourceCodeStart":98,"sourceCodeEnd":134,"githubUrl":"https://github.com/multica-ai/multica/blob/2c0912b6ec764b373d44eeea1e80f0d9f11ab417/server/internal/handler/autopilot_webhook.go#L98-L134","documentation":"Returned by normalizeWebhookPayload in server/internal/handler/autopilot_webhook.go when the incoming webhook body is empty after BOM stripping. The handler refuses to fabricate an envelope from nothing: with no bytes there is no event to infer (X-GitHub-Event, body.event, etc. all need a body) and no payload to hand the agent. It is a request-shape rejection at the trust boundary, before any JSON parsing.","triggerScenarios":"POST to the autopilot webhook endpoint with a zero-length body (Content-Length: 0); a webhook sender issuing an empty POST for certain event types; a misconfigured proxy stripping the body; curl test without -d.","commonSituations":"Health-check probes hitting the webhook URL with empty POSTs; provider integrations that send notification-only pings with no body; reverse proxies misconfiguring request buffering.","solutions":["Send an actual JSON payload (object or array) in the request body.","If testing, use curl with a data argument: curl -X POST -H 'Content-Type: application/json' -d '{\"event\":\"ping\"}' <url>.","Point health checks at a dedicated health endpoint instead of the webhook URL.","Check proxy/ingress config if legitimate provider payloads arrive empty."],"exampleFix":"# before\ncurl -X POST https://host/api/webhooks/autopilot\n\n# after\ncurl -X POST https://host/api/webhooks/autopilot \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"event\":\"ping\",\"action\":\"opened\"}'","handlingStrategy":"validation","validationCode":"// Sender side, before POST\nconst body = JSON.stringify(payload ?? {});\nif (!body || body === \"null\") {\n  throw new Error(\"webhook payload is empty\");\n}\nawait fetch(url, { method: \"POST\", headers: { \"Content-Type\": \"application/json\" }, body });","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always send a JSON body, even for ping-style notifications ({} at minimum).","Point health checks at the health endpoint, not the webhook URL.","Verify proxy/ingress request buffering is not dropping bodies."],"tags":["webhook","validation","http","go"],"backgroundTag":null,"analyzedSha":"2c0912b6ec764b373d44eeea1e80f0d9f11ab417","analyzedAt":"2026-08-15T13:25:18.241Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}