{"record":{"id":"5ea6b3568bfeb254","repo":"risingwavelabs/risingwave","slug":"failed-to-parse-request-body","errorCode":null,"errorMessage":"failed to parse request body","messagePattern":"failed to parse request body","errorType":"http","errorClass":null,"httpStatus":422,"severity":"error","filePath":"src/frontend/src/webhook/mod.rs","lineNumber":191,"sourceCode":"\n        if res.status == fast_insert_response::Status::Succeeded as i32 {\n            Ok(())\n        } else {\n            Err(err(\n                anyhow!(\"Fast insert failed: {}\", res.error_message),\n                StatusCode::INTERNAL_SERVER_ERROR,\n            ))\n        }\n    }\n\n    fn generate_data_chunk(is_batched: bool, body: &Bytes) -> Result<DataChunk> {\n        let mut builder = JsonbArrayBuilder::with_type(1, DataType::Jsonb);\n\n        if !is_batched {\n            // Use builder to obtain a single column & single row DataChunk\n            let json_value = Value::from_text(body).map_err(|e| {\n                err(\n                    anyhow!(e).context(\"failed to parse request body\"),\n                    StatusCode::UNPROCESSABLE_ENTITY,\n                )\n            })?;\n\n            let jsonb_val = JsonbVal::from(json_value);\n            builder.append(Some(jsonb_val.as_scalar_ref()));\n\n            Ok(DataChunk::new(vec![builder.finish().into_ref()], 1))\n        } else {\n            let rows: Vec<_> = body\n                .split(|&b| b == b'\\n')\n                .filter(|b| !b.is_empty())\n                .collect();\n\n            for row in &rows {\n                let json_value = Value::from_text(row).map_err(|e| {\n                    err(\n                        anyhow!(e).context(\"failed to parse request body\"),","sourceCodeStart":173,"sourceCodeEnd":209,"githubUrl":"https://github.com/risingwavelabs/risingwave/blob/6469eb736d691e8e9b8a419a57edd6429ca77417/src/frontend/src/webhook/mod.rs#L173-L209","documentation":"When the webhook body is not batched, generate_data_chunk parses the entire body as a single JSON value via Value::from_text. If parsing fails, the error is wrapped with context 'failed to parse request body' and returned with HTTP 422 (Unprocessable Entity). The webhook expects a valid JSON document per row.","triggerScenarios":"POSTing a non-batched webhook request whose body is not valid JSON: empty body, truncated JSON, HTML/text payloads, wrong Content-Type content, or JSON that fails jsonb parsing rules (e.g. duplicate handling, non-object roots where required).","commonSituations":"Senders posting form-encoded or plain-text data; proxies truncating large bodies; webhook producers emitting NDJSON (multiple JSON lines) to a single-object endpoint.","solutions":["Validate the body is a single well-formed JSON document (jq . body.json should succeed).","Set Content-Type: application/json and send the raw JSON object as the body.","If sending multiple records, use the batched endpoint format (array of objects) instead.","Log the raw body server-side and re-test with curl --data-binary to rule out encoding issues."],"exampleFix":"// before\ncurl -X POST -d 'key=value' .../webhook/table/orders\n\n// after\ncurl -X POST -H 'Content-Type: application/json' -d '{\"id\":1,\"item\":\"x\"}' .../webhook/table/orders","handlingStrategy":"validation","validationCode":"// client-side pre-check\nconst body = await res.text();\nJSON.parse(body); // throws locally before sending if invalid\nif (!body.trim().startsWith('{')) throw new Error('webhook body must be a single JSON object');","typeGuard":"function isJsonObject(s: string): boolean {\n  try { const v = JSON.parse(s); return typeof v === 'object' && v !== null && !Array.isArray(v); }\n  catch { return false; }\n}","tryCatchPattern":"try {\n  await postWebhook(url, body);\n} catch (e) {\n  if (e.status === 422 && e.message.includes('failed to parse request body')) {\n    logInvalidBody(rawBody); // inspect and fix producer\n  } else { throw e; }\n}","preventionTips":["Always send Content-Type: application/json with a raw JSON object body.","Validate the payload with a JSON parser before sending.","Use the batched format for multiple records, not NDJSON to a single-row endpoint.","Guard against proxies/gateways truncating or re-encoding bodies."],"tags":["webhook","json","http-422","parsing"],"backgroundTag":"json-parse-error","analyzedSha":"6469eb736d691e8e9b8a419a57edd6429ca77417","analyzedAt":"2026-09-11T21:06:21.487Z","contentChangedAt":"2026-09-11T21:06:21.487Z","schemaVersion":2},"datasetVersion":"2026-09-14T16:17:12.679Z"}