hasura/graphql-engine · error · Error

Unexpected status code: {0}

Error message

Unexpected status code: {0}

What it means

The pre-NDC-response plugin replied with an HTTP status code the engine does not recognize as part of the hook protocol (anything other than the expected success and defined error statuses). The numeric status is included. This means the plugin endpoint is reachable but behaving as a plain HTTP service rather than a hook — e.g. 404 from a wrong path, or 502/503 from an ingress in front of it.

Source

Thrown at v3/crates/plugins/pre-ndc-response-plugin/src/execute.rs:22

use metadata_resolve::{DataConnectorLink, Qualified, ResolvedLifecyclePreNdcResponsePluginHook};
use open_dds::data_connector::DataConnectorName;
use reqwest::{
    Client,
    header::{InvalidHeaderName, InvalidHeaderValue},
};
use serde::{Deserialize, Serialize};
use std::{collections::BTreeMap, str::FromStr, sync::Arc};
use tracing_util::{ErrorVisibility, SpanVisibility, TraceableError, set_attribute_on_active_span};

#[derive(Debug, thiserror::Error)]
pub enum Error {
    #[error("Error while making the HTTP request to the pre-response plugin {0} - {1}")]
    ErrorWhileMakingHTTPRequestToTheHook(String, reqwest::Error),
    #[error("Error while building the request for the pre-response plugin {0} - {1}")]
    BuildRequestError(String, #[source] BuildRequestError),
    #[error("Reqwest error: {0}")]
    ReqwestError(reqwest::Error),
    #[error("Unexpected status code: {0}")]
    UnexpectedStatusCode(u16),
    #[error("Error parsing the request: {0}")]
    PluginRequestParseError(serde_json::error::Error),
    #[error("Internal error from plugin {plugin_name}")]
    PluginInternalError {
        plugin_name: String,
        error: serde_json::Value,
    },
    #[error("User error from plugin {plugin_name}")]
    PluginUserError {
        plugin_name: String,
        error: serde_json::Value,
    },
}

#[derive(Debug, thiserror::Error)]
pub enum BuildRequestError {
    #[error("Invalid header name {header_name}: {error}")]

View on GitHub (pinned to 724551b9ae)

Solutions

  1. Check which status code appears and curl the exact webhook URL to see the raw response
  2. Correct the path/port in the plugin webhook config
  3. Bypass or configure auth middleware on the plugin route so hook requests reach the plugin
  4. Ensure the plugin returns the protocol's expected status codes on success
Defensive patterns

Strategy: validation

Validate before calling

let status = resp.status();
assert!(status.is_success() || EXPECTED_ERROR_STATUSES.contains(&status.as_u16()), "unexpected {}", status);

Try / catch

let code = resp.status().as_u16();
if !EXPECTED.contains(&code) {
    let body = resp.text().await.unwrap_or_default();
    tracing::error!(code, body, "unexpected hook status");
}

Prevention

When it happens

Trigger: The webhook URL points at the wrong path so the server returns 404, or an ingress/gateway in front of the plugin returns 502/503/504, and the engine maps the unknown code to UnexpectedStatusCode(code).

Common situations: Wrong path or port in plugin webhook config (404s); plugin behind a load balancer that returns 503 during deploys; auth middleware in front of the plugin returning 401/403.

Related errors


AI-assisted analysis of hasura/graphql-engine@724551b9ae (2026-08-28). Data as JSON: /api/errors/ed5cd7a0555d61ce. Report an issue: GitHub.