risingwavelabs/risingwave · error

failed to build webhook JSON decoder

Error message

failed to build webhook JSON decoder

What it means

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.

Source

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

};

use super::WebhookTableColumnDesc;
use super::utils::{Result, err};

const WEBHOOK_FORMAT_HEADER: &str = "x-rw-webhook-format";
const WEBHOOK_ENCODE_HEADER: &str = "x-rw-webhook-encode";
const WEBHOOK_JSON_TIMESTAMP_HANDLING_HEADER: &str = "x-rw-webhook-json-timestamp-handling-mode";
const WEBHOOK_JSON_TIMESTAMPTZ_HANDLING_HEADER: &str =
    "x-rw-webhook-json-timestamptz-handling-mode";
const WEBHOOK_JSON_TIME_HANDLING_HEADER: &str = "x-rw-webhook-json-time-handling-mode";
const WEBHOOK_JSON_BIGINT_UNSIGNED_HANDLING_HEADER: &str =
    "x-rw-webhook-json-bigint-unsigned-handling-mode";
const WEBHOOK_JSON_HANDLE_TOAST_COLUMNS_HEADER: &str = "x-rw-webhook-json-handle-toast-columns";

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,
            )
        })?;

View on GitHub (pinned to 6469eb736d)

Solutions

  1. Read the inner error message to see which decoder option was rejected.
  2. Send only well-formed `x-rw-webhook-json-*` headers, or omit them entirely to use defaults (plain JSON).
  3. Validate header values against the supported modes (timestamp handling `milli`/`guess_number_unit`, encode `json`, format `plain`).
  4. Pin the producer's header set to a RisingWave version and re-check after upgrades.
  5. Test with a minimal curl request containing no custom headers to isolate the cause.

Example fix

// before (invalid header)
headers: { 'x-rw-webhook-json-supported-types': 'nubmer' }
// after (fixed or removed)
headers: { 'x-rw-webhook-format': 'plain', 'x-rw-webhook-encode': 'json' }
Defensive patterns

Strategy: validation

Validate before calling

const OK = {
  'x-rw-webhook-format': v => !v || String(v).toLowerCase() === 'plain',
  'x-rw-webhook-encode': v => !v || String(v).toLowerCase() === 'json'
};
for (const [h, ok] of Object.entries(OK))
  if (!ok(req.headers[h])) throw new Error(`invalid webhook header ${h}: ${req.headers[h]}`);

Type guard

const isPlainJsonHeaders = (h) =>
  (!h['x-rw-webhook-format'] || String(h['x-rw-webhook-format']).toLowerCase() === 'plain') &&
  (!h['x-rw-webhook-encode'] || String(h['x-rw-webhook-encode']).toLowerCase() === 'json');

Try / catch

try {
  const res = await fetch(url, { method: 'POST', headers, body });
  if (res.status === 500) console.error('decoder config rejected:', await res.text());
} catch (e) { /* surface decoder option errors */ }

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


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