{"record":{"id":"f6ff6274d060ccb7","repo":"RocketChat/Rocket.Chat","slug":"integration-payload-must-be-a-json-object-not-an","errorCode":null,"errorMessage":"Integration payload must be a JSON object, not an array or primitive","messagePattern":"Integration payload must be a JSON object, not an array or primitive","errorType":"exception","errorClass":"Error","httpStatus":400,"severity":"error","filePath":"apps/meteor/server/api/webhooks.ts","lineNumber":141,"sourceCode":" * with Content-Type: application/x-www-form-urlencoded (e.g. `payload={\"text\":\"hello\"}`).\n * This function unwraps it so integrations receive the parsed JSON directly.\n */\nfunction getBodyParams(bodyParams: unknown, request: Request): Record<string, unknown> {\n\tif (!isPlainObject(bodyParams)) {\n\t\treturn {};\n\t}\n\n\tif (\n\t\trequest.headers.get('content-type')?.startsWith('application/x-www-form-urlencoded') &&\n\t\tObject.keys(bodyParams).length === 1 &&\n\t\ttypeof bodyParams.payload === 'string'\n\t) {\n\t\ttry {\n\t\t\tconst parsed = JSON.parse(bodyParams.payload);\n\n\t\t\t// Valid JSON must be an object, not an array or primitive\n\t\t\tif (!isPlainObject(parsed)) {\n\t\t\t\tthrow new Error('Integration payload must be a JSON object, not an array or primitive');\n\t\t\t}\n\n\t\t\treturn parsed;\n\t\t} catch (err) {\n\t\t\t// Invalid JSON -> return original bodyParams (backward compatibility)\n\t\t\tif (err instanceof SyntaxError) {\n\t\t\t\treturn bodyParams;\n\t\t\t}\n\t\t\tthrow err;\n\t\t}\n\t}\n\n\treturn bodyParams;\n}\n\nasync function executeIntegrationRest(\n\tthis: IntegrationThis,\n): Promise<","sourceCodeStart":123,"sourceCodeEnd":159,"githubUrl":"https://github.com/RocketChat/Rocket.Chat/blob/b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0/apps/meteor/server/api/webhooks.ts#L123-L159","documentation":"Rocket.Chat's incoming-webhook body parser throws this when a form-urlencoded POST has exactly one field, `payload`, whose value parses as valid JSON but is not a plain object (an array, string, number, boolean, or null). Slack-style integrations wrap their data as payload=<json>, and the handler must merge the decoded object's keys into the processed params, so a non-object shape is rejected. Syntactically broken JSON is deliberately tolerated (the raw bodyParams are returned for backward compatibility); only valid JSON of the wrong shape raises this error.","triggerScenarios":"POST to an integration/incoming webhook URL with Content-Type: application/x-www-form-urlencoded, a single body key `payload`, and a value such as [1,2,3] (array), \"hello\" (string), 42, true, or null — e.g. --data-urlencode 'payload=[1,2,3]'. Sending Content-Type: application/json, more than one form field, or unparseable JSON takes a different path and avoids this throw.","commonSituations":"Porting a Slack-compatible integration that submits a top-level JSON array; curl tests where quoting makes payload parse as a bare string; webhook senders that JSON.stringify a primitive instead of an object; integrations migrated from form-based APIs that assumed string payloads pass through untouched.","solutions":["Make the top-level JSON an object: send payload={\"items\":[1,2,3]} instead of payload=[1,2,3]","Or POST the JSON object directly with Content-Type: application/json, which bypasses the payload-field unwrapping entirely","Wrap any primitive under a key: payload={\"value\":\"hello\"}","Verify locally before sending: JSON.parse the payload string and assert it is a non-array object"],"exampleFix":"# before — payload parses to an array, throws\ncurl -X POST https://rocket.chat/hooks/<id> \\\n  -H 'Content-Type: application/x-www-form-urlencoded' \\\n  --data-urlencode 'payload=[1,2,3]'\n\n# after — payload parses to an object, accepted\ncurl -X POST https://rocket.chat/hooks/<id> \\\n  -H 'Content-Type: application/x-www-form-urlencoded' \\\n  --data-urlencode 'payload={\"items\":[1,2,3]}'","handlingStrategy":"type-guard","validationCode":"const raw = typeof payload === 'string' ? payload : JSON.stringify(payload);\nconst parsed = JSON.parse(raw); // throws locally on bad JSON — fix before sending\nif (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {\n  throw new Error('payload must be a JSON object');\n}\nawait fetch(webhookUrl, {\n  method: 'POST',\n  headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n  body: new URLSearchParams({ payload: raw }),\n});","typeGuard":"const isPlainObject = (v: unknown): v is Record<string, unknown> =>\n  typeof v === 'object' && v !== null && !Array.isArray(v);","tryCatchPattern":"try {\n  const res = await fetch(webhookUrl, { method: 'POST', body: new URLSearchParams({ payload: raw }) });\n  if (!res.ok) console.error(await res.text());\n} catch (err) {\n  // transport-level failure only; a 400 body names the payload-shape problem\n}","preventionTips":["Always build the payload as an object literal, never stringify an array or primitive","Add a unit test asserting the outgoing payload parses to a non-array object","In senders, centralize payload construction behind a helper that applies the isPlainObject guard"],"tags":["webhook","json","integration","http","validation"],"backgroundTag":"webhook-payload-validation","analyzedSha":"b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0","analyzedAt":"2026-08-18T15:26:39.429Z","contentChangedAt":"2026-08-18T15:26:39.429Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}