{"record":{"id":"f176de4b7c0669a4","repo":"risingwavelabs/risingwave","slug":"failed-to-build-webhook-json-decoder","errorCode":null,"errorMessage":"failed to build webhook JSON decoder","messagePattern":"failed to build webhook JSON decoder","errorType":"http","errorClass":null,"httpStatus":500,"severity":"error","filePath":"src/frontend/src/webhook/payload.rs","lineNumber":40,"sourceCode":"};\n\nuse super::WebhookTableColumnDesc;\nuse super::utils::{Result, err};\n\nconst WEBHOOK_FORMAT_HEADER: &str = \"x-rw-webhook-format\";\nconst WEBHOOK_ENCODE_HEADER: &str = \"x-rw-webhook-encode\";\nconst WEBHOOK_JSON_TIMESTAMP_HANDLING_HEADER: &str = \"x-rw-webhook-json-timestamp-handling-mode\";\nconst WEBHOOK_JSON_TIMESTAMPTZ_HANDLING_HEADER: &str =\n    \"x-rw-webhook-json-timestamptz-handling-mode\";\nconst WEBHOOK_JSON_TIME_HANDLING_HEADER: &str = \"x-rw-webhook-json-time-handling-mode\";\nconst WEBHOOK_JSON_BIGINT_UNSIGNED_HANDLING_HEADER: &str =\n    \"x-rw-webhook-json-bigint-unsigned-handling-mode\";\nconst WEBHOOK_JSON_HANDLE_TOAST_COLUMNS_HEADER: &str = \"x-rw-webhook-json-handle-toast-columns\";\n\npub(crate) fn build_json_access_builder(headers: &HeaderMap) -> Result<JsonAccessBuilder> {\n    JsonAccessBuilder::new(json_properties_from_headers(headers)?).map_err(|e| {\n        err(\n            anyhow!(e).context(\"failed to build webhook JSON decoder\"),\n            StatusCode::INTERNAL_SERVER_ERROR,\n        )\n    })\n}\n\npub(crate) fn owned_row_from_payload_row(\n    access_builder: &mut JsonAccessBuilder,\n    columns: &[WebhookTableColumnDesc],\n    payload_row: &[u8],\n) -> Result<OwnedRow> {\n    let access = access_builder\n        .generate_json_access(payload_row.to_vec())\n        .map_err(|e| {\n            err(\n                anyhow!(e).context(\"failed to decode webhook JSON payload\"),\n                StatusCode::UNPROCESSABLE_ENTITY,\n            )\n        })?;","sourceCodeStart":22,"sourceCodeEnd":58,"githubUrl":"https://github.com/risingwavelabs/risingwave/blob/6469eb736d691e8e9b8a419a57edd6429ca77417/src/frontend/src/webhook/payload.rs#L22-L58","documentation":"The `JsonAccessBuilder` — the decoder that turns the webhook JSON body into column accessors — could not be constructed from the request's decoder configuration headers (`x-rw-webhook-*`). `JsonAccessBuilder::new` validates the supplied JsonProperties; an invalid combination fails construction and the request is rejected with HTTP 500. The message is a wrapper; the inner error names the offending option.","triggerScenarios":"POST carrying invalid/contradictory `x-rw-webhook-json-*` headers (e.g. malformed `x-rw-webhook-json-supported-types`, invalid bigint handling mode) so `JsonAccessBuilder::new(json_properties_from_headers(headers))` fails.","commonSituations":"A producer SDK or proxy adds decoder headers with wrong values or typos; header semantics changed after a RisingWave upgrade; hand-crafted curl requests copy an option name incorrectly.","solutions":["Read the inner error message to see which decoder option was rejected.","Send only well-formed `x-rw-webhook-json-*` headers, or omit them entirely to use defaults (plain JSON).","Validate header values against the supported modes (timestamp handling `milli`/`guess_number_unit`, encode `json`, format `plain`).","Pin the producer's header set to a RisingWave version and re-check after upgrades.","Test with a minimal curl request containing no custom headers to isolate the cause."],"exampleFix":"// before (invalid header)\nheaders: { 'x-rw-webhook-json-supported-types': 'nubmer' }\n// after (fixed or removed)\nheaders: { 'x-rw-webhook-format': 'plain', 'x-rw-webhook-encode': 'json' }","handlingStrategy":"validation","validationCode":"const OK = {\n  'x-rw-webhook-format': v => !v || String(v).toLowerCase() === 'plain',\n  'x-rw-webhook-encode': v => !v || String(v).toLowerCase() === 'json'\n};\nfor (const [h, ok] of Object.entries(OK))\n  if (!ok(req.headers[h])) throw new Error(`invalid webhook header ${h}: ${req.headers[h]}`);","typeGuard":"const isPlainJsonHeaders = (h) =>\n  (!h['x-rw-webhook-format'] || String(h['x-rw-webhook-format']).toLowerCase() === 'plain') &&\n  (!h['x-rw-webhook-encode'] || String(h['x-rw-webhook-encode']).toLowerCase() === 'json');","tryCatchPattern":"try {\n  const res = await fetch(url, { method: 'POST', headers, body });\n  if (res.status === 500) console.error('decoder config rejected:', await res.text());\n} catch (e) { /* surface decoder option errors */ }","preventionTips":["Keep the custom x-rw-webhook-* headers minimal; rely on defaults","Validate header values against documented supported modes before deploy","Re-validate headers after RisingWave version upgrades","Log the full header set when webhook requests fail"],"tags":["webhook","json","configuration","http"],"backgroundTag":"invalid-config-value","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"}