RocketChat/Rocket.Chat · error · Error

Integration payload must be a JSON object, not an array or p

Error message

Integration payload must be a JSON object, not an array or primitive

What it means

Thrown by getBodyParams() in the incoming-webhook body parser when the request is form-urlencoded with a single 'payload' field, the payload parses as valid JSON, but the parsed value is not a plain object (it is an array or a primitive like a string/number/boolean). Slack/GitHub-style webhooks wrap a JSON object in payload; this guard rejects malformed integrations that wrap non-object JSON.

Source

Thrown at apps/meteor/server/api/webhooks.ts:141

 * with Content-Type: application/x-www-form-urlencoded (e.g. `payload={"text":"hello"}`).
 * This function unwraps it so integrations receive the parsed JSON directly.
 */
function getBodyParams(bodyParams: unknown, request: Request): Record<string, unknown> {
	if (!isPlainObject(bodyParams)) {
		return {};
	}

	if (
		request.headers.get('content-type')?.startsWith('application/x-www-form-urlencoded') &&
		Object.keys(bodyParams).length === 1 &&
		typeof bodyParams.payload === 'string'
	) {
		try {
			const parsed = JSON.parse(bodyParams.payload);

			// Valid JSON must be an object, not an array or primitive
			if (!isPlainObject(parsed)) {
				throw new Error('Integration payload must be a JSON object, not an array or primitive');
			}

			return parsed;
		} catch (err) {
			// Invalid JSON -> return original bodyParams (backward compatibility)
			if (err instanceof SyntaxError) {
				return bodyParams;
			}
			throw err;
		}
	}

	return bodyParams;
}

async function executeIntegrationRest(
	this: IntegrationThis,
): Promise<

View on GitHub (pinned to f9d3ec372b)

Solutions

  1. Ensure the upstream sends payload as a JSON object literal, e.g. payload={"text":"hello"}.
  2. If the upstream legitimately sends an array, wrap it in an object before forwarding: {"events":[...]}
  3. Switch the Content-Type to application/json and POST the object directly if the wrapper is not required.

Example fix

// before - upstream sends
// Content-Type: application/x-www-form-urlencoded
// payload=[{"text":"hi"}]

// after
// Content-Type: application/x-www-form-urlencoded
// payload={"items":[{"text":"hi"}]}
Defensive patterns

Strategy: type-guard

Validate before calling

function isPlainObject(v) { return typeof v === 'object' && v !== null && !Array.isArray(v); }
if (typeof bodyParams.payload === 'string') {
  const parsed = JSON.parse(bodyParams.payload);
  if (!isPlainObject(parsed)) throw new ClientError('payload must be object');
}

Type guard

function isPlainObject(v) {
  if (v === null || typeof v !== 'object') return false;
  const proto = Object.getPrototypeOf(v);
  return proto === Object.prototype || proto === null;
}

Prevention

When it happens

Trigger: POST to a webhook URL with Content-Type application/x-www-form-urlencoded and body payload=[1,2,3] or payload="hello" or payload=42. The JSON is syntactically valid but not an object, so the parser refuses to substitute it for the body params.

Common situations: An integration forwards an array-wrapped event instead of an object. A misconfigured upstream service sends payload=<string>. A migration tool emits top-level arrays.

Related errors


AI-assisted analysis of RocketChat/Rocket.Chat@f9d3ec372b (2026-08-12). Data as JSON: /api/errors/f6ff6274d060ccb7. Report an issue: GitHub.