risingwavelabs/risingwave · error

Signature verification failed

Error message

Signature verification failed

What it means

The webhook payload's computed signature did not match the signature supplied by the client (after building the comparison expression and evaluating it). This returns HTTP 401 UNAUTHORIZED and aborts request processing. It protects webhook endpoints from unauthenticated or tampered payloads.

Solutions

  1. Verify the client signs the exact raw request body bytes that are sent.
  2. Confirm the webhook secret matches the one configured in RisingWave.
  3. Check the signature algorithm and header name match RisingWave's expectations.
  4. Ensure no intermediary (proxy/gateway) rewrites the body or headers.
  5. Log both computed and provided signatures to diagnose mismatch.

Example fix

// before
const signature = crypto.createHmac('sha256', secret).update(JSON.stringify(body)).digest('hex');
// after
const signature = crypto.createHmac('sha256', secret).update(rawBodyBuffer).digest('hex'); // sign raw bytes
Defensive patterns

Strategy: try-catch

Validate before calling

// client-side sanity check before sending
if (!signature || !rawBody) throw new Error('signature and raw body are both required');

Try / catch

// JS fetch
try {
  const res = await fetch(url, { method: 'POST', body: rawBody, headers: { signature } });
  if (res.status === 401) throw new Error('webhook signature rejected: check secret and raw-body signing');
} catch (e) { /* log secret name + body hash for diagnosis */ }

Prevention

When it happens

Trigger: POSTing to a webhook-protected table via handle_post_request or try_handle_connection with a missing, malformed, or incorrect signature header, or with a body that was modified after signing.

Common situations: Signing the wrong payload (e.g. signing pretty-printed JSON while sending compact), wrong secret configured, proxies altering the body, clock/algorithm mismatches, sending raw vs re-serialized body.

Related errors


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

Appendix: source

Thrown at src/frontend/src/webhook/utils.rs:127

    payload: &[u8],
    webhook_source_info: &risingwave_pb::catalog::WebhookSourceInfo,
) -> Result<()> {
    let is_valid = if let Some(signature_expr) = webhook_source_info.signature_expr.clone() {
        let secret = if let Some(secret_ref) = webhook_source_info.secret_ref {
            LocalSecretManager::global()
                .fill_secret(secret_ref)
                .map_err(|e| err(e, StatusCode::NOT_FOUND))?
        } else {
            String::new()
        };
        verify_signature(headers_jsonb, secret.as_str(), payload, signature_expr).await?
    } else {
        true
    };

    if !is_valid {
        return Err(err(
            anyhow!("Signature verification failed"),
            StatusCode::UNAUTHORIZED,
        ));
    }

    Ok(())
}

pub(crate) async fn verify_signature(
    headers_jsonb: JsonbVal,
    secret: &str,
    payload: &[u8],
    signature_expr: ExprNode,
) -> Result<bool> {
    let row = OwnedRow::new(vec![
        Some(headers_jsonb.into()),
        Some(secret.into()),
        Some(payload.into()),
    ]);

View on GitHub (pinned to 6469eb736d)