nautechsystems/nautilus_trader · error

Wrong template ID: expected

Error message

Wrong template ID: expected {execution_report_event_codec::SBE_TEMPLATE_ID}, received {template_id}

What it means

decode_execution_report validates the 8-byte SBE message header of a Binance Spot user-data binary frame before decoding. This error fires when the header's template ID is not the ExecutionReportEvent template (603), meaning the payload is a different SBE message type routed to the wrong decoder. The library bails early instead of misinterpreting bytes of another template as an execution report.

Solutions

  1. Verify the frame actually came from the executionReport user-data event before calling this decoder
  2. Parse the SBE header's template ID first and dispatch to the matching decoder (601→balance update, 603→execution report, 607→account position)
  3. Regenerate/update the codecs if Binance bumped the template ID in a schema revision
  4. Fix test fixtures to use a buffer encoded with the execution_report_event_codec

Example fix

// before
let report = decode_execution_report(raw_frame).unwrap();
// after
let template_id = u16::from_be_bytes([raw_frame[2], raw_frame[3]]);
let report = match template_id {
    execution_report_event_codec::SBE_TEMPLATE_ID => decode_execution_report(raw_frame)?,
    _ => return Err(anyhow::format_err!("unexpected template {template_id}")),
};
Defensive patterns

Strategy: validation

Validate before calling

pub fn is_execution_report_frame(data: &[u8]) -> bool {
    data.len() >= 8
        && u16::from_be_bytes([data[2], data[3]]) == execution_report_event_codec::SBE_TEMPLATE_ID
}

Type guard

fn as_execution_report(data: &[u8]) -> Option<&[u8]> {
    (data.len() >= 8
        && u16::from_be_bytes([data[2], data[3]]) == execution_report_event_codec::SBE_TEMPLATE_ID)
    .then_some(data)
}

Try / catch

match decode_execution_report(&frame) {
    Ok(report) => handle(report),
    Err(e) if e.to_string().contains("Wrong template ID") => route_to_other_decoder(&frame),
    Err(e) => log::warn!("SBE decode failed: {e}"),
}

Prevention

When it happens

Trigger: Calling decode_execution_report with a binary payload whose SBE header (bytes 2-3) carries a template ID other than 603 — e.g. a BalanceUpdateEvent (601) or OutboundAccountPositionEvent (607) frame, an out-of-order/duplicated stream event, or a hand-built test buffer with the wrong template.

Common situations: Routing frames by stream name instead of parsing the template ID before dispatch; Binance changing or the user mis-mapping user-data event types; writing unit tests that reuse a captured buffer from a different event type.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of nautechsystems/nautilus_trader@18893faf8b (2026-09-08). Data as JSON: /api/errors/168eb7482d97fe56. Report an issue: GitHub.

Appendix: source

Thrown at crates/adapters/binance/src/spot/websocket/trading/decode_sbe.rs:77

///
/// Returns error if the buffer is too short, the template ID is wrong,
/// the schema ID does not match, or variable-length data is malformed.
pub fn decode_execution_report(data: &[u8]) -> anyhow::Result<BinanceSpotExecutionReport> {
    if data.len() < HEADER_LEN {
        anyhow::bail!(
            "Buffer too short for SBE header: expected {HEADER_LEN}, was {}",
            data.len()
        );
    }

    let buf = ReadBuf::new(data);
    let block_length = buf.get_u16_at(0);
    let template_id = buf.get_u16_at(2);
    let schema_id = buf.get_u16_at(4);
    let version = buf.get_u16_at(6);

    if template_id != execution_report_event_codec::SBE_TEMPLATE_ID {
        anyhow::bail!(
            "Wrong template ID: expected {}, received {template_id}",
            execution_report_event_codec::SBE_TEMPLATE_ID
        );
    }

    if schema_id != crate::spot::sbe::spot::SBE_SCHEMA_ID {
        anyhow::bail!(
            "Wrong schema ID: expected {}, received {schema_id}",
            crate::spot::sbe::spot::SBE_SCHEMA_ID
        );
    }

    let min_block_len = execution_report_min_block_length(version);
    if usize::from(block_length) < min_block_len {
        anyhow::bail!(
            "SBE execution report block length too short: expected at least {min_block_len}, was {block_length}"
        );
    }

View on GitHub (pinned to 18893faf8b)