Back to Blog
Technology

FIX Reject Storms: 12 Production 35=3 Errors and the 60‑Second Triage Playbook

Priya DesaiPriya Desai
April 19, 20267 min read21 views
FIX Reject Storms: 12 Production 35=3 Errors and the 60‑Second Triage Playbook

FIX Session Rejects (35=3) are the kind of “small” errors that can quietly break fills, stall market data subscriptions, or trigger cascading reconnects—right when your dealing desk and support team can least afford ambiguity.

This post is a production-focused checklist: the 12 FIX Reject Reasons brokers and prop firms most commonly see, and the fastest way to triage each. The goal isn’t to memorize the FIX spec—it’s to shorten mean-time-to-innocence between your bridge, your FIX engine, and your liquidity counterparty.

1) Invalid tag number (SessionRejectReason=0)

This shows up when you send a tag the counterparty’s FIX engine doesn’t recognize in that message context (or version). It’s common after a config change, a new LP venue rollout, or when a bridge is upgraded and starts emitting optional tags.

Fast triage

  • Confirm BeginString (8=) and MsgType (35=) match the session you think you’re on.
  • Diff the outbound message against the counterparty’s latest field list for that flow (order, cancel, logon, etc.).
  • If the tag is “valid FIX” but not “valid for them,” remove it or gate it by counterparty profile.

2) Required tag missing (SessionRejectReason=1)

A required field was omitted. In production, this often happens due to conditional mapping rules (e.g., only sending a tag for certain symbols, accounts, or order types) or a serialization bug when a value is blank.

Fast triage

  • Look at the reject’s RefTagID (371=) and confirm the tag is present and non-empty.
  • Check whether your bridge/adapter is stripping fields on retries.
  • Verify you’re not mixing message templates (e.g., sending an order template that assumes a different venue).

3) Tag not defined for this message type (SessionRejectReason=2)

The tag exists in FIX, but it’s not allowed in that specific MsgType (or the counterparty enforces a stricter schema). This is a frequent cause of “it works in UAT but not in prod” when prod has stricter validation.

Fast triage

  • Confirm the outbound MsgType (35=) is correct for the action (e.g., NewOrderSingle vs OrderCancelReplace).
  • Remove the tag or move it to the correct message (or repeating group) if your counterparty expects it elsewhere.
  • Validate your FIX dictionary (data dictionary / data schema) matches the counterparty’s implementation notes.

4) Undefined tag (SessionRejectReason=3)

Similar to invalid tag number, but typically indicates the counterparty doesn’t define this tag in their implementation (even if it exists in “base FIX”). You’ll see this with venue-specific extensions, custom tags, or when you accidentally send tags from another LP profile.

Fast triage

  • Identify whether the tag is custom (e.g., >5000) or a standard tag they simply don’t support.
  • Check if your routing layer attached the wrong “LP profile” to the session.
  • If you must send it, negotiate support with the counterparty; otherwise, remove it.

5) Tag specified without a value (SessionRejectReason=4)

A tag is present but empty (e.g., 55=). This is often a data quality issue upstream (symbol mapping, account mapping, or instrument master) rather than a FIX engine problem.

Fast triage

  • Use the reject’s RefTagID (371=) to pinpoint the empty field.
  • Trace the field back to your source of truth: symbol table, account config, or order entry gateway.
  • Add “non-empty” validation before serialization so you fail fast internally (and log the real cause).

6) Incorrect data format for value (SessionRejectReason=6)

The value is present but formatted incorrectly: timestamps, decimals, enums, or boolean fields are the usual suspects. This can happen after locale/decimal formatting changes, or when a component starts emitting scientific notation.

Fast triage

  • Inspect the rejected field and confirm it matches the expected type (e.g., UTCTimestamp format for SendingTime (52=)).
  • Check rounding/precision rules for price and quantity fields, especially on CFDs/crypto where decimals vary.
  • Confirm your FIX engine is not converting numeric strings using OS locale settings.

7) Value is incorrect (out of range / invalid enum) (SessionRejectReason=5)

Here the format is fine, but the value is not acceptable (e.g., an enum not supported by the counterparty). Common cases: unsupported OrdType (40=), TimeInForce (59=), or SecurityType (167=).

