multica-ai/multica · warning
empty body
Error message
empty body
What it means
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.
Source
Thrown at server/internal/handler/autopilot_webhook.go:116
// normalizeWebhookPayload parses an incoming webhook body and returns a
// WebhookEnvelope. Rules:
//
// 1. Body must be a valid JSON object or array. Scalars / invalid JSON
// return an error so the handler can respond 400.
// 2. If the body is an object containing a string `event` and any
// `eventPayload`, those are preserved as-is.
// 3. Otherwise `event` is inferred from headers/body fields, and the entire
// original body becomes `eventPayload`.
// 4. The default event is `webhook.received`.
//
// Inference order:
//
// X-GitHub-Event (combined with body.action when present),
// X-Gitlab-Event, X-Event-Type, body.event, body.type, body.action.
func normalizeWebhookPayload(body []byte, headers http.Header) (WebhookEnvelope, error) {
body = stripBOM(body)
if len(body) == 0 {
return WebhookEnvelope{}, errors.New("empty body")
}
// First, validate JSON shape (object or array). Reject scalars early —
// `"hello"` is technically valid JSON but has no useful interpretation
// as a webhook payload and would land in the agent prompt as a bare
// string.
var asAny any
if err := json.Unmarshal(body, &asAny); err != nil {
return WebhookEnvelope{}, fmt.Errorf("invalid json: %w", err)
}
switch asAny.(type) {
case map[string]any, []any:
// ok
default:
return WebhookEnvelope{}, errors.New("body must be a JSON object or array")
}
now := time.Now().UTC().Format(time.RFC3339)View on GitHub (pinned to 2c0912b6ec)
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.
Example fix
# before
curl -X POST https://host/api/webhooks/autopilot
# after
curl -X POST https://host/api/webhooks/autopilot \
-H 'Content-Type: application/json' \
-d '{"event":"ping","action":"opened"}' Defensive patterns
Strategy: validation
Validate before calling
// Sender side, before POST
const body = JSON.stringify(payload ?? {});
if (!body || body === "null") {
throw new Error("webhook payload is empty");
}
await fetch(url, { method: "POST", headers: { "Content-Type": "application/json" }, body }); Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- body must be a JSON object or array
- copy agent: %w
- create daemon checkout request: %w
- create skill: %w
- Invalid desktop runtime config: ${field} must use http or ht
AI-assisted analysis of multica-ai/multica@2c0912b6ec (2026-08-15).
Data as JSON: /api/errors/b1c55a0a39d8af8e.
Report an issue: GitHub.