Bite Apps/SafeFIX

Bite Apps › SafeFIX › FIX session troubleshooting

FIX session troubleshooting — logon, sequence numbers, resends and heartbeats

Why FIX sessions drop, loop on resends or refuse to log on: how Logon, MsgSeqNum, ResendRequest, SequenceReset gap fill and heartbeats work, a table of common symptoms with their causes, and a worked example of a gap recovery.

Have the session log? SafeFIX parses it in your browser — sequence numbers, rejects and order lifecycles, nothing uploaded.

Open SafeFIX — free, in your browser

Session basics

A FIX session is identified by BeginString (8), SenderCompID (49) and TargetCompID (56). Each side numbers the messages it sends with MsgSeqNum (34), starting at 1 at the beginning of a session (usually once a day, by schedule) and incrementing by one per message, administrative messages included. Each side persists both its next outgoing and next expected incoming number. Almost every session problem is a disagreement about one of these two numbers, the CompIDs or the heartbeat.

MessageUsed forKey fields
Logon (35=A)Opens the session; the acceptor answers with its own LogonHeartBtInt (108), EncryptMethod (98), ResetSeqNumFlag (141), NextExpectedMsgSeqNum (789) (FIX 4.4+)
Heartbeat (35=0)Keeps the session alive; answers a TestRequestTestReqID (112) when answering
TestRequest (35=1)Forces the other side to send a HeartbeatTestReqID (112)
ResendRequest (35=2)Asks for a range of messages againBeginSeqNo (7), EndSeqNo (16) (0 = all after BeginSeqNo)
Reject (35=3)Session-level reject of a malformed messageRefSeqNum (45), RefTagID (371), RefMsgType (372), SessionRejectReason (373)
SequenceReset (35=4)Gap fill (123=Y) or hard reset (123=N) of the incoming numberNewSeqNo (36), GapFillFlag (123)
Logout (35=5)Closes the session; the other side answers with a LogoutText (58) with the reason

Logon

The initiator sends Logon with its HeartBtInt; the acceptor validates CompIDs, credentials and the sequence number and replies with its own Logon. If the MsgSeqNum of the Logon is higher than expected, the receiver first accepts the Logon and then sends a ResendRequest; if it is lower, the receiver logs out (or simply disconnects) because messages would be lost silently. ResetSeqNumFlag (141=Y) on Logon asks both sides to start again at 1 — both Logon messages then carry 34=1 and 141=Y. NextExpectedMsgSeqNum (789), available from FIX 4.4, tells the other side which number you expect next so that gaps can be resolved during logon.

Sequence numbers

Incoming MsgSeqNum is…What the receiver does
as expectedProcesses the message and increments the expected number.
higher than expectedSends a ResendRequest for the missing range; queues or discards the newer messages until the gap is filled.
lower, without PossDupFlag (43=Y)A serious error: logs out with a Text such as “MsgSeqNum too low, expecting X but received Y” and disconnects.
lower, with PossDupFlag (43=Y)A resent message: ignored if already processed. OrigSendingTime (122) must be present and not later than SendingTime (52), otherwise Reject 373=10.
garbled (bad BodyLength 9 or CheckSum 10)Discarded without a Reject and without incrementing the expected number; the next good message reveals a gap.

ResendRequest and gap fill

A ResendRequest gives BeginSeqNo (7) and EndSeqNo (16); since FIX 4.2, EndSeqNo 0 means “everything after BeginSeqNo” (FIX 4.0 and 4.1 used 999999). The sender replays application messages from its store with PossDupFlag=Y, the original MsgSeqNum and OrigSendingTime. Administrative messages (Logon, Logout, Heartbeat, TestRequest, ResendRequest, SequenceReset) are never resent: the sender replaces each run of them — and any application messages it decides not to resend, such as orders that are too old to act on — with a SequenceReset with GapFillFlag=Y whose MsgSeqNum is the first skipped number and whose NewSeqNo is the next number that follows. SequenceReset with GapFillFlag=N (or absent) is a hard reset: it sets the receiver's expected number to NewSeqNo regardless of gaps, may only move the number forward, and is meant for disaster recovery.

Heartbeats and TestRequest

Both sides send a Heartbeat whenever they have sent nothing for HeartBtInt seconds. When a side has received nothing for HeartBtInt plus a reasonable transmission allowance (often 20%), it sends a TestRequest with a TestReqID; the answer must be a Heartbeat carrying the same TestReqID (112). If that does not arrive within another interval, the connection is considered lost and should be closed. The HeartBtInt is agreed in the Logon messages and should be the same on both sides.

Symptoms and fixes