Fast triage

  • Map the rejected value to the counterparty’s supported list (their “allowed values” are what matter).
  • Confirm the order type is appropriate for the instrument (spot vs CFD vs crypto).
  • If you’re a broker, ensure your client-facing platform isn’t allowing order attributes your LP won’t accept.

8) MsgSeqNum too low / too high (sequence number issues)

Sequence desync is one of the most common production causes of 35=3 storms—especially after reconnects, failovers, or active/active setups. If either side believes it missed messages, you can get a loop of rejects and resends.

Fast triage

  • Check your last inbound/outbound MsgSeqNum (34=) at disconnect time.
  • If you run multiple instances, confirm only one is writing sequence state for that session.
  • Use ResendRequest (35=2) and SequenceReset (35=4) deliberately—don’t “spam reset” without confirming the counterparty’s expected recovery flow.

9) SendingTime accuracy / time drift (often tied to 52=)

Many counterparties enforce strict rules around SendingTime (52=) (and sometimes OrigSendingTime (122=) on resent messages). Clock drift, timezone mistakes, or incorrect resend handling can trigger rejects.

Fast triage

  • Verify NTP is healthy on all FIX nodes (primary + standby) and that drift is within your counterparty’s tolerance.
  • Confirm 52 is UTC and correctly formatted.
  • On resends, ensure you populate 122=OrigSendingTime and keep 52=SendingTime as “now,” if that’s what your counterparty expects.

10) CompID / SubID / LocationID mismatch (49/56/50/57/142)

If SenderCompID (49=), TargetCompID (56=) (or related IDs) don’t match what the counterparty provisioned, you may see session rejects or immediate logout loops. This often happens when moving from UAT to production or when cloning configs between LPs.

Fast triage

  • Compare the live logon message to the counterparty’s onboarding sheet (character-for-character).
  • Check you didn’t swap 49/56 (it happens during quick config edits).
  • If you use SubID (50/57) routing, confirm the correct values per trading account/stream.

11) Invalid checksum / BodyLength (10= / 9=)

This is usually not “human error”—it’s a serialization issue, character encoding problem, or a message being modified in transit (rare, but possible with misbehaving middleware). You’ll often see it right after a FIX engine upgrade or a custom adapter change.

Fast triage

  • Confirm your FIX engine is emitting SOH delimiters correctly (not | in production wire format).
  • Check for non-ASCII characters sneaking into free-text fields.
  • Ensure no proxy or logging middleware is rewriting payloads.

12) Repeating group count / group structure errors

Groups (e.g., party IDs, legs, allocations, MD entries) are a common source of rejects when the NumInGroup count doesn’t match, tags are out of order, or required group fields are missing.

Fast triage

  • Validate the group count tag matches the actual number of group instances.
  • Confirm tag ordering rules for that counterparty (some engines are strict).
  • If you aggregate multiple sources (CRM → bridge → FIX), ensure no layer “merges” groups incorrectly.

A practical triage workflow your ops team can run in minutes

When 35=3 appears, the fastest path is to classify it as schema, state, or identity/time—then route to the right owner (ops vs dev vs counterparty).

Use this quick workflow

  • Step 1: Identify the failing layer: session logon vs application flow (orders/market data). If it’s during logon, suspect CompIDs, seqnums, encryption settings, or time.
  • Step 2: Read the reject fields: prioritize RefTagID (371=), RefMsgType (372=), and SessionRejectReason (373=).
  • Step 3: Reproduce with one message: send a single minimal valid message for that flow (don’t test with a full feature payload).
  • Step 4: Decide “reset or repair”: sequence issues may need controlled resend/reset; schema issues need message changes.

Operational note: if you’re regulated (or operating under a licensed entity), treat repeated rejects as an execution quality and operational resilience signal. Keep audit-ready logs, change records, and incident notes, and check local regulations for any reporting or recordkeeping obligations.

The Bottom Line

FIX Session Rejects (35=3) are usually predictable: a tag problem, a sequencing/state problem, or an identity/time mismatch.

If you standardize a triage playbook around 371/372/373, you can cut troubleshooting from hours to minutes and avoid “reject storms” during peak volatility.

If you want help hardening FIX connectivity, monitoring, and bridge-to-LP routing, talk to Brokeret: /get-started.

Share:TwitterLinkedIn