A FIX log analyzer built for NDA-bound teams
Support engineers, FIX onboarding teams, quants and prop-firm developers handle production FIX logs that contain account numbers, CompIDs, credentials and live order flow. Most compliance policies forbid pasting those logs into cloud parsers or AI chat tools. SafeFIX is a single static page: the parser, the dictionary validator, the anonymizer and the latency inspector all run in JavaScript inside your browser tab. Nothing is uploaded, nothing is stored on a server, and the page keeps working with the network cable unplugged once it has loaded.
How to verify it yourself: open Chrome DevTools → Network, tick Preserve log, then paste or drop a 10 MB log and analyze it. The request list stays empty. The page also ships a strict Content-Security-Policy (default-src 'none') whose only allowed outbound destination is api.polar.sh, used once when you activate a Pro license key.
What SafeFIX does
1. Dirty log multi-parser
Real logs are never clean. SafeFIX strips engine timestamps and prefixes (QuickFIX, QuickFIX/J and QuickFIX/n file logs, FIX.4.4:SENDER->TARGET session tags, syslog lines, IN/OUT markers), detects the delimiter per line — raw SOH \x01, |, ^A, <SOH>, TAB or ; — and handles several messages on one line, header-less fragments and values that contain the delimiter. Lines without FIX content are skipped from analysis and passed through unchanged by the anonymizer.
2. Custom data dictionary support
Pick a built-in dictionary (FIX 4.2, FIX 4.4, Binance Spot FIX, cTrader FIX or a oneZero / PrimeXM bridge profile) and drop your counterparty's QuickFIX-format XML (FIX44.xml, FIX50SP2.xml + FIXT11.xml, or a broker-customised file) on top. Custom fields in the user-defined range 5000–9999 are decoded with their names and enum values, required fields are checked per message type, and every tag that would trigger a session-level reject on a strict acceptor is highlighted with the matching SessionRejectReason (373) code.
3. Log anonymizer for support tickets
One click replaces Account (1), Username (553), Password (554), NewPassword (925), SenderCompID / TargetCompID (49 / 56), OnBehalfOf and DeliverTo IDs, ClOrdID (11), OrigClOrdID (41), OrderID (37), PartyID (448) and any custom tags you list. Tokens are consistent across the whole file — a ClOrdID becomes the same token wherever it appears, so order chains stay traceable. SafeFIX then recalculates BodyLength (9) and CheckSum (10) for every rewritten message, updates data-length pairs such as RawDataLength (95) / RawData (96), and re-checks each output message, so the sanitized log still replays through an engine.
4. Order lifecycle & latency inspector
Messages are grouped into order chains by ClOrdID (11), OrigClOrdID (41) and OrderID (37): 35=D → 35=8 New → 35=G → 35=8 Replaced → 35=8 Fill, or 35=F → 35=8 Canceled. Each hop shows its delta in milliseconds from SendingTime (52), TransactTime (60) or the engine's own log timestamp, with count / min / median / mean / p95 / p99 / max for New→Ack, New→First fill, Replace→Ack, Cancel→Ack and New→Reject. Any two messages can be compared side by side, with changed, added and removed tags highlighted.
How FIX BodyLength (9) and CheckSum (10) are calculated
BodyLength (9) is the number of bytes after the 9=…<SOH> field up to and including the SOH that precedes 10=. CheckSum (10) is the sum of every byte from the start of 8= up to and including that same SOH, modulo 256, written as exactly three digits. Both are computed on the wire format, where the delimiter is the single byte 0x01 — if your log shows | or ^A, count each delimiter as one byte.
8=FIX.4.4|9=55|35=0|49=CLIENT|56=BROKER|34=2|52=20261102-13:45:31.120|10=077| Body (after 9=55|, up to and including the | before 10=): 35=0|49=CLIENT|56=BROKER|34=2|52=20261102-13:45:31.120| → 55 bytes → 9=55 Sum of all bytes from "8=" through that last SOH = 3661 3661 mod 256 = 77 → 10=077
Editing any value — for example masking a CompID — changes both numbers. Engines reject a message with a wrong BodyLength or CheckSum before the application ever sees it (QuickFIX typically logs it as a garbled message and drops it), which is why naive find-and-replace sanitizing breaks log replay.
Troubleshooting 35=3 session-level rejects
A Reject (35=3) means the counterparty's FIX engine refused your message before it reached their business logic. Read RefSeqNum (45) to find the rejected message, RefTagID (371) for the offending tag, RefMsgType (372) for its type, and SessionRejectReason (373) for the cause. SafeFIX links the reject to the original message automatically.
| 373 | Reason | Typical cause and fix |
|---|---|---|
| 0 | Invalid tag number | The tag is not in their dictionary at all — often a custom tag in the 5000–9999 range. Remove it or ask them to add it. QuickFIX acceptors relax this with ValidateUserDefinedFields=N or AllowUnknownMsgFields=Y. |
| 1 | Required tag missing | Their dictionary marks the field required="Y" for this MsgType (including required components and groups). Compare against their rules of engagement. |
| 2 | Tag not defined for this message type | The tag exists but not in this message. Move it, or have them extend the message definition. |
| 3 | Undefined tag | Similar to 0 on many engines — the number is unknown. |
| 4 | Tag specified without a value | An empty field such as 58=|. Never send empty tags; ValidateFieldsHaveValues=N relaxes the check. |
| 5 | Value is incorrect (out of range) for this tag | An enum value they do not support, e.g. an OrdType (40) or TimeInForce (59) the venue does not offer. |
| 6 | Incorrect data format for value | A decimal in an INT field, a timestamp not in YYYYMMDD-HH:MM:SS[.sss], a lowercase boolean. |
| 9 | CompID problem | SenderCompID / TargetCompID or SubIDs do not match the session configured on their side. |
| 10 | SendingTime accuracy problem | Your clock drifts beyond their tolerance. Sync with NTP/PTP; QuickFIX controls the check with CheckLatency and MaxLatency. |
| 11 | Invalid MsgType | The message type is not supported on that session. |
| 13 | Tag appears more than once | Usually a repeating group sent without its NumInGroup field, or a header tag duplicated by middleware. |
| 14 | Tag specified out of required order | Header fields must come first and 8, 9, 35 must be the first three. ValidateFieldsOutOfOrder=N relaxes ordering checks. |
| 15 | Repeating group fields out of order | The first field after NumInGroup must be the group's delimiter field, and members must follow the dictionary order. |
| 16 | Incorrect NumInGroup count | The number of entries does not match the NumInGroup value. |
| 99 | Other | Read Text (58) and the counterparty's rules of engagement. |
Business Message Reject (35=j)
A 35=j passes session validation but is refused by the application. BusinessRejectReason (380): 0 Other, 1 Unknown ID, 2 Unknown security, 3 Unsupported message type, 4 Application not available, 5 Conditionally required field missing, 6 Not authorized, 7 DeliverTo firm not available, 18 Invalid price increment. Order-level refusals arrive instead as an Execution Report with OrdStatus (39)=8 and OrdRejReason (103), or as an Order Cancel Reject (35=9) with CxlRejReason (102).
QuickFIX DataDictionary settings that change validation
UseDataDictionary, DataDictionary (FIX 4.x), TransportDataDictionary + AppDataDictionary (FIXT 1.1 / FIX 5.0), ValidateUserDefinedFields, AllowUnknownMsgFields, ValidateFieldsOutOfOrder, ValidateFieldsHaveValues, CheckLatency / MaxLatency and CheckCompID. When a counterparty rejects with 373=0 or 373=2 for a tag you know is valid, the usual root cause is that their acceptor loads an older or un-customised XML.
FIX tag reference — the tags you read every day
| Tag | Name | Meaning |
|---|---|---|
| 8 | BeginString | Protocol version: FIX.4.2, FIX.4.4, FIXT.1.1. Always first. |
| 9 | BodyLength | Byte count of the body; always second. |
| 10 | CheckSum | Byte sum modulo 256, three digits; always last. |
| 35 | MsgType | Message type (see the table below); always third. |
| 34 | MsgSeqNum | Per-direction sequence number used for gap detection and resends. |
| 49 / 56 | SenderCompID / TargetCompID | Session identity of each side. |
| 52 | SendingTime | UTC time the message was sent; basis for latency deltas. |
| 43 / 97 / 122 | PossDupFlag / PossResend / OrigSendingTime | Set on retransmitted messages after a ResendRequest. |
| 1 | Account | Trading account — sensitive, masked by the anonymizer. |
| 11 | ClOrdID | Client-assigned order ID, unique per order request. |
| 41 | OrigClOrdID | ClOrdID of the order being replaced or canceled. |
| 37 | OrderID | Venue-assigned order ID. |
| 17 | ExecID | Unique ID of each execution report. |
| 150 | ExecType | What this report is: 0 New, 4 Canceled, 5 Replaced, 8 Rejected, F Trade (4.4), I Order status. |
| 39 | OrdStatus | Order state after this report: 0 New, 1 Partially filled, 2 Filled, 4 Canceled, 8 Rejected. |
| 54 | Side | 1 Buy, 2 Sell, 5 Sell short. |
| 55 | Symbol | Instrument symbol (venue-specific symbology). |
| 38 | OrderQty | Order quantity. |
| 40 | OrdType | 1 Market, 2 Limit, 3 Stop, 4 Stop limit. |
| 44 / 99 | Price / StopPx | Limit price / stop trigger price. |
| 59 | TimeInForce | 0 Day, 1 GTC, 3 IOC, 4 FOK, 6 GTD. |
| 32 / 31 | LastQty / LastPx | Quantity and price of this fill (LastShares in FIX 4.2). |
| 14 / 151 / 6 | CumQty / LeavesQty / AvgPx | Filled so far, remaining open, average fill price. |
| 60 | TransactTime | Business time of the event at the originator. |
| 58 | Text | Free text — often contains the real reject reason. |
| 45 / 371 / 372 / 373 | RefSeqNum / RefTagID / RefMsgType / SessionRejectReason | Fields of a 35=3 Reject. |
| 380 / 379 | BusinessRejectReason / BusinessRejectRefID | Fields of a 35=j Business Message Reject. |
| 103 / 102 / 434 | OrdRejReason / CxlRejReason / CxlRejResponseTo | Order and cancel reject reasons. |
| 98 / 108 / 141 | EncryptMethod / HeartBtInt / ResetSeqNumFlag | Logon parameters. |
| 553 / 554 / 95 / 96 | Username / Password / RawDataLength / RawData | Logon credentials and signatures — masked by the anonymizer. |
| 112 | TestReqID | Echoed in the Heartbeat that answers a TestRequest. |
| 1128 | ApplVerID | Application version on FIXT.1.1 sessions (FIX 5.0 SP2 = 9). |
| 5000–9999 | User-defined | Custom tags agreed bilaterally — load the counterparty's XML to decode them. |
MsgType (35) values
| 35 | Message | 35 | Message |
|---|---|---|---|
| 0 | Heartbeat | D | NewOrderSingle |
| 1 | TestRequest | F | OrderCancelRequest |
| 2 | ResendRequest | G | OrderCancelReplaceRequest |
| 3 | Reject (session level) | 8 | ExecutionReport |
| 4 | SequenceReset | 9 | OrderCancelReject |
| 5 | Logout | H | OrderStatusRequest |
| A | Logon | j | BusinessMessageReject |
| V | MarketDataRequest | W / X | MarketData snapshot / incremental refresh |
| Y | MarketDataRequestReject | AE | TradeCaptureReport |
| AF | OrderMassStatusRequest | AN / AP | RequestForPositions / PositionReport |
FAQ
Does SafeFIX upload my FIX logs?
No. Parsing, validation, anonymization and latency analysis run entirely in your browser tab. The page's Content-Security-Policy blocks every network destination except api.polar.sh, which is contacted only when you activate a Pro license key — and only the key is sent. You can confirm in DevTools → Network that analyzing a log makes zero requests.
Which log formats can it read?
Raw SOH-delimited messages, pipe (|), ^A, <SOH>, TAB or semicolon delimited logs, with or without engine prefixes such as QuickFIX "YYYYMMDD-HH:MM:SS.sss : " timestamps, session identifiers or IN/OUT markers. Several messages per line and header-less fragments are handled.
Can I validate against my broker's custom FIX dictionary?
Yes. Load any QuickFIX-format XML DataDictionary (FIX44.xml, FIX50SP2.xml with FIXT11.xml, or a broker-customised file). It is layered over the selected built-in dictionary, so custom tags in the 5000–9999 range are decoded and required fields are checked per message type. Pro users can save dictionaries in the browser.
Will an anonymized log still replay through a FIX engine?
Yes. After masking, SafeFIX recalculates BodyLength (9) and CheckSum (10) for every message, updates data-length fields such as RawDataLength (95), keeps tokens consistent across the file and re-verifies each output message. Choose SOH output to feed the file straight into a replay tool.
How is order latency measured?
SafeFIX links messages by ClOrdID (11), OrigClOrdID (41) and OrderID (37) and computes deltas from SendingTime (52), TransactTime (60) or the engine's log-line timestamp. SendingTime is stamped by each sender, so cross-host deltas include clock skew; the log timestamp gives single-host round-trip times.
What does a 35=3 reject with 373=0 mean?
SessionRejectReason 0 is "Invalid tag number": the counterparty's engine does not know the tag in RefTagID (371). It is usually a custom tag missing from their data dictionary. Remove the tag or ask them to add it; QuickFIX acceptors can also relax the check with ValidateUserDefinedFields=N.
What does SafeFIX Pro include, and is it a subscription?
Pro is a one-time $19 payment, not a subscription. It unlocks large file processing (100 MB+ streamed locally), downloads of sanitized logs and token maps, saving custom XML dictionaries in the browser, and CSV/Excel latency report exports. Payment is processed by Polar as Merchant of Record.
Can you build a custom FIX bridge or fix our dictionary problems?
Yes. Bite Apps is run by a trading platform support engineer with six years of daily FIX work. Email support@biteapps.cc for custom FIX API bridges, QuickFIX dictionary troubleshooting, log forensics and trading infrastructure support.