multica-ai/multica · warning
body must be a JSON object or array
Error message
body must be a JSON object or array
What it means
Returned by normalizeWebhookPayload when the body is valid JSON but not an object or array — e.g. a bare string, number, boolean, or null. Per the source comment, a scalar like "hello" parses fine but has no useful interpretation as a webhook payload and would land in the agent prompt as a bare primitive, so it is rejected after the early shape check (invalid JSON is a separate error).
Source
Thrown at server/internal/handler/autopilot_webhook.go:131
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)
contentType := headers.Get("Content-Type")
if i := strings.Index(contentType, ";"); i >= 0 {
contentType = strings.TrimSpace(contentType[:i])
}
env := WebhookEnvelope{
Request: WebhookRequest{
ReceivedAt: now,
ContentType: contentType,
},
}
// 1. Caller-provided envelope.
if obj, ok := asAny.(map[string]any); ok {
if eventStr, ok := obj["event"].(string); ok && eventStr != "" {View on GitHub (pinned to 2c0912b6ec)
Solutions
- Wrap the payload in an object: send {"value": 42} or {"message": "hello"} instead of a bare scalar.
- Check for double serialization in the sender — the top-level value must be {...} or [...].
- Validate the payload shape client-side before posting if you control the sender.
Example fix
# before
curl -X POST https://host/api/webhooks/autopilot -d '"deploy finished"'
# after
curl -X POST https://host/api/webhooks/autopilot \
-H 'Content-Type: application/json' \
-d '{"event":"deploy","message":"deploy finished"}' Defensive patterns
Strategy: type-guard
Validate before calling
// Sender side, before POST
function isEnvelopeShape(v: unknown): boolean {
return typeof v === "object" && v !== null;
}
if (!isEnvelopeShape(payload)) {
throw new Error("webhook payload must be a JSON object or array");
} Type guard
function isWebhookEnvelopeShape(v: unknown): v is Record<string, unknown> | unknown[] {
return typeof v === "object" && v !== null;
} Prevention
- Always post an object or array as the top-level JSON value.
- Beware double JSON.stringify — it turns the envelope into a string.
- Add a sender-side shape check in shared webhook client code.
When it happens
Trigger: POSTing a JSON scalar: body of "42", "true", "null", or a double-quoted string; webhook senders emitting a bare token; clients double-encoding so the top level becomes a string.
Common situations: Hand-rolled integrations posting a plain status string; test payloads written as JSON strings; encoding bugs where JSON.stringify is applied twice (the body becomes '"{\"event\":...}"').
Related errors
- empty body
- --runtime-config must be valid JSON: %w
- %s must be a JSON object, or 'null' to clear
- %s must be a JSON object
- --runtime-config must be valid JSON: %w
AI-assisted analysis of multica-ai/multica@2c0912b6ec (2026-08-15).
Data as JSON: /api/errors/0168b5fddbce73b9.
Report an issue: GitHub.