risingwavelabs/risingwave · warning

failed to decode webhook JSON payload

Error message

failed to decode webhook JSON payload

What it means

The webhook JSON body could not be parsed into JSON access values (`generate_json_access` failed). The request payload is not valid JSON or does not match the decoder's expectations, so the service rejects it with HTTP 422 UNPROCESSABLE_ENTITY. This is a client-side payload problem, not a server fault.

Source

Thrown at src/frontend/src/webhook/payload.rs:55

pub(crate) fn build_json_access_builder(headers: &HeaderMap) -> Result<JsonAccessBuilder> {
    JsonAccessBuilder::new(json_properties_from_headers(headers)?).map_err(|e| {
        err(
            anyhow!(e).context("failed to build webhook JSON decoder"),
            StatusCode::INTERNAL_SERVER_ERROR,
        )
    })
}

pub(crate) fn owned_row_from_payload_row(
    access_builder: &mut JsonAccessBuilder,
    columns: &[WebhookTableColumnDesc],
    payload_row: &[u8],
) -> Result<OwnedRow> {
    let access = access_builder
        .generate_json_access(payload_row.to_vec())
        .map_err(|e| {
            err(
                anyhow!(e).context("failed to decode webhook JSON payload"),
                StatusCode::UNPROCESSABLE_ENTITY,
            )
        })?;
    let row = columns
        .iter()
        .map(
            |column| match access.access(&[column.name.as_str()], &column.data_type) {
                Ok(datum) => Ok(datum.to_owned_datum()),
                Err(error) if column.is_pk => Err(error),
                Err(_) => Ok(None),
            },
        )
        .collect::<AccessResult<Vec<_>>>()
        .map(OwnedRow::new);
    row.map_err(|e| {
        err(
            anyhow!(e).context("failed to decode webhook JSON payload"),
            StatusCode::UNPROCESSABLE_ENTITY,

View on GitHub (pinned to 6469eb736d)

Solutions

  1. Validate the body is well-formed JSON (e.g. JSON.parse in the producer) before sending.
  2. Set `Content-Type: application/json` and send a UTF-8 JSON document.
  3. Log/inspect the exact payload bytes that were rejected and compare with the table's expected schema.
  4. Ensure no proxy/gateway is truncating or transforming the body.
  5. For large payloads, check the JSON column handling headers and payload size limits.

Example fix

// before
send(JSON.stringify({a: 1,})); // trailing comma -> invalid JSON
// after
const body = JSON.stringify({a: 1});
JSON.parse(body); // sanity check
send(body);
Defensive patterns

Strategy: validation

Validate before calling

// validate payload before posting
const body = JSON.stringify(event);
JSON.parse(body); // throws locally if invalid
const res = await fetch(url, { method: 'POST', headers: {'content-type':'application/json'}, body });

Type guard

function isJsonObject(x) { return x !== null && typeof x === 'object' && !Array.isArray(x); }
if (!isJsonObject(event)) throw new Error('webhook payload must be a JSON object');

Try / catch

try {
  const res = await fetch(url, { method: 'POST', headers: {'content-type':'application/json'}, body });
  if (res.status === 422) throw new Error('payload rejected as invalid JSON: ' + (await res.text()));
} catch (e) { log('webhook payload error', e); }

Prevention

When it happens

Trigger: POST a body that is not valid JSON (trailing comma, truncated payload, HTML error page); binary or form-encoded body sent to a JSON webhook; payload exceeds decoder limits or contains a structure the accessor cannot traverse.

Common situations: Producer serializes with the wrong content type; a gateway truncates large bodies; payload is form-urlencoded or multipart instead of JSON; a failed upstream call returns an error page that gets forwarded to the webhook.

Understand the failure class

Related errors


AI-assisted analysis of risingwavelabs/risingwave@6469eb736d (2026-09-11). Data as JSON: /api/errors/1a45582d413db323. Report an issue: GitHub.