SymptomUsual causeWhat to check
Counterparty drops the connection right after your Logon, no LogoutWrong SenderCompID / TargetCompID, wrong BeginString, wrong IP or port, or a TLS mismatch. Many engines simply disconnect unknown sessions.Compare 8, 49, 56 (and 50/57 if used) with the rules of engagement; check the acceptor’s logs or ask the counterparty which logon they saw.
Logout with Text (58) “MsgSeqNum too low, expecting X but received Y”Your outgoing sequence number is lower than the one the counterparty expects — typically after you reset your side (new store, restart without persistence) but they did not.Agree a reset: log on with ResetSeqNumFlag (141=Y) if the counterparty allows it, or set your next outgoing number to X. Never resend old numbers without PossDupFlag.
You receive a ResendRequest (35=2) right after LogonYour sequence number is higher than expected — they missed messages or reset their side.Resend the requested range from your message store with PossDupFlag (43=Y) and OrigSendingTime (122), or gap-fill administrative and stale messages with SequenceReset-GapFill.
Endless resend loopBoth sides request resends at the same time, or a resend contains a gap that is never filled (e.g. the message store lost messages).Look for the first ResendRequest and what was sent back. Fill missing numbers with SequenceReset-GapFill (35=4, 123=Y); as a last resort reset with 35=4 and 123=N.
Session drops every HeartBtInt secondsTestRequest (35=1) not answered with a Heartbeat (35=0) echoing TestReqID (112), or the HeartBtInt (108) values differ.Make sure Heartbeats are sent even under load; check that the Logon from both sides carries the same 108.
Reject (35=3) with 373=10 “SendingTime accuracy problem”SendingTime (52) too far from the receiver’s clock, or OrigSendingTime (122) later than SendingTime on a resent message.Synchronise clocks (NTP/PTP), send UTC timestamps, and keep OrigSendingTime from the original message.
Messages silently ignored, then a gap and a ResendRequestGarbled messages: wrong BodyLength (9) or CheckSum (10), or a SOH inside a value. Garbled messages are discarded without a Reject.Recompute 9 and 10 after any edit — the SafeFIX anonymizer does this — and never put SOH (0x01) in a data value.
Logon rejected with a business reason in Text (58)Credentials (Username 553 / Password 554), entitlement, session schedule or a duplicate connection for the same CompIDs.Read 58 literally; check the session window and that no other process is logged on with the same CompIDs.

Example: recovering a gap

The counterparty expects 101 but our side logs on with 105 (four messages were sent while their side was down):

us   → 35=A  34=105  108=30
them → 35=A  34=88   108=30
them → 35=2  34=89   7=101  16=104                  (ResendRequest: 101 to 104)
us   → 35=4  34=101  43=Y  123=Y  36=103            (gap fill: 101–102 were administrative)
us   → 35=D  34=103  43=Y  122=20261112-08:15:02.120 (resent NewOrderSingle)
us   → 35=F  34=104  43=Y  122=20261112-08:15:09.004 (resent OrderCancelRequest)
us   → 35=0  34=106                                  (normal traffic continues after the Logon's 105)

Logs like this are where most session problems are found. SafeFIX reads them in the browser — sequence numbers, PossDupFlag, rejects and the order lifecycle side by side — without uploading them.

FAQ

What does “MsgSeqNum too low” mean in FIX?

The receiver expected a higher sequence number than the one in your message, and the message is not marked as a possible duplicate (43=Y). This usually means one side reset its sequence numbers and the other did not. The receiver logs out; agree a reset (141=Y on Logon) or set your outgoing number to the expected one.

What is a SequenceReset GapFill?

A SequenceReset (35=4) with GapFillFlag (123) = Y that replaces messages the sender will not resend — administrative messages or stale orders. Its MsgSeqNum is the first skipped number and NewSeqNo (36) the next number that follows.

What does EndSeqNo 0 mean in a ResendRequest?

Since FIX 4.2, EndSeqNo (16) = 0 means all messages from BeginSeqNo (7) onwards. FIX 4.0 and 4.1 used 999999 for the same purpose.

When should ResetSeqNumFlag be used?

ResetSeqNumFlag (141=Y) on a Logon resets both sides to sequence number 1. It is common for daily session starts and is sometimes the agreed way to recover; it must be supported and expected by the counterparty, because any unconfirmed messages are lost.

Why is my FIX message ignored without a Reject?

Messages with a wrong BodyLength (9) or CheckSum (10), or with a SOH character inside a value, are treated as garbled and discarded silently. The next valid message then shows a gap and triggers a ResendRequest.

Last updated 2026-11-12. Something wrong or missing? Email support@biteapps.cc